feat(onboarding): opt-in to unrestricted battery/background usage #50

Merged
JMR-dev merged 1 commits from feat/49-onboarding-battery-optimization into main 2026-07-01 17:42:52 +00:00
JMR-dev commented 2026-07-01 17:32:41 +00:00 (Migrated from github.com)

Implements #49 (part of the onboarding epic #9): a guided, F-Droid-safe way for users to allow LibreMail Unrestricted background/battery usage, so IMAP IDLE push (IdleService) and periodic WorkManager sync aren't throttled or killed by Doze.

Approach

Deep-links to the system app-details screen (where Battery → Unrestricted lives) rather than firing the restricted REQUEST_IGNORE_BATTERY_OPTIMIZATIONS dialog. No new manifest permission — safe on Google Play (#17) and F-Droid (#16), consistent with the project's F-Droid-first posture.

What's included

  • BatteryPromptDecision — pure, unit-tested gate: supported && !alreadyUnrestricted && !alreadyHandled.
  • BatteryOptimizationManager — reads PowerManager.isIgnoringBatteryOptimizations and builds the app-details intent.
  • Onboarding step (BatteryOptimizationScreen) — shown after the first account is added; Take me there deep-links, Not now is non-blocking; re-checks status on resume and flips to a success state. Skipped entirely when already unrestricted or already handled.
  • Advanced Settings row — shows current status (Unrestricted vs. Optimized) and re-opens the system screen; the recovery path for users who skipped onboarding or are on aggressive-OEM devices.
  • battery_prompt_handled flag persisted in the settings DataStore (kept out of AppSettings — it's onboarding state, not a user preference).

Testing

  • Unit: BatteryPromptDecisionTest (4) + OnboardingViewModelTest (6).
  • E2E (BatteryOptimizationStepTest, Espresso-Intents): step offered → Not now → inbox (+ flag set); Take me there fires ACTION_APPLICATION_DETAILS_SETTINGS for this package; skip-when-handled. All 3 pass locally on API 37; the two touched existing E2E classes still pass.
  • Full local gate green: assembleDebug, testDebugUnitTest, compileDebugAndroidTestKotlin, lintDebug, detekt, ktlintCheck.

Notes

  • At minSdk 29 the "supported" gate is always true (Doze allowlist is API 23+); kept explicit for correctness if the floor ever drops.
  • The API can't distinguish Restricted from Optimized (isIgnoringBatteryOptimizations is false for both), so the "not unrestricted" copy covers both — noted inline.
  • Adds espresso-intents (reusing the existing espresso version) to androidTest deps.

Closes #49

🤖 Generated with Claude Code

Implements #49 (part of the onboarding epic #9): a guided, F-Droid-safe way for users to allow LibreMail **Unrestricted** background/battery usage, so IMAP IDLE push (`IdleService`) and periodic WorkManager sync aren't throttled or killed by Doze. ## Approach Deep-links to the system **app-details** screen (where Battery → Unrestricted lives) rather than firing the restricted `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` dialog. No new manifest permission — safe on Google Play (#17) and F-Droid (#16), consistent with the project's F-Droid-first posture. ## What's included - **`BatteryPromptDecision`** — pure, unit-tested gate: `supported && !alreadyUnrestricted && !alreadyHandled`. - **`BatteryOptimizationManager`** — reads `PowerManager.isIgnoringBatteryOptimizations` and builds the app-details intent. - **Onboarding step** (`BatteryOptimizationScreen`) — shown after the first account is added; **Take me there** deep-links, **Not now** is non-blocking; re-checks status on resume and flips to a success state. Skipped entirely when already unrestricted or already handled. - **Advanced Settings row** — shows current status (Unrestricted vs. Optimized) and re-opens the system screen; the recovery path for users who skipped onboarding or are on aggressive-OEM devices. - **`battery_prompt_handled`** flag persisted in the settings DataStore (kept out of `AppSettings` — it's onboarding state, not a user preference). ## Testing - **Unit:** `BatteryPromptDecisionTest` (4) + `OnboardingViewModelTest` (6). - **E2E** (`BatteryOptimizationStepTest`, Espresso-Intents): step offered → **Not now** → inbox (+ flag set); **Take me there** fires `ACTION_APPLICATION_DETAILS_SETTINGS` for this package; skip-when-handled. All 3 pass locally on API 37; the two touched existing E2E classes still pass. - Full local gate green: `assembleDebug`, `testDebugUnitTest`, `compileDebugAndroidTestKotlin`, `lintDebug`, `detekt`, `ktlintCheck`. ## Notes - At minSdk 29 the "supported" gate is always true (Doze allowlist is API 23+); kept explicit for correctness if the floor ever drops. - The API can't distinguish **Restricted** from **Optimized** (`isIgnoringBatteryOptimizations` is false for both), so the "not unrestricted" copy covers both — noted inline. - Adds `espresso-intents` (reusing the existing espresso version) to androidTest deps. Closes #49 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.