Commit Graph
104 Commits
Author SHA1 Message Date
darken 6d5c5d29cc fix(test): Use runTest2 with autoCancel for DeviceSettingsViewModelTest 2026-04-14 11:28:41 +02:00
darken d4a355c356 fix(reaction): Fix autoplay/autopause for AAP-based ear detection
PlayPause coerced null per-side ear values to false, making all ear states invisible when resolvedPrimaryPod was unknown. Fall back to AAP aggregate state (isBeingWorn/isEitherPodInEar) when per-side mapping is unavailable, keeping PodDevice.isLeftInEar/isRightInEar truthful for UI consumers. Add isEitherPodInEar to PlayPauseMonitorKey for proper dedup. Add diagnostic logging at the distinctUntilChangedBy boundary.
2026-04-14 11:28:41 +02:00
darken 34c23751b3 refactor(ui): Split PodCardComponents into per-component files
Move each composable from PodCardComponents.kt into its own file under a new cards/components/ subpackage. Add previews to each file. Also add getBatteryIcon() for Compose Material Icon battery levels used in the popup.
2026-04-14 11:28:41 +02:00
darken 04c23ecf63 fix(ble): Prefer case-context pod when merging split BLE broadcasts
When both pods broadcast independently (one in case, one on desk), the pod inside the case carries authoritative case state via hasCaseContext bits. Previously, whichever address was processed last would overwrite the other, causing case state to flip-flop between OPEN and NOT_IN_CASE every scan cycle.

Two-layer fix: BlePodMonitor.processWithCache() now prefers the pod with case context when two scan results map to the same identity in one batch. ApplePodsFactory.getLatestCaseLidState() no longer treats NOT_IN_CASE as authoritative when recent history contains a broadcast with case context.
2026-04-14 11:28:41 +02:00
darken 1f7bc7cfd8 fix(popup): decode lid state from case context 2026-04-14 11:28:41 +02:00
darken 46fcf5a4cb fix(popup): use fresh broadcast timing for connection popup 2026-04-14 11:28:41 +02:00
darken a4e7820e6f Add delayed retry for permission recheck when returning from settings 2026-04-14 11:28:41 +02:00
darken 71ba531025 Break cache feedback loops in monitor and reactions 2026-04-14 11:28:41 +02:00
darken 6825abaa41 Guard BLE-only autoplay and clarify one-pod mode 2026-04-14 11:28:41 +02:00
darken a4e1a3ba2e fix(overview): Hide stale cached age on live cards 2026-04-14 11:28:41 +02:00
darken 1cd8ae5ace Refactor time access behind TimeSource 2026-04-14 11:28:41 +02:00
darken c1601c105c Fix auto-resume after CAP pause 2026-04-14 11:28:41 +02:00
darken a6f292f737 Render device settings before BT profile state arrives 2026-04-14 11:28:41 +02:00
darken a37b9b0213 fix(settings): Show passive info when device is connected but AAP unavailable 2026-04-14 11:28:41 +02:00
darken 067eb2e9e0 feat(reactions): Move reaction settings to per-device profiles
Migrate reaction toggles (auto-play, auto-pause, auto-connect, popups) from global ReactionSettings singleton to per-profile fields on AppleDeviceProfile. Each paired device can now have independent reaction behavior.

Extract ReactionConfig snapshot to decouple PodDevice from AppleDeviceProfile — reaction consumers read device.reactions instead of device.profile. Remove auto-connect from upgrade benefits (now free). Add LegacyReactionSettingsReader for one-shot DataStore migration.
2026-04-14 11:28:41 +02:00
darken 70d9c047dc fix: Prevent duplicate device cards for stranger AirPods with same model 2026-04-13 14:09:33 +02:00
RomaZinkevich aeaf92d7e6 support for airpods max 2 added 2026-04-10 07:58:46 +02:00
darken f2bf39bda1 fix(monitor): Use AAP message time for Last Seen while connected 2026-04-08 19:21:19 +02:00
darken ed37e142ad fix(ui): Fix signal bars and restore individual connection icons 2026-04-08 18:07:06 +02:00
darken 41245d928c fix(aap): Make AirPods rename actually apply on device
Switch the AAP rename packet to the opcode 0x1A format (04 00 04 00 1A 00 01 [size] 00 [name]) matching the LibrePods documentation and Linux implementation. The previous 0x1E variant (from the LibrePods Android code) was silently ignored by AirPods Pro 2 USB-C firmware — no 0x001D echo, no persistence across reconnect.

Verified on AirPods Pro 2 USB-C (firmware 81.2675...): the device now echoes the new name back via the next 0x001D INFORMATION message, and the name persists after disconnect/reconnect.

Also hardens the rename UX: gate the edit icon on isAapReady (was isAapConnected, which allowed sending during HANDSHAKING), apply an optimistic deviceInfo update with a scoped rollback on send failure, surface send errors via a new Event.SendFailed + snackbar, and restrict dialog input to ASCII with inline error feedback. Unifies send() / sendProGated() through a single sendInternal() helper so error plumbing benefits every command, not just rename.

