Google Play scores the obfuscation share of uploaded bundles and flags
listings below its threshold ("App optimisation is below our threshold",
fix by Feb 2027). Minify and shrink were already on; the shared
-dontobfuscate was the only thing keeping the score at 0%.
-dontobfuscate moves to a FOSS-only rule file; the Play rule file keeps
SourceFile/LineNumberTable for retracing and pins names only where they
reach users or logs: AapSetting/AapCommand subclasses (session logs),
ViewModel1 subclasses (log tag), Throwables (error dialog label). All
reflective targets (BuildConfig, ArtMirror, NeverCall, InvokeStub,
AncModeActionCallback) already carry @Keep or explicit keeps and are
identity-mapped in the gplayRelease mapping. The release workflow
archives the mapping next to the Play upload.
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.
Adds a review prompt card to the overview. On Google Play it uses the
in-app review flow, gated on the user having been Pro for a while, not
having dismissed it recently and not having reviewed yet. FOSS gets a
no-op implementation.
The card is the lowest priority item on the overview and stays hidden
while a permission, troubleshooter, background-monitoring-off or
no-profiles card is on screen.
Replaces capod's older billing core, upgrade UI and their tests with the
canonical sdmaid-se stack at the pinned revision.
Core (gplay): BillingManager/BillingConnection/BillingConnectionProvider on
billing 8.3 with the centralized connect loop, merging purchases-listener
overlay and the canonical ack pipeline; the dying ack collector, the
ackedTokens gate and the in-billing foreground loop are gone. Full canonical
exception set (internal/network/offer-unavailable added), OurSku with capod's
product ids, BillingCache with snapshot()/episode-guarded stampLastProState.
FOSS: UpgradeControlFoss becomes UpgradeRepoFoss and exposes the canonical API
surface over capod's RETAINED FossUpgrade/FossCache schema — existing supporter
records must keep decoding.
Diagnostics: UpgradeDiagnostics + gplay/foss implementations, read by
RecorderModule next to CurriculumVitae's Pro history as two independent,
isolated header reads.
UI: canonical upgrade screens for both flavors under common/upgrade/ui with
capod chrome (M3 AlertDialog keeping rotation-safety, capod Scaffold, capod
previews). Nav.Main.Upgrade gains `forced`. Entitlement refresh moves to a
per-resume, unthrottled MainActivity call.
Strings reuse capod's existing translated ids wherever equivalent; only
referenced-but-missing ones are authored.
mockk 1.12.4 -> 1.14.9: 1.12.4 cannot synthesize a sealed-class return value
while recording, which the ported restore tests need.
* feat(upgrade): Add Pro status view, grace UI and sub-to-IAP switch
* fix(upgrade): Pad restore purchase to a minimum visible duration
* ui(upgrade): Mention Play-website install fix in restore troubleshooting
* fix(upgrade): Stop re-acknowledging already-acked purchases
* ui(settings): Move upgrade status row into the Other category
Use AAP-connected mocks for the dark dashboard to showcase full connectivity badges (BLE + IRK + encrypted + AAP) and the ANC mode selector. Populate the add-profile preview with a demo name, model, and paired device. Add user-facing labels to the dashboard mock devices.
Refactor BluetoothDevice2 so name/address are primary constructor fields, letting previews construct one without a real Android BluetoothDevice. Bump the screenshot test JVM heap to 4g — the smoke batch (42 renders) was hitting the test executor's default ceiling.
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
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.
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.
Replace the legacy AppWidgetProvider/RemoteViews implementation with Glance AppWidget for reactive updates, proper centering, and a unified render state model.
- Add BatteryGlanceWidget with collectAsState for live device/profile/upgrade data
- Add WidgetRenderState sealed class and WidgetRenderStateMapper
- Add GlanceWidgetContent (Glance composables) and ComposeWidgetPreview (config preview)
- Fix centering by passing spacing via modifier param (no trailing padding on last item)
- Remove old XML widget layouts and RemoteViews rendering code
- Migrate ProjectConfig from static object to proper Gradle plugin
- Add version type support (beta/rc) to version.properties
- Clean up legacy fastlane changelogs
- Update release script to support version types
- Remove automatic fastlane changelog generation from release script
- Add BuildConfig field injection for version info
This commit enables the automatic generation of per-app language preferences by configuring the Android Gradle Plugin.
A `resources.properties` file is added to specify `en` as the `unqualifiedResLocale`, which serves as the default/fallback locale.
Additionally, `.kotlin` is added to `.gitignore` to exclude Kotlin build-related files.
- Add UnmatchedDevicesCard to show/hide devices without profiles
- Separate devices with profiles from unmatched devices in overview
- Add priority-based sorting (profile.priority with 0 = highest)
- Add compiler args for experimental unsigned types and annotation targets
- Add string resources for unmatched devices UI
- Refactor overview to handle both profiled and non-profiled devices
The UI now shows:
1. Devices with configured profiles first (sorted by priority)
2. Collapsible section for unmatched devices with toggle button
3. Session-persistent show/hide state for unmatched devices
- Merged all 76 locale strings.xml files from app-common to app module
- Removed app-common module dependency from app/build.gradle.kts
- Removed app-common from settings.gradle
- Fixed all BuildConfig and R class import statements
- Updated fully qualified R references from eu.darken.capod.common.R to R
- Deleted entire app-common directory and all associated files
- Added temporary fallback for missing GITSHA BuildConfig field
The project now has a simplified structure with all string resources
consolidated in the app module, eliminating the unnecessary app-common
module while maintaining full functionality.
Builds successfully for both FOSS and Google Play variants.
This commit refactors the `buildSrc` module by:
- Removing the `setupLibraryDefaults()` extension function from `ProjectConfig.kt` as its functionality is largely covered by standard Gradle plugin configurations.
- Inlining version numbers for Moshi and various testing libraries directly into `Dependencies.kt`, removing them from `Versions.kt`. This simplifies version management for these specific libraries.
- Removing unused version declarations from `Versions.kt`.
Additionally, this commit updates the following dependencies:
- Kotlin from 2.1.21 to 2.2.10
- Android Gradle Plugin from 8.11.0 to 8.12.2
- KSP from 2.1.21-2.0.1 to 2.2.10-2.0.2
- Dagger from 2.56.2 to 2.57.1
- AndroidX Navigation from 2.8.9 to 2.9.3
The `targetSdk` was also removed from the `app-common` module's `defaultConfig` as it's typically inherited or set at the app level.
This commit introduces an `EdgeToEdgeHelper` class to centralize and simplify the application of window insets for edge-to-edge display.
Key changes:
- Created `EdgeToEdgeHelper` to manage padding based on system bar insets.
- Migrated various Fragments (`OverviewFragment`, `OnboardingFragment`, `SettingsFragment`, `TroubleShooterFragment`) to use `EdgeToEdgeHelper` for consistent padding.
- Removed manual inset handling from `Activity2` and `Fragment2` as `EdgeToEdgeHelper` now manages this.
- Enabled edge-to-edge display in `MainActivity`.
- Minor updates to `BillingClientConnection` for product details fetching and `build.gradle.kts` for configuration.
This commit updates:
- Android Gradle Plugin to 8.11.0
- Compile/Target SDK to 36
- Google Play Billing Library to 8.0.0
The `BillingClient` is updated to use the new `PendingPurchasesParams` and `QueryPurchasesParams` APIs.
* More IAP improvements
* More refactoring
* More tolerance towards GPlay API issues.
* Bump billing dependency
* Migrate away from deprecated methods.
* 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