Fix volume up/down doing nothing by using adjustSuggestedStreamVolume instead of dispatchMediaKeyEvent which ignores volume keycodes.
Add reset-to-defaults button with confirmation dialog in the stem actions TopAppBar.
Rename 'None' to 'Default' (firmware handles the press) and add 'No Action' (claimed but do nothing) to correctly model per-press-type claim mask semantics.
Remove disableNone lock; add cross-side auto-set logic so selecting Default resets both sides and selecting an action promotes the other side from Default to No Action.
Combine ANC mode selector with listening mode cycle into a card with eye icon visibility toggles and radio button mode selection. Send AllowOff command when toggling OFF visibility.
Fix ANC resend logic fighting rapid mode changes by cancelling pending resend jobs on new commands. Reorder settings into Noise Control, Sound, Controls, General, and Connections sections.
Add 8 new AAP writable settings (microphone mode, ear detection toggle, listening mode cycle, allow off, stem config, sleep detection, in-case tone, device rename) and 4 new data reception features (stem press events, connected devices, audio source, EQ data).
Implement stem press action system with per-bud configurable Android actions (play/pause, next/prev track, volume), auto-sent stem config on connection, and dedicated config screen.
Combine ANC mode selector with listening mode cycle visibility into a unified noise control component. Eye icons control which modes appear in the dashboard card and settings. Pro gating with stars icon for upgrade-required features.
Add 64 unit tests covering all new decoders, model feature flags, malformed payloads, and rename byte-length validation.
Remove timestamps, connection state, and conversation awareness from overview cards. Move signal badge inline with model subtitle. Relocate removed info to device settings screen info card. Fix cached devices showing Gen 1 icons by adding per-pod icon properties to PodModel. Add profile name subtitle to device settings toolbar. Simplify preview coverage to full/minimal/cached variants.
AAP connections now run in appScope via AapLifecycleManager, independent of MonitorMode. Fixes AAP not connecting when monitor mode is MANUAL.
Also fixes reconnect cleanup on flow cancellation (try/finally).
Free users see only their highest-priority profiled device. Additional devices are hidden behind an upgrade card showing the count and a flavor-aware CTA (Upgrade/Donate).
Wire up the missing drag-to-reorder UI that the priority hint already advertises. Long-press a profile row to reorder. Extracts a reusable ReorderableState component into common/compose.
Backend (repo + ViewModel) was already in place; this adds the gesture handling, auto-scroll, visual feedback, and ID-based reorder validation.
Move per-device persist logic from DeviceMonitor into a pure PodDevice.toCachedState() extension in the cache package. Add ToCachedStateTest with coverage for creates, skips, dedup, and slot preservation. Delete unused PodSorter.
Eliminate DeviceStatePersister class by chaining persistence as a side effect in DeviceMonitor's flow. The flow is now shared via replayingShare(appScope) so persistence runs once per emission regardless of subscriber count, and works for BLE-only devices without MonitorService.
Add label property to PodDevice, populated from the profile in DeviceMonitor. Cards now use device.label instead of reaching through BLE metadata, so cached-only cards display the profile name instead of '?'.
Move DeviceStatePersister and AapKeyPersister from reaction to monitor package since they are always-on infrastructure, not user-togglable reactions.
Add periodic ticker to BlePodMonitor to force stale device eviction when BLE scanner produces no results, fixing cached card not appearing after disconnect.
Replace PodDeviceCache (raw BLE scan bytes) with DeviceStateCache that stores decoded combined device state (battery, charging, model) per profile.
Battery values persist across app restarts with per-slot timestamps. Cached-only cards appear for offline devices with muted visuals and a staleness indicator. Fallback chain: AAP -> BLE -> cached.
Add AirPodsPro2UsbcAapSessionTest with real captured bytes from a live
device session (model A3048, Pixel 8, 2026-04-02). Covers handshake,
device info, battery states, private keys, all Pro 2 USB-C settings
(including ADAPTIVE ANC, VolumeSwipe, ConversationalAwareness),
ear detection across 6 transitions, and unhandled messages.
Also document ChargingState observations across models and
EndCallMuteMic subtype variations in code comments.
Add @Stable to PodDevice to enable Compose referential equality checks, reducing unnecessary recompositions from the 3-second update ticker. Make icon properties non-null with built-in defaults and fix lazy list keys to use stable string identifiers.
Remove false positive hasEarDetection from Beats Studio Buds and Studio Buds+ (neither has in-ear detection). Remove false positive hasVolumeSwipe and hasVolumeSwipeLength from AirPods 4 ANC (volume swipe is exclusive to AirPods Pro). Add missing hasEarDetection to AirPods Max, AirPods Max USB-C, AirPods Gen 4, Beats Solo Pro, and Beats Studio 3 (all have head/ear detection per Apple docs).
Add AirPodsProAapSessionTest with real captured protocol data from AirPods Pro (A2084). Fix AIRPODS_PRO feature flags: remove hasVolumeSwipe/Length (hardware limitation), add hasEndCallMuteMic (firmware-supported). Fix primary pod decoder to accept byte[2]=0x00 format sent by Pro 1 on initial connect.
Only attempt AAP connections to classically-connected devices, eliminating futile retries for devices connected to other phones.
- Filter initialConnect() to devices in connectedAddresses
- Use mapLatest so stale retry loops cancel on state changes
- Parallelize per-profile connection attempts
- Add 5s connect timeout (best-effort) to cap retry cycles
- Add classic BT check to reconnectOnDisconnect()
- Keep MonitorService alive when AAP connections are active
When connected via AAP, the device reports its hardware model number. If the profile has a wrong or missing model, detect the mismatch and automatically correct it with a reconnect to apply correct feature flags (ANC modes, InitExt, etc.).
Adds modelNumbers field to PodModel enum with Apple hardware identifiers for all known devices, and a fromModelNumber() lookup function.
Initial connect had no retry — a single failed L2CAP attempt was silently swallowed. Reconnect-on-disconnect used separate longer backoff delays.
Both paths now share the same retry schedule (3s,3s,3s,5s,5s,10s,10s) giving 7 retries over ~39s. Initial connect checks if another path already reconnected before each retry.
initialConnect() only ran on profile changes, so if the L2CAP connection failed at service start (device not yet connected), it was never retried. Now also triggers on connectedDevices changes.
Also fix reconnect BLE address comparison: was comparing BLE RPA with bonded BR/EDR address (never matches), now uses profile address.
Decode cmd 0x0006 as EarDetection with per-pod placement (IN_EAR, NOT_IN_EAR, IN_CASE, DISCONNECTED). Map AAP primary/secondary to left/right using BLE primary pod bit.
Queue ANC mode changes when no pod is in ear, auto-send when a pod goes in ear. Show pending mode in UI with secondary color treatment.
Debounce device-initiated ANC mode cycling during ear transitions. Skip debounce for user-initiated commands and initial handshake. Optimistic UI update on send for instant feedback.
Move BLE key and AAP connection icons into the SignalBadge pill.
Add BleKeyState enum on PodDevice to expose IRK/ENC state cleanly.
Key icon: outlined for IRK-only, solid for IRK+ENC.
Bluetooth icon shown when AAP transport is active.
Solo Pro (0x0C20), Solo 4 (0x2520), Solo Buds (0x2620), Studio Buds (0x1120), Studio Buds+ (0x1620), Studio Pro (0x1720). Tests use handcrafted data pending real captures.
Move BLE-specific code (snapshots, devices, factories, protocol) under apple/ble/. Move AAP code under apple/aap/ with protocol/ subdirectory. PodModel stays in apple/ as the only shared type.
Standardize the formatting of pod models and their feature sets. This improves the readability of device-specific capabilities and ensures cleaner diffs for future hardware additions.
Full integration of the Apple Accessory Protocol (AAP) over L2CAP, enabling direct communication with AirPods for 1% battery granularity, ANC mode control, Conversation Awareness toggle, and private key exchange for BLE encrypted battery.
Complete type rename chain: PodDevice (interface) becomes BlePodSnapshot, MonitoredDevice (facade) claims PodDevice name, Model enum extracted to top-level PodModel. Delete redundant type alias files. Fix stale comments and log tags.
Replace direct PodDevice/PodMonitor usage with MonitoredDevice/DeviceMonitor across ViewModels, UI screens, notifications, widgets, reactions, and service. All interface cast-based property access replaced with flat MonitoredDevice properties. Make L2capSocketFactory injectable.
Introduce type aliases for the planned rename (PodDevice -> BlePodSnapshot, PodMonitor -> BlePodMonitor). New code uses the aliases to clarify BLE-specific types vs the unified MonitoredDevice facade.
Full codebase rename deferred to IDE refactoring pass (103 files, 470 occurrences).
MonitoredDevice unifies BLE and AAP data sources with dynamic resolution.
DeviceMonitor combines PodMonitor + AapConnectionManager into a single Flow.
To be renamed to PodDevice/PodMonitor when the old types are renamed to BlePodSnapshot/BlePodMonitor.