Note: this only updates the AirPods firmware's self-reported name. Android's system Bluetooth settings read from the bond database and are not affected — renaming there still requires the Android system Bluetooth UI.
2026-04-08 15:36:18 +02:00
darken ccd95a0981 feat(aap): Show 'Not connected' card when AAP is unavailable 2026-04-08 11:49:07 +02:00
darken 2d38db5cba feat(aap): Log decoded 0x1D segments for engraving discovery
Diagnostic-only NUL-delimited UTF-8 segmentation of the 0x1D INFORMATION packet, logged at INFO so it lands in debug recordings via the existing FileLogger pipeline. The production decode path stays untouched — this only adds visibility, no behavior change. Refs #173.
2026-04-08 11:21:15 +02:00
darken 175f8e36b5 fix(aap): Address device settings review feedback
- Show connected device MACs in correct byte order (remove stale reversal)

- Rename 'Volume Swipe Length' setting to 'Volume Swipe Wait Time'

- Hide 'Charging Sounds' toggle: real case tones go over ATT and the actual effect of this AAP setting is unknown. Decode kept internally so AAP freshness signal still refreshes.

- Fix duplicate commands on End Call/Mute Mic radio buttons: use Modifier.selectable with Role.RadioButton, short-circuit when already selected, and wrap options in selectableGroup for TalkBack.
2026-04-07 18:53:54 +02:00
darken a102b03261 fix: Show signal strength as bar indicator instead of percent 2026-04-07 17:56:29 +02:00
darken f67336d4fe fix(aap): Synthesize AAP-only devices for cold-start window 2026-04-07 15:24:03 +02:00
darken a511c57b7d fix(aap): Keep device settings visible when BLE scan goes stale 2026-04-07 13:28:37 +02:00
darken e356344ba6 feat(aap): Add new device settings, stem actions, and noise control UI
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.
2026-04-04 11:41:26 +02:00
darken fea1092a95 refactor: Move AAP auto-connect from MonitorService to DeviceMonitor scope
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).
2026-04-03 13:27:52 +02:00
darken c609b03148 feat(overview): Gate dashboard device count for free users
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).
2026-04-02 18:30:40 +02:00
darken 1f5f406b33 feat(profiles): Add drag-to-reorder for profile list
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.
2026-04-02 18:17:00 +02:00
darken 7c37854076 refactor: Extract toCachedState extension and add tests
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.
2026-04-02 16:46:56 +02:00
darken 30d7efeec2 Moving packages around 2026-04-02 16:40:56 +02:00
darken 942e03af36 refactor: Merge device state persistence into DeviceMonitor
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.
2026-04-02 16:36:12 +02:00
darken 24a84a49f1 refactor: Remove meta from PodDevice public API and delete unused PodMonitorExtensions
All external consumers now use top-level PodDevice properties. BlePodMonitor extensions were dead code — all callers use DeviceMonitor equivalents.
2026-04-02 16:01:00 +02:00
darken 360067b07e refactor: Move persisters to monitor package and fix stale BLE device eviction
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.
2026-04-02 15:14:47 +02:00
darken e91243e577 feat: Add unified device state cache for persistent battery display
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.
2026-04-02 13:56:09 +02:00
darken 19d61ca5cb feat: Add AAP session tests for AirPods Pro 2 USB-C
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.
2026-04-02 13:45:52 +02:00
darken fe4ac306cf fix: Correct feature flags for multiple PodModel entries
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).
2026-04-01 06:57:45 +02:00
darken 618229b96b feat: Add AAP session tests for AirPods Pro Gen 1
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.
2026-03-31 22:36:23 +02:00
darken 1931e60b07 feat: Speed up AAP L2CAP reconnection
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
2026-03-31 22:06:23 +02:00
darken 7bdc9ca212 feat: Auto-correct device model from AAP device info
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.
2026-03-31 20:52:57 +02:00
darken 04ea3d0cd8 feat: Add retry logic for AAP L2CAP connection failures
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.
2026-03-31 19:17:09 +02:00
darken fef6c97d2d feat: Detect primary pod via AAP cmd 0x0008 for faster ear detection 2026-03-31 19:17:09 +02:00
darken dc2e318374 fix: Trigger AAP connection when classic Bluetooth connects
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.
2026-03-31 19:17:09 +02:00
darken 71638c13ab feat: Decode AAP ear detection and stabilize ANC mode changes
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.
2026-03-31 19:17:09 +02:00
darken 70e5b99e1d feat: Add connection type indicators to SignalBadge
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.
2026-03-31 19:17:09 +02:00
darken f65c6e4a29 feat: Add BLE device support for 6 new Beats models
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.
2026-03-31 19:17:09 +02:00
darken c860ecc430 feat: Integrate AAP connection freshness into signal quality indicator 2026-03-31 19:17:09 +02:00
darken a8593eecd3 refactor: Reorganize pods package into ble/ and aap/ domains
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.
2026-03-31 19:17:09 +02:00
darken 4808e04146 fix: Update Dagger component reference after BaseBlePodsTest rename 2026-03-31 19:17:09 +02:00