Device: Polish device settings and fix ANC mode visibility#519
Merged
Conversation
…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.
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
?:) bound tighter than!=, causing the filter to pass every mode unconditionally when a cycle mask was presentcurrent=OFFas 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 allowedReview checklist