Skip to content

Device: Polish device settings and fix ANC mode visibility#519

Merged
d4rken merged 13 commits into
mainfrom
DeviceSettingsPolish
Apr 16, 2026
Merged

Device: Polish device settings and fix ANC mode visibility#519
d4rken merged 13 commits into
mainfrom
DeviceSettingsPolish

Conversation

@d4rken

@d4rken d4rken commented Apr 16, 2026

Copy link
Copy Markdown
Member

What changed

Polished the device settings screen: device details now open in a bottom sheet, the device name is tappable for inline editing, and the ANC mode selector no longer shows the "Off" option when the device doesn't support it. Previously, tapping "Off" on devices where it wasn't enabled caused a rejection sound and a confusing UI revert.

Also improved reliability of AirPods communication — settings sent while no pod is in the ear are now queued and delivered when a pod goes in, instead of being silently dropped.

Technical Context

  • Root cause of the OFF visibility bug was a Kotlin operator precedence issue: the Elvis operator (?:) bound tighter than !=, causing the filter to pass every mode unconditionally when a cycle mask was present
  • The "Off" mode inference (inferring that OFF is allowed when the device reports it as the current mode) was too aggressive — pods in the charging case report current=OFF as idle state, not as a user preference. The inference now requires a pod to be in-ear and the OFF state to be stable for 1.5 seconds before concluding OFF is genuinely allowed
  • Session engine extraction from the connection class was needed to make command queueing, ear-detection gating, and verification logic independently testable

Review checklist

  • ANC mode selector hides OFF by default on Pro 2 USB-C (no ListeningModeCycle/AllowOffOption reported by device)
  • Enabling OFF via the Listening Mode Cycle dialog makes the OFF button appear
  • Pods in case on connect do not cause OFF to appear transiently
  • Settings sent while pods are out of ear are queued and delivered on pod insertion
  • Device info bottom sheet shows serial numbers, firmware, and model details

d4rken added 12 commits April 15, 2026 09:43
…fix switch bypass

Add SettingsInfoBox title support and experimental feature warning for Sleep Detection and Personalized Volume toggles with issue tracker action.

Pro-gate tone volume, microphone mode. Un-gate sleep detection, conversation awareness. Fix SettingsSwitchItem allowing direct switch toggle to bypass requiresUpgrade.
Based on Apple's official documentation, enable features for models

that support them per iOS but were previously excluded in CAPod:

- Gen 1/2: microphone mode (auto/left/right)

- Gen 3: press speed, press hold duration, tone volume, microphone mode

- Gen 4 non-ANC: press speed, press hold duration, tone volume

- Max original/USB-C: listening mode cycle, allow off option
Swap the logical mapping for `EndCallMuteMic` settings to align with hardware states observed on AirPods Pro devices. Updated session tests for Pro 1, 2, and 3 to reflect the corrected protocol behavior.
- Add UnknownSetting catch-all for 9 unconfirmed H2+ setting IDs (0x29, 0x2C, 0x2F, 0x30, 0x33, 0x37, 0x38, 0x3B, 0x3E)

- Downgrade known non-settings commands (0x0000, 0x0002, 0x000C, 0x0017, 0x002B, 0x004E, 0x0055, 0x0057) from INFO to VERBOSE

- Add earbud serials and build number to DeviceInfo (parsed from 0x001D segments 8-10, shown in DeviceInfoCard, persisted in cache)

- Fix UTF-8 device name parsing (curly quotes etc. no longer break segment indices)

- Add [was: ...] to setting change logs for easier protocol diff analysis

- Label DeviceInfoDump segments 5-12 (firmwareVersionDup, protocolVersion, updaterAppId, earbudSerials, buildNumber, encryptedBlob, timestamp)
…de UI

Device info card: add model label with Apple model number, rename label to

Bluetooth Device Label, show cursive font on name mismatch with system BT

name, combine first/last seen on single row, add profile prefix to subtitle.

ANC mode: extract shared AncModeUi helpers for labels and icons, update

overview cards and device settings to use shared components.
Move serial, firmware, build, manufacturer, and per-pod serials from

the info card into a ModalBottomSheet triggered by an info icon.

Firmware+build and left+right pod serials render as paired rows.
…n engine extraction

Queue all setting commands when no pod is in ear, flush when a pod goes in. Enable the adaptive noise slider when ADAPTIVE mode is pending. Show an info box when settings changes are pending.

Extract AapSessionEngine (state, send path, message processing, inference) and AapSettingsCoordinator (queue, optimistic updates, verification) from AapConnection, reducing it from 773 to 173 lines.
…lush ordering

Track lastAncSentAt separately so non-ANC commands during flush don't break the ANC echo debounce window. Prioritize ANC for post-flush verification.

Move HANDSHAKING→READY transition to top of processMessage so decoded battery, stem press, and device info messages also trigger it.

Sort flush: AllowOffOption before AncMode before others, preventing device rejection when enabling OFF mode.
During case transitions, AirPods send 800+ cmd 0x0017 HID frames in ~20s. Previously each logged identically at VERBOSE with raw hex (~160KB noise). Now a HidTracker classifies frames (service directory, descriptor bulk, terminator) and batches consecutive bulk frames by (phase, fill), emitting 3-4 summary lines instead of 822.
Fix Elvis operator precedence bug in visibleAncModes() where OFF passed the filter unconditionally. Move filtering logic to PodDevice.visibleAncModes extension, unifying DualPodsCard, SinglePodsCard, and DeviceSettingsScreen. Gate AllowOffOption inference on pod-in-ear + 1.5s stability to prevent false positives from in-case OFF reports.
@d4rken d4rken added enhancement Add a new feature of improve an existing feature device support labels Apr 16, 2026
Split AapSessionEngine into dedicated controllers (AapAncController, AapOutboundController), a typed inbound decoder (AapInboundInterpreter), a HID frame batcher (HidTracker), and a device-info diagnostics helper (AapDeviceInfoDiagnostics). Each controller returns typed decisions carrying state, timer actions, and logs instead of mutating engine state via callbacks. AapSettingsCoordinator is now stateless — pending queue and verification state live in engine runtime state.

All coroutine timer Jobs live in the engine's timerJobs map keyed by EngineTimerKey, with cancelAllTimers() on reset to prevent forgotten cancellations. Engine event dispatch is split: suspend path for user-initiated sends (errors propagate to caller), non-suspend for sync events (timer fires, inbound updates).
@d4rken d4rken merged commit f141ce3 into main Apr 16, 2026
10 checks passed
@d4rken d4rken deleted the DeviceSettingsPolish branch April 16, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

device support enhancement Add a new feature of improve an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant