M4: Connection service and sync engine (ENG-259) #24

Merged demi.dev
targeting main from stoopkidddd/eng-259-m4-connection-service-and-sync-engine-foreground-websocket
Merged opened by and assigned to demi.dev
targeting main from stoopkidddd/eng-259-m4-connection-service-and-sync-engine-foreground-websocket

Linear: https://linear.app/engineereddev/issue/ENG-259

What's in this PR (54 files, all under apps/android/):

  • service/: ConnectionService, a remoteMessaging foreground service with a silent connection channel ("Connected to " / "Reconnecting…" / "Offline", tap opens the app), START_STICKY. Started after pairing, on launch when paired and by BootReceiver (BOOT_COMPLETED, MY_PACKAGE_REPLACED); stopped only on sign-out or revoke.
  • net/ws/: WsClient (OkHttp, pingInterval 0): hello with since = lastPushId, pong, a 2 × WS_PING_SECONDS + 15 s watchdog, full-jitter backoff 1/2/5/10/30/60 s reset after 60 s, an immediate reconnect from the NetworkCallback, 4401 → sign-out flow, 1012 → retry after 1–3 s; StateFlow<ConnectionState>.
  • data/db/: Room v1 (pushes with envelope JSON, file id/size and a notified flag; devices; sync_state), with the exported schema. Plaintext is never stored.
  • sync/: SyncEngine applies frames, pages GET /api/pushes on resync until it overlaps Room, advances lastPushId per push, and hands each eligible push to PushSink once (a no-op for now).
  • Settings shows the live connection state.
  • Roborazzi: 6 new baselines connection-{connected,reconnecting,offline}-{light,dark} and 6 re-recorded on purpose main-shell-{settings,settings-busy,sign-out-error}-{light,dark}. The commit says so.

Verify (lead, rebased on e5f90f2):

  • mise run check:android → exit 0 (Spotless, detekt, Lint clean, Roborazzi verify)
  • ./gradlew pixel6Api34DebugAndroidTest --rerun → BUILD SUCCESSFUL, "Running tests on devices: pixel6Api34"
  • ./gradlew testDebugUnitTest --rerun → 146 tests, 0 failures
  • Implementer's forced-Doze acceptance (local server, emulator): connected for all 10 one-minute samples with screen off and deviceidle force-idle; after unforce, Room held all 12 test pushes (10 sent during Doze), all marked notified. Package replacement restored the connection.

Deviations:

  • No managed-device harness server exists yet (ENG-268), so connection, Doze and package replacement were checked with the issue's permitted local-server fallback.
  • Android 14 raised the active foreground-service channel from MIN to LOW; the original importance stays MIN and sound stays off.
  • Exactly-once PushSink hand-off is durable-claim-based: process death between claim and sink can drop one notification but never duplicates it. Proposed spec clarification: idempotent notification delivery.

🤖 Generated with Claude Code

Commits 0