Skip to content

feat(service): CDM presence — restart MeshService when the radio reappears (Phase 2) - #6479

Draft
jamesarich wants to merge 1 commit into
feat/cdm-association-phase1from
feat/cdm-presence-phase2-3
Draft

feat(service): CDM presence — restart MeshService when the radio reappears (Phase 2)#6479
jamesarich wants to merge 1 commit into
feat/cdm-association-phase1from
feat/cdm-presence-phase2-3

Conversation

@jamesarich

@jamesarich jamesarich commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Why

Stacked on #6477 (Phase 1: associations). An association legalizes background starts, but nothing produces one when the radio comes back into range with the app backgrounded or dead. Companion Device Manager presence closes that gap: the system binds our CompanionDeviceService — reviving the process if necessary — when the associated radio's BLE presence changes, and we restart MeshService so it reconnects on its own. This is the "it just reconnects, like a smartwatch" behavior, and CDM is the only sanctioned mechanism for it.

🌟 What changed

  • MeshCompanionDeviceService: adapts all three callback generations (String 31–32, AssociationInfo 33–35, DevicePresenceEvent 36+ — API shapes verified against the SDK 37 jar) onto pure decisions in CompanionPresencePolicy. A device-appeared event for the selected radio starts MeshService with the new CompanionDevicePresent trigger; MeshServiceOrchestrator.start() then reconnects to the persisted address — no scan step, the transport builds its Kable Peripheral straight from the MAC. Events for other (older) associated radios are ignored, and appeared events never fight a live connection.
  • CompanionPresenceCoordinator keeps presence observation registered for exactly one radio — the selected BLE device while it holds an association — retiring registrations on selection change/disassociation, and re-asserting on every process start (registration does not reliably survive reboots). Eagerly started from MeshUtilApplication.onCreate per the repo's eager-init pattern.
  • Policy: CompanionDevicePresent flows through the existing appInForeground || hasCompanionAssociation gate (the trigger implies an association by construction); the caller's try/catch still lets the OS verify the claim.
  • Manifest: REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE (normal-level, API 31) + the service exported behind the platform-signature BIND_COMPANION_DEVICE_SERVICE permission. Older platforms never bind it.

Deliberately absent: device-disappeared events are unhandled. Presence-driven FGS teardown (Phase 3 of the design) waits until field data shows appeared-events are trustworthy across OEMs — a torn-down service that never restarts is strictly worse than today's idle one. If/when soak supports it, teardown returns as a fresh, small change.

🧪 Testing performed

  • CompanionPresencePolicyTest: appeared matrix — selected-only, case-insensitive MAC, never restarts a live connection.
  • CompanionPresenceCoordinatorTest: reconciliation via a recording repository fake — observe on start, retire on selection change/deselection, pick up late associations (revision bump), ignore non-BLE selections, inert pre-31.
  • CompanionAssociationRepositoryTest: presence-observation forks under Robolectric (the String-overload shadow only exists from sdk 33, hence sdk 34 for that case), feature/SDK guards, association-id ↔ MAC resolution.
  • ForegroundStartPolicyTest: CompanionDevicePresent through the trigger × SDK matrix (allowed backgrounded with association, refused without) plus the existing while-in-use invariant.
  • Full baseline green: spotlessApply spotlessCheck detekt assembleDebug test allTests (incl. KoinVerify).

📲 On-device gate (before merge)

With an associated radio: background (or force-stop) the app, power-cycle the radio → MeshService should start and reconnect on its own. Verify on google + fdroid, ideally on an API 31–32 device and a 33+ device (different callback generations).

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 774ea1a5-e399-4732-9fec-9e2b3ac78607

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the enhancement New feature or request label Jul 27, 2026
@jamesarich
jamesarich force-pushed the feat/cdm-presence-phase2-3 branch from 0c4bb19 to 0c29a24 Compare July 27, 2026 23:30
@jamesarich jamesarich changed the title feat(service): CDM presence — restart on radio appearance, teardown on absence (Phases 2+3) feat(service): CDM presence — restart MeshService when the radio reappears (Phase 2) Jul 27, 2026
…pears (Phase 2)

Phase 2 of CDM adoption: MeshCompanionDeviceService, a system-bound
CompanionDeviceService, receives presence events for the associated radio
(String callbacks on 31-32, AssociationInfo on 33-35, DevicePresenceEvent
on 36+). A device-appeared event for the selected radio starts MeshService
with the new CompanionDevicePresent trigger; the orchestrator reconnects
to the persisted address on its own — no scan involved, the transport
builds its Peripheral straight from the MAC. CompanionPresenceCoordinator
keeps observation registered for exactly the selected, associated radio
and re-asserts it on every process start.

Device-disappeared events are deliberately unhandled: presence-driven
teardown (Phase 3) waits until field data shows appeared-events are
trustworthy across OEMs — a torn-down service that never restarts is
strictly worse than today's idle one.

- Manifest: REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE + exported service
  behind BIND_COMPANION_DEVICE_SERVICE.
- CompanionAssociationRepository: start/stopObservingPresence with the
  31-35 String vs 36+ ObservingDevicePresenceRequest fork, plus
  macForAssociationId for the event-based callback.
- Tests: presence policy matrix, coordinator reconciliation (recording
  fake), repository forks under Robolectric (String-overload shadow only
  exists from sdk 33), new trigger through the ForegroundStartPolicy
  matrix, StateFlow added to KoinVerify known types.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jamesarich
jamesarich force-pushed the feat/cdm-association-phase1 branch from d9efb64 to b5cae14 Compare August 1, 2026 15:24
@jamesarich
jamesarich force-pushed the feat/cdm-presence-phase2-3 branch from 0c29a24 to 40d5302 Compare August 1, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant