Commit Graph
528 Commits
Author SHA1 Message Date
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 94e28fb0b5 refactor: Move Proximity* classes into protocol.ble package 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
darken aebf43c355 style: Reformat PodModel enum for better maintainability
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.
2026-03-31 19:17:09 +02:00
darken c1cd99f701 feat: Add AAP L2CAP protocol support for reading and writing AirPods settings
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.
2026-03-31 19:17:09 +02:00
darken 3fa01bb510 refactor: Rename PodDevice to BlePodSnapshot, MonitoredDevice to PodDevice, extract PodModel
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.
2026-03-31 19:17:09 +02:00
darken e84f8762b4 Extract Model to PodModel.kt 2026-03-31 19:17:09 +02:00
darken ac21261d4d refactor: Migrate all consumers from PodDevice to MonitoredDevice
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.
2026-03-31 19:17:09 +02:00
darken c19ee88ae6 refactor: Add BlePodSnapshot and BlePodMonitor type aliases
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).
2026-03-31 19:17:09 +02:00
darken f751cdb37e feat: Add MonitoredDevice facade and DeviceMonitor merge point
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.
2026-03-31 19:17:09 +02:00
darken 28cd3b1340 test: Add unit tests for AAP protocol layer and Model.Features 2026-03-31 19:17:09 +02:00
darken 3e2852e622 feat: Add AAP protocol layer, L2CAP socket factory, and Model.Features
Add L2CAP infrastructure (HiddenApiBypass, ArtMirror, L2capSocketFactory) in common/bluetooth/l2cap/ for BR/EDR L2CAP socket creation on Android 17+.

Add AAP protocol abstraction (AapSetting, AapCommand, AapMessage, AapFramer, AapDeviceProfile, AapConnection, AapConnectionManager) with sealed domain classes containing zero wire bytes.

Add Model.Features capability flags (hasDualPods, hasCase, hasEarDetection, hasAncControl) to all 25 device model enum entries.

Add debug-only L2capPocActivity for testing AAP connections.
2026-03-31 19:17:09 +02:00
darken 217d9be177 fix: Show key icon for single-pod devices when encryption keys are set 2026-03-23 13:00:48 +00:00
darken 53447f3fb9 fix: Stop retrying auto-connect after SecurityException
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.
2026-03-23 11:24:23 +00:00
darken d913e72c5c General: Update app translations from Crowdin (retranslated strings) 2026-03-22 21:45:33 +00:00
darken 20e22c0910 General: Update app translations from Crowdin 2026-03-22 21:45:33 +00:00
darken f25718ab2c fix: Catch Hilt DI failure in MonitorService.onCreate()
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.
2026-03-20 19:17:56 +00:00
darken 6e9b275936 fix: Replace nullable Float composable params to avoid ART native crash
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.
2026-03-20 19:12:22 +00:00
darken 992cb5e695 fix: Remove premature startForegroundService call from App.onCreate()
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.
2026-03-20 17:49:15 +00:00
darken 039d20d7aa fix(billing): Use billing-ktx suspend APIs instead of manual callback bridging 2026-03-20 17:49:05 +00:00
darken 52433460be fix: Bump Compose BOM to fix ExposedDropdownMenuBox crash on detach 2026-03-20 17:48:55 +00:00
darken 5e21941765 General: Update app translations from Crowdin 2026-03-12 12:12:40 +00:00
darken 2db01b410b fix(billing): Prevent crash when billing service disconnects 2026-03-12 11:59:07 +00:00
darken 193a1030fb feat: Start BLE scanning with only core permissions granted
Allow the app to scan for AirPods when only BLE scan permissions are granted, without waiting for all optional permissions (notifications, overlay, etc.).

Add isScanBlocking flag to Permission enum. Gate monitor service and pod scanning on scan permissions only. Show scan-blocking permission cards with error color and sorted first. Wrap BLUETOOTH_CONNECT-dependent calls in try-catch for graceful degradation. Fix POST_NOTIFICATIONS minApiLevel from S (31) to TIRAMISU (33).
2026-03-10 16:37:43 +00:00
darken 12e35631d6 fix: Scope ViewModels to navigation entries via NavDisplay decorator 2026-03-10 15:11:10 +00:00
darken 67474a30da fix(debug): Rename recorder screen button from Close to Keep
The keep button's onClick handler was already named onKeep but the label said Close. Renamed to Keep for clarity alongside the Delete/Share buttons. Added dedicated string resource with translations for all 75 locales.
2026-03-10 07:50:36 +00:00
darken 44cd0eec2a test: Add missing AutoConnectCondition round-trip and Model encode tests 2026-03-09 20:29:19 +00:00
darken a79f6a0535 refactor: Replace Moshi with kotlinx.serialization
Remove Moshi dependency entirely, completing the migration to kotlinx.serialization. All JSON serialization now uses kotlinx with explicit @SerialName annotations for wire format stability.

- Migrate PodDeviceCache from Moshi to kotlinx Json injection

- Add MapIntByteArrayBase64Serializer for BleScanResult cache compat

- Strip @JsonClass/@Json annotations from all dual-annotated classes

- Delete Moshi adapters, ProGuard rules, and build config

- Convert compat tests to pure kotlinx round-trip tests
2026-03-09 20:29:19 +00:00
darken 8675d73a98 fix(debug): Persist recording session across app restarts
The debug recording trigger file now stores the session directory path and start timestamp. On app restart, the recorder resumes into the same session directory and log file instead of creating a new one, preserving the original start time so the short-recording guard doesn't reset.
2026-03-09 17:34:29 +00:00
darken 3423786f06 chore: Remove redundant package name from manifest 2026-03-09 17:41:08 +01:00
darken 00083707c6 fix(debug): Harden debug session recording and sharing 2026-03-09 16:39:56 +00:00
darken 8e3b235c81 ui: Improve PodGauge layout alignment in DualPodsCard 2026-03-09 16:46:25 +01:00
darken 879ec1b69c General: Update app translations from Crowdin 2026-03-09 14:33:56 +00:00
darken d7d9d53ff3 refactor: Replace MaterialAlertDialogBuilder with Compose AlertDialog
Replace all 11 instances of MaterialAlertDialogBuilder across 5 files with Compose AlertDialog. Use sealed dialog state per screen to prevent dialog stacking. Add shared ConfirmationDialog composable. Delete dead ErrorDialog.kt.
2026-03-09 12:46:23 +00:00
darken cba239f411 General: Update app translations from Crowdin 2026-03-09 12:40:01 +00:00
darken de89c51457 fix(debug): Use human-readable UTC timestamp in debug session directory names
Parse creation time from the embedded filename timestamp instead of relying on filesystem attributes, which are non-deterministic and caused flaky test failures in CI.
2026-03-09 11:57:42 +00:00
darken 7f8daddfe0 feat(debug): Show confirmation dialog after sharing debug logs
After sharing a debug log or sending a contact form email, show a dialog on return asking if the send was successful. Confirming deletes the log session and closes/navigates back.
2026-03-09 11:17:50 +00:00
darken a76e43ac04 chore: Use List in Zipper and add global ExperimentalMaterial3Api opt-in
Replace Array<String> with List<String> in Zipper.zip().

Add global -opt-in flag for ExperimentalMaterial3Api in build.gradle.kts

and remove per-file @OptIn annotations from 12 Compose screens.
2026-03-09 04:53:01 +00:00
darken fd0dc49396 test(debug): Add session ID round-trip and edge case tests 2026-03-09 04:53:01 +00:00
darken a5f5feacae fix(debug): Fix deadlock risk, missing IO dispatcher, and thread safety in session manager
Replace sessions.first() inside fsMutex.withLock with synchronous volatile

read to avoid potential deadlock. Wrap file deletions in IO dispatcher.

Log partial delete failures. Make pendingAutoZips thread-safe via

Collections.synchronizedSet. Add test for ext/cache same-basename IDs.
2026-03-09 04:53:01 +00:00
darken 557ee9b3ed fix(test): Fix flaky sort test that relied on filesystem creation time 2026-03-09 04:53:01 +00:00
darken 58bd0f91f5 refactor(debug): Use Instant for createdAt, extract scanSessions helpers, add pendingAutoZips guard 2026-03-09 04:53:01 +00:00
darken b2f8586dc9 fix(recorder): Use red delete button and rename Done to Close 2026-03-09 04:53:01 +00:00
darken 62797e8ea2 test(support): Add ContactFormViewModel.State unit tests 2026-03-09 04:53:01 +00:00
darken c614e7bd4f test(debug): Add unit tests for Zipper, DebugLogZipper, RecorderModule.State, and scanSessions edge cases 2026-03-09 04:53:01 +00:00
darken 62e39ce3d2 refactor(support): Add zipFile/compressedSize to Ready, harden session manager
Add zipFile and compressedSize fields to DebugSession.Ready, populated during scan. Simplify RecorderActivityVM by reading compressed size directly from session model. Apply code review fixes: fsMutex in zipSessionAsync, CancellationException handling, ZipOutputStream use{}, atomic file moves, dedup deriveSessionId, LifecycleResumeEffect for session refresh.
2026-03-09 04:53:01 +00:00
darken 7df74e3b44 refactor(support): Unify debug log session model and add session manager bottom sheet
Introduce DebugSession sealed interface as single source of truth for session lifecycle (Recording, Compressing, Ready, Failed). Auto-compress after recording stops. Replace scattered clear/delete actions with a session manager bottom sheet on the Support screen.
2026-03-09 04:53:01 +00:00
darken 06e5954ef5 fix(upgrade): Reduce FOSS sponsor unlock timer from 10s to 5s 2026-03-07 14:26:35 +00:00