# Cross-Device Alarm Dismissal

## Overview

When an alarm is dismissed on one device, it automatically dismisses on all other connected devices. This prevents the same alarm from ringing on multiple devices.

## How It Works

### Architecture

```
Device A (dismisses alarm)
    ↓
AlarmManager detects dismissal
    ↓
Calls onAlarmDismissed callback
    ↓
GatewayClient sends {"type":"alarm.dismiss","alarmId":"..."}
    ↓
OAuth Proxy receives message
    ↓
Proxy broadcasts {"type":"event","event":"mobile.alarm.dismissed","payload":{...}} to ALL clients
    ↓
Device B, C, D... receive broadcast
    ↓
Each device silently dismisses that alarm ID
```

### Implementation Details

**Mobile App (Alfred Mobile):**

1. **AlarmManager.kt** - Singleton with `onAlarmDismissed` callback
   - When `dismissAlarm(alarmId)` is called, triggers callback
   - Callback is set by MainScreen to notify GatewayClient

2. **GatewayClient.kt** - Added `dismissAlarm(alarmId)` method
   - Sends `{"type":"alarm.dismiss","alarmId":"..."}` via WebSocket
   - Receives `mobile.alarm.dismissed` events
   - Calls `listener.onAlarmDismissed(alarmId)`

3. **GatewayListener interface** - Added `onAlarmDismissed(alarmId)` callback

4. **MainScreen.kt** - Wires everything together
   - Sets `alarmManager.onAlarmDismissed` callback to call `gatewayClient.dismissAlarm()`
   - Implements `onAlarmDismissed()` to dismiss alarm locally without re-broadcasting

**Proxy Server (alfred-proxy):**

1. **Message Interception** - Checks for `alarm.dismiss` messages from clients
2. **Broadcasting** - Creates `mobile.alarm.dismissed` event and sends to all connected clients
3. **No OpenClaw Forwarding** - Dismiss events are handled internally, not sent to OpenClaw

### Message Format

**Client → Proxy (dismiss):**
```json
{
  "type": "alarm.dismiss",
  "alarmId": "alarm-1770148325914",
  "timestamp": 1770148325914
}
```

**Proxy → All Clients (broadcast):**
```json
{
  "type": "event",
  "event": "mobile.alarm.dismissed",
  "payload": {
    "alarmId": "alarm-1770148325914",
    "timestamp": 1770148325914
  }
}
```

## Testing

### Prerequisites
- Alfred Mobile app installed on 2+ devices
- All devices connected to the same proxy (alfred-app.dnspegasus.net)
- All devices authenticated with the same user account

### Test Procedure

1. **Set an alarm:**
   ```bash
   # Via Alfred or directly:
   curl -X POST http://localhost:18790/api/notify \
     -H "Content-Type: application/json" \
     -d '{"notificationType":"alarm","title":"Cross-Device Test","message":"Testing sync","priority":"high","sound":true,"vibrate":true}'
   ```

2. **Verify alarm triggers on all devices**
   - Sound and vibration should start on all devices
   - Full-screen AlarmActivity should appear on all devices

3. **Dismiss on ONE device**
   - Use either the notification dismiss button OR the full-screen dismiss button
   - Watch the other devices

4. **Expected behavior:**
   - ✅ Alarm stops immediately on the device you dismissed
   - ✅ Alarm automatically stops on all other devices within 1-2 seconds
   - ✅ Notifications clear on all devices
   - ✅ No re-triggering or duplicate dismissals

### Troubleshooting

**Alarm doesn't dismiss on other devices:**
- Check proxy logs: `tail -f /tmp/alfred-proxy-new.log`
- Look for: `[proxy] Alarm dismiss received:` and `Broadcasted alarm dismiss to X client(s)`
- Verify all devices are connected: broadcast count should match device count

**Alarm dismisses multiple times:**
- Check for infinite loops in logs
- Verify the `onAlarmDismissed` callback is properly cleared before local dismissal in broadcast handler

**Delay in dismissal:**
- Normal: 1-2 second delay is expected (network latency)
- Check WebSocket connection health on all devices
- Verify proxy is not rate-limiting or buffering messages

## Logging

**Mobile App (adb logcat):**
```bash
# Watch for alarm dismissal events
adb logcat | grep -E "AlarmManager.*Dismiss|onAlarmDismissed|alarm.dismiss"

# Example output:
# AlarmManager: Dismissing alarm: alarm-1770148325914
# MainScreen: Alarm dismissed locally, broadcasting: alarm-1770148325914
# GatewayClient: Sending alarm dismiss: alarm-1770148325914
# MainScreen: Received alarm dismiss broadcast: alarm-1770148325914
```

**Proxy Server:**
```bash
tail -f /tmp/alfred-proxy-new.log | grep -i alarm

# Example output:
# [proxy] Alarm dismiss received: alarm-1770148325914
# [proxy] Broadcasted alarm dismiss to 2 client(s)
```

## Edge Cases

### Same Device Dismissal
- Device dismisses alarm → triggers broadcast → receives own broadcast
- **Handled:** Callback is temporarily cleared before processing broadcast to avoid re-dismissal

### Partial Network Failure
- One device offline when alarm is dismissed
- **Behavior:** Alarm continues ringing on offline device until manually dismissed
- **No persistence:** Dismiss events are not queued or stored

### Multiple Simultaneous Dismissals
- Two devices dismiss the same alarm within milliseconds
- **Behavior:** Each sends dismiss event, proxy broadcasts both
- **Handled:** AlarmManager is idempotent - dismissing already-dismissed alarm is safe

### Connection During Active Alarm
- Device connects while alarm is already ringing on other devices
- **Behavior:** Newly connected device starts alarm (receives notification)
- **No state sync:** Active alarm state is not synchronized on connect

## Future Enhancements

### Possible Improvements:
1. **State sync on connect** - Send active alarm list when device connects
2. **Dismiss persistence** - Store recent dismissals (last 5 minutes) and send to new connections
3. **Snooze sync** - Extend cross-device sync to snooze actions
4. **Offline queue** - Queue dismiss events when offline, send when reconnected

### Not Implemented:
- Alarm creation sync (alarms created via Alfred cron, not device-local)
- Snooze functionality
- Custom alarm sounds per device
- Volume control sync

## Security Considerations

- **Authentication:** All clients must be authenticated via OAuth to connect to proxy
- **Authorization:** Same user account across devices (enforced by OAuth)
- **No spoofing:** Alarm IDs include timestamp - difficult to forge
- **Rate limiting:** Consider adding if dismiss spam becomes an issue

## Version History

- **v1.0.1** (2026-02-03) - Initial cross-device alarm dismissal implementation
- **v1.0.0** (2026-02-03) - Basic alarm system with single-device dismissal

## Related Documentation

- `ALARMS.md` - Full alarm system documentation
- `ARCHITECTURE.md` - Overall system architecture
- `AGENT_TOOLS.md` - Mobile-notify and alarms skill integration
- `~/.openclaw/workspace/skills/alarms/SKILL.md` - Alarm scheduling skill
