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.
Add explicit permissions and persist-credentials: false to all workflows.
Without an explicit permissions block, GITHUB_TOKEN inherits the repo default (write-all). These CI workflows only need contents: read. The release workflow already declares contents: write at job level where needed.
persist-credentials: false prevents the token from lingering in .git/config for subsequent steps, reducing attack surface if a third-party action is compromised.
BluetoothHeadset.connect() requires MODIFY_PHONE_STATE on modern Android, which is a system-only permission. Detect the SecurityException and stop further attempts instead of retrying every second.
AGP 9.0.1 no longer generates a separate mapping.txt for bundle tasks when -dontobfuscate is active. The mapping param was causing supply to fail on a non-existent file.
Wrap super.onCreate() (Hilt injection) in try-catch to prevent
DI failures from crashing the process. The service is already
foreground at this point, so a graceful stopSelf() satisfies the
FGS timeout requirement without killing the app.
ComposerImpl.changed() boxes Float? and calls Float.equals() at the ART native level, triggering a known crash. Non-null Float params use the primitive overload with no boxing.
Introduces BATTERY_UNKNOWN sentinel, toBatteryFloat(), and toBatteryOrNull() extensions to eliminate Float? from all composable signatures and WidgetRenderState data classes.
Calling startMonitor() in App.onCreate() started the foreground service timeout
clock before the BroadcastReceiver even ran, consuming timeout budget
with post-init work and receiver processing. The service is already
started by BluetoothEventReceiver, BootCompletedReceiver, and
OverviewViewModel, making this call redundant.