Skip to content

Device: Fix AirPods features not working until app restart after granting permission#531

Merged
d4rken merged 2 commits into
mainfrom
fix-aap-lifecycle-permission-gate
Apr 17, 2026
Merged

Device: Fix AirPods features not working until app restart after granting permission#531
d4rken merged 2 commits into
mainfrom
fix-aap-lifecycle-permission-gate

Conversation

@d4rken

@d4rken d4rken commented Apr 17, 2026

Copy link
Copy Markdown
Member

What changed

Fixed a bug where AirPods feature support (ANC toggle, stem controls, learned settings, firmware-level state) would stay broken until the app was force-stopped and relaunched, if the user granted the Bluetooth permission after first launch.

Typical repro: install the app, launch it, create a profile, then grant BLUETOOTH_CONNECT when prompted. BLE scanning started working but the Apple protocol layer never came up — the main screen and widget kept showing battery but no firmware-level features. Killing the app and reopening it fixed it.

Technical Context

  • Two independent flow-lifecycle bugs were in play. The first kept AAP from ever starting; the second kept it from ever receiving classic Bluetooth connection state even after the first was fixed.
  • AAP lifecycle (AapLifecycleManager): start() was called once from DeviceMonitor.init and subscribed a merged flow in appScope. On first launch without BLUETOOTH_CONNECT, the inner bondedDevices().first() threw SecurityException; the existing .catch {} absorbed it and the merged flow completed — the launched Job was done forever, with no resubscription when permission was granted. Fix: wrap the merge in permissionTool.missingPermissions.flatMapLatest gated on Permission.BLUETOOTH_CONNECT, plus an inner retryWhen with capped backoff for mid-run transient failures. Mirrors the established pattern in MonitorService.doMonitor().
  • connectedDevices (BluetoothManager2): the upstream retryWhen gave up after 3 attempts and .catch { emit(emptyList()) } terminated the flow. The backing stateIn StateFlow then served its cached empty list to any new subscriber indefinitely — so even after the AAP lifecycle fix re-subscribed, connectedDevices remained frozen at [] and no AAP connect was ever attempted. Fix: in retryWhen, treat SecurityException as non-terminal and keep retrying with a 3s backoff; the next attempt succeeds as soon as the user grants the permission. The 3-attempt limit still applies to other causes.
  • The permission-gate check is a no-op on pre-S (where BLUETOOTH_CONNECT doesn't exist in Permission.entries) because PermissionTool.missingPermissions already filters by Permission.isRequired(context) which enforces the API-level bound.
  • Debug logs attached to the support email show the two failure modes sequentially: (1) initialConnectSecurityException at BluetoothManager2.kt:295aapActive.onCompletion (first bug), and after the first fix, (2) AAP lifecycle starts but every combine emission sees connectedDevices=[] because the BluetoothManager2 StateFlow stayed frozen from its earlier retry exhaustion (second bug).

@d4rken d4rken merged commit fd20ef0 into main Apr 17, 2026
10 checks passed
@d4rken d4rken deleted the fix-aap-lifecycle-permission-gate branch April 17, 2026 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant