Commit Graph
517 Commits
Author SHA1 Message Date
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
darken 450a187b65 test(reaction): Add ViewModel and reaction logic unit tests
Extract pure decision functions from AutoConnect and PopUpReaction for testability, following the existing PlayPause pattern. Add FakeDataStoreValue test helper.

Fix reversed Duration.between args in PopUpReaction connection monitor that caused the false-positive age filter to never trigger.
2026-03-07 14:19:17 +00:00
darken da6a6f6ff2 fix(widget): Broaden work-runtime keep rule and add widget error handling
R8 full mode strips no-arg constructors from work-runtime classes accessed via

Class.newInstance() reflection (WorkDatabase_Impl, OverwritingInputMerger, etc).

Replace narrow WorkDatabase_Impl keep rule with broad androidx.work.** rule.

Also add @Keep to Hilt WidgetEntryPoint and wrap provideGlance setup in

try-catch so the widget shows an error message instead of spinning forever.
2026-03-06 18:47:46 +00:00
darken d37c8aa3a5 fix(proguard): Add keep rule for WorkDatabase_Impl stripped by R8 full mode 2026-03-06 18:47:46 +00:00
darken 5cd97805b4 test(upgrade): Add unit tests for Google Play billing and upgrade logic 2026-03-05 13:41:34 +00:00
darken e35bce4d19 feat(upgrade): Add yearly subscription with trial to Google Play billing
Rewrite SKU type system from data class to interface hierarchy supporting both IAP and subscriptions. Add subscription query, offer matching, and billing flow launch for yearly plans with optional free trial.

FOSS: Add sponsor-gate with 10-second timer and snackbar nudge.
2026-03-04 22:38:23 +00:00
darken 1a196689db fix(test): Move FossUpgrade tests to testFoss source set for gplay compat
FossUpgrade is foss-flavor-only, so tests referencing it must live in testFoss/ to avoid compilation failures in testGplayDebugUnitTest.
2026-03-04 15:23:34 +00:00
darken 8ada7b071d test(settings): Add missing DataStore serialization and migration compat tests
Cover round-trip tests for ThemeStyle, ThemeColor, MonitorMode, ScannerMode, AutoConnectCondition, FossUpgrade, and InstantEpochMillisSerializer. Add Moshi decode tests for ThemeStyle, ThemeColor, AutoConnectCondition, FossUpgrade, and FossUpgrade.Reason annotation parity.
2026-03-04 15:23:34 +00:00
darken a92e364ca6 refactor(settings): Migrate preferences from SharedPreferences to DataStore
Replace FlowPreference<T> wrapping SharedPreferences with DataStoreValue<T> wrapping AndroidX DataStore. Includes SharedPreferencesMigration for preserving existing user data, kotlinx-serialization for complex types (replacing Moshi for preferences), and comprehensive unit tests for the new infrastructure.
2026-03-04 15:23:34 +00:00
darken 6326f4a5b9 fix(monitor): Gate BLE scanning on permission state and stop retrying on SecurityException
PodMonitor's retryWhen always returned true, causing infinite 3-second retry loops when BLUETOOTH_SCAN permission was missing (e.g. on emulators). Now the scan flow checks missingPermissions before starting, and SecurityException aborts retries since it requires user action.
2026-03-03 20:52:32 +00:00
darken abb189f43c feat(upgrade): Replace upgrade dialog with full-screen upgrade screen
Add dedicated Compose upgrade screens for both FOSS and gplay flavors, presenting pro benefits before the purchase/sponsor action. Migrate all upgrade dialog callers to navigate to the new screen instead.
2026-03-03 20:35:32 +00:00
darken e80bf1db6c feat(support): Add wiki link to support screen 2026-03-03 17:22:04 +00:00