The connection timestamp cache was only maintained while the connected-devices
flow had a subscriber. If the process died while a device stayed connected and
that device then disconnected and reconnected with nothing running, the
restarted collection found the old entry still keyed by a currently-connected
address, kept it through the prune, and reported the original connect time —
so the popup age check rejected an arbitrarily old connection.
The ACL broadcasts arrive at a manifest-registered receiver that wakes the
process regardless of any flow subscription, so the stamp is taken there
instead: connect stamps (keeping an existing one, which is the earlier and
therefore truer time), disconnect drops the entry. The flow keeps its own
stamp-on-first-sight and prune as a backstop for the force-stopped state and
missed broadcasts.
ACL_DISCONNECTED was already in the receiver's expected actions but was never
registered in the manifest. It does not start the monitor: a disconnect is not
a reason to start monitoring, and the start triggers here are deliberately
conservative.
Play auto-refunds (and revokes) purchases not acknowledged within 3 days.
The in-process ack machinery covers every case where the process lives
long enough; what it cannot cover is a process death around the Play
sheet (aggressive OEM task killers) followed by the user not reopening
the app before the deadline.
Add a gplay-only WorkManager safety net:
- PurchaseAckWorker: self-completing sweep via a new bounded
BillingManager.ensureAllAcknowledged() that refreshes and acknowledges
in the same coroutine (the reactive ack collector is async, so a worker
cannot prove its acks happened through it). Retries with exponential
backoff until the purchase's refund deadline, then gives up visibly.
- PurchaseAckScheduler: two unique work identities. A launch watch
(REPLACE, armed and awaited before launchBillingFlow with a 30min delay
so it cannot complete while the user is still in the sheet) and a
discovered-purchase rescue (KEEP, 1min delay, armed directly from an
ack pass that finds unacknowledged purchases, pre-attempt). Separate
identities so a new purchase flow can never displace a pending rescue.
Both triggers are fail-open: a broken WorkManager never blocks a
purchase or an ack. WorkManager resolves via Provider at first arm so
eager Application-time construction of the billing stack cannot
trigger WorkManager's on-demand initialization prematurely.
- Nothing cancels the work from the foreground path: an ack pass can see
zero unacked purchases while the sheet is still open, so the worker
completes itself after its own reconciliation instead.
The ack pass now runs under a mutex (the worker sweep and the reactive
collector would otherwise race the token bookkeeping) and reports
per-outcome counts for the sweep result mapping.
This is a port of d4rken-org/sdmaid-se#2685; the ported sources are
byte-identical to the donor apart from the package rename.
CAPod had no explicit WorkManager wiring at all (work-runtime only
arrived transitively through Glance), so this also adds it:
- addWorkerManager() pinning androidx.work 2.7.1, the version already
resolved via Glance, plus androidx.hilt:hilt-work and its KSP
compiler. work-runtime-ktx is required at 2.7.1: CoroutineWorker,
Operation.await, OneTimeWorkRequestBuilder and workDataOf all still
live in the ktx artifact at that version. androidx.hilt moves 1.0.0 ->
1.2.0 (by conflict resolution) because 1.0.0's hilt-compiler ships no
KSP SymbolProcessorProvider, so @HiltWorker would generate nothing.
- WorkManagerModule providing the singleton WorkManager.
- App implements Configuration.Provider with the injected
HiltWorkerFactory. WorkManager 2.7.1 still declares that interface as
getWorkManagerConfiguration(), not the later property form.
- The manifest removes androidx.work's startup initializer so the
on-demand configuration is the one that takes effect.
FOSS stays untouched behaviour-wise: all new billing types live in
src/gplay, workers need no manifest entry, and the worker factory
resolves the worker only in gplay variants.
New home-screen widget that toggles AirPods ANC modes (Off / ANC / Transparency / Adaptive) directly, with six adaptive layouts that pick based on widget size (QUAD_CORNERS, ROW_ICONS, COLUMN_ICONS, GRID_2X2, ROW, COLUMN). Consolidated battery+ANC configuration into a single ViewModel that detects widget type from AppWidgetManager. ACTIVE state uses Material3 secondaryContainer/onSecondaryContainer for guaranteed contrast. Includes live config preview, preview subtitle, stale-selection guard, device-label toggle, and aligned icons with the app's AncModeSelector.
Add structured contact form with category selection (Question/Feature/Bug), description with word count validation, expected behavior field for bugs, and debug log picker with inline recording.
Enhance RecorderActivity with full-screen hero layout, file list, Share/Keep/Discard actions. Migrate debug logs from flat files to session directories in external files dir. Add DebugLogZipper, EmailTool attachment support, log folder size display, and clear stored logs.
- Fix BleScanResultReceiver package name in manifest (.bluetooth → .common.bluetooth)
- Fix HandlerThread leak in BluetoothManager2 when registerReceiver() throws
- Cache battery values in MonitorNotifications to match hardening pattern
- Remove duplicate fragment-ktx dependency and align fragment-testing version
- Remove deprecated lifecycle-extensions dependency
```
Caused by java.lang.IllegalStateException: WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider.
at androidx.work.impl.WorkManagerImpl.getInstance (WorkManagerImpl.java)
at androidx.work.impl.foreground.SystemForegroundDispatcher.<init> (SystemForegroundDispatcher.java)
at androidx.work.impl.foreground.SystemForegroundService.initializeDispatcher (SystemForegroundService.java)
at androidx.work.impl.foreground.SystemForegroundService.onCreate (SystemForegroundService.java)
at android.app.ActivityThread.handleCreateService (ActivityThread.java:4726)
```
* wip
* Setup bugsnag only in gplay builds.
* Bugsnag does not support uploading mapping files or build information from library modules. This should be done from the application module which produces your APK instead.
* Only apply Bugsnag plugin for the right flavors.
* Run unit tests in debug, otherwise we run issues requiring api keys in the env