Skip to content

fix(install): aggressive-oem download reliability (E3 R4)#545

Merged
rainxchzed merged 7 commits intomainfrom
fix/e3-r4-oppo-downloads
May 8, 2026
Merged

fix(install): aggressive-oem download reliability (E3 R4)#545
rainxchzed merged 7 commits intomainfrom
fix/e3-r4-oppo-downloads

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 8, 2026

Foundation Sprint Task 4 / E3 R4. Survey #41: "Fix download issues on Oppo devices (that's one of the reasons I switched to Obtainium)."

Problem

ColorOS / MIUI / Funtouch / MagicOS aggressively kill background WorkManager tasks even when scheduled through GitHub Store's own settings, and intercept system installer dialogs. Result: scheduled update checks silently fail, auto-update via Shizuku doesn't fire, and downloads stall mid-flight.

Three layers of defence shipped in this PR:

1. Aggressive-OEM detector

New AggressiveOemDetector interface (commonMain) + Android impl that flags devices via Build.BRAND / Build.MANUFACTURER substring match against the known-aggressive list (Oppo, OnePlus, Realme, Xiaomi, Redmi, Poco, vivo, iQOO, Huawei, Honor, Meizu). Desktop returns false / always-ignored — non-issue off-Android. Wired through Koin single in both platform modules.

2. Tweaks → Updates → "Allow background updates" card

Surfaces only when (aggressive OEM) && !(already exempted) && !(user dismissed). One tap routes to Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS for the targeted whitelist screen, falls back to the generic optimization-settings screen if the targeted intent is rejected. "Maybe later" persists a BATTERY_OPT_PROMPT_DISMISSED_KEY watermark so the user is never re-nagged. Card auto-disappears on Tweaks re-entry once the exemption is granted (state re-evaluated in onStart).

3. WorkManager priority changes

  • UpdateCheckWorker immediate one-shot now uses setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) — short, network-only work, fits comfortably in the 10-minute expedited cap.
  • AutoUpdateWorker deliberately stays NON-expedited per reviewer feedback — it downloads multiple APKs and installs sequentially, which can exceed 10 minutes and would get the worker terminated mid-install. The worker already promotes itself to a foreground service via setForeground, which gives it the headroom it needs without the time cap.

4. Manifest

  • REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission added with tools:ignore="BatteryLife". GitHub Store ships outside Play Store (sideloaded APK from GitHub Releases), so the targeted intent is the right tool — Play policy compliance is moot.
  • FOREGROUND_SERVICE_DATA_SYNC already present (E12) — verified.
  • Scoped storage already correct (getExternalFilesDir(DIRECTORY_DOWNLOADS)).

Strings

  • 4 new keys (battery_optimization_card_title, _description, _open, _dismiss) shipped across all 13 locales.

What's-new

  • New IMPROVED bullet in 17.json × 13 locales.

Test plan

Cannot verify on real device — no Oppo / ColorOS hardware available.

Static checks done:

  • :composeApp:compileDebugKotlinAndroid
  • :composeApp:compileKotlinJvm
  • Local CodeRabbit review: 0 findings (after applying all valid feedback)

For the operator's device-side smoke pass:

  • Card appears on Tweaks → Updates on a real Oppo / OnePlus / Realme / Xiaomi / vivo / Honor device that hasn't been whitelisted.
  • "Open settings" lands on the correct system screen.
  • "Maybe later" hides the card permanently.
  • Whitelist via system Settings → return to Tweaks → card disappears.
  • Background update check actually fires on schedule on whitelisted ColorOS device.
  • AutoUpdate via Shizuku completes a full download + install on whitelisted ColorOS.

Reviewer notes (CodeRabbit pass — applied + skipped)

  • Applied: zh-rCN full-width comma; fr formal voice ("Votre" / "Excluez"); fr/es/it/pl/hi expedited→native equivalent in whatsnew; AutoUpdateWorker setExpedited reverted (10min cap unsafe for download+install); evaluateBatteryOptimizationCard wrapped in withTimeoutOrNull with explicit logger.error on failure; duplicate evaluate-on-onStart removed.
  • Skipped: manifest REQUEST_IGNORE_BATTERY_OPTIMIZATIONS Play-policy concern (rationale above — sideloaded APK, not a Play app); pre-existing api(libs.ktor.client.core) in feature/tweaks/presentation/build.gradle.kts (not in scope of this PR); RELEASE_NOTES_1.8.1.md grammar nit (untracked working file, not in diff).

Summary by CodeRabbit

  • New Features

    • Battery-optimization card added for Oppo/OnePlus/Realme/Xiaomi/vivo/Honor with a one-tap Settings shortcut to allow background updates; card can be dismissed.
  • Improvements

    • More reliable background update checks and installs; update tasks use expedited priority when available.
    • Whats‑new release notes updated across languages.
  • Chores

    • Dismissal choice is now remembered and the card auto-re-evaluates on return from Settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f4ac201d-8321-4f36-8042-8bcd8cac0dd5

📥 Commits

Reviewing files that changed from the base of the PR and between c69d0e8 and e940946.

📒 Files selected for processing (1)
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksRoot.kt

Walkthrough

Introduce aggressive OEM detection and battery optimization prompting for reliable background updates. Add platform-specific detector implementations, persist user dismissal state, wire expedited work requests with fallback, render a conditional UI card, and include localized strings and release notes across 13 languages.

Changes

Battery Optimization Detection and UI Prompt

Layer / File(s) Summary
Interface Contracts
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/AggressiveOemDetector.kt, core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt
New AggressiveOemDetector interface defines detection of aggressive OEM status and battery optimization handling. TweaksRepository adds getBatteryOptimizationPromptDismissed() and setBatteryOptimizationPromptDismissed() methods.
Platform Implementations
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AndroidAggressiveOemDetector.kt, core/data/src/jvmMain/kotlin/zed/rainxch/core/data/services/DesktopAggressiveOemDetector.kt
AndroidAggressiveOemDetector checks device brand/manufacturer against a vendor list and uses PowerManager.isIgnoringBatteryOptimizations() to detect status; attempts to launch battery optimization intent with fallback. DesktopAggressiveOemDetector is a no-op stub for JVM.
Dependency Injection & Persistence
composeApp/src/androidMain/AndroidManifest.xml, core/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.kt, core/data/src/jvmMain/kotlin/zed/rainxch/core/data/di/PlatformModule.jvm.kt, core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt, core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/UpdateScheduler.kt
Android manifest declares REQUEST_IGNORE_BATTERY_OPTIMIZATIONS. Platform modules register AggressiveOemDetector bindings. TweaksRepositoryImpl persists battery optimization dismissal flag. UpdateScheduler configures expedited requests with OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST fallback and documents auto-update nond-expedited behavior.
Presentation State & Actions
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksState.kt, feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksAction.kt
TweaksState adds showBatteryOptimizationCard boolean. TweaksAction adds three new variants: OnOpenBatteryOptimizationSettings, OnDismissBatteryOptimizationCard, OnReevaluateBatteryOptimizationCard.
ViewModel Logic
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt
Add aggressiveOemDetector dependency. On startup, call evaluateBatteryOptimizationCard() to determine visibility based on OEM aggressiveness, battery optimization status, and dismissal flag (with timeout-safe reads). Handle new actions to open settings, dismiss card, or re-evaluate visibility.
UI Component & Dependency
feature/tweaks/presentation/build.gradle.kts, feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt
Add libs.touchlab.kermit dependency. Render BatteryOptimizationCard composable in Updates section when state.showBatteryOptimizationCard is true, wiring buttons to dismiss and open-settings actions.
Localized Strings
core/presentation/src/commonMain/composeResources/values*/strings*.xml (13 languages)
Add battery optimization card title, description, and action labels (battery_optimization_card_title, battery_optimization_card_description, battery_optimization_card_open, battery_optimization_card_dismiss) across English, Arabic, Bengali, Spanish, French, Hindi, Italian, Japanese, Korean, Polish, Russian, Turkish, and Simplified Chinese.
Release Notes
core/presentation/src/commonMain/composeResources/files/whatsnew/*/17.json (13 languages)
Add IMPROVED section to v1.9.0 release notes across 13 languages, describing more reliable background updates on Oppo, OnePlus, Realme, Xiaomi, vivo, and Honor via new battery-optimization shortcut and expedited update-worker priority.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as BatteryOptimizationCard
  participant VM as TweaksViewModel
  participant Detector as AggressiveOemDetector
  participant Repo as TweaksRepository
  participant System as Android System (PowerManager/Intents)
  User->>UI: Tap "Open settings"
  UI->>VM: OnOpenBatteryOptimizationSettings action
  VM->>Detector: openBatteryOptimizationSettings()
  Detector->>System: ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS / fallback
  System-->>Detector: Activity started / failure
  Detector-->>VM: success/failure
  VM->>Repo: setBatteryOptimizationPromptDismissed(true) (on dismiss)
  VM->>VM: evaluateBatteryOptimizationCard() (on resume or re-evaluate)
  VM->>Detector: isAggressiveOem(), isBatteryOptimizationIgnored()
  VM->>Repo: getBatteryOptimizationPromptDismissed()
  VM-->>UI: update state.showBatteryOptimizationCard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit nudged a sleepy phone awake,

"Whitelist once and let the background take—
One tap for peace, one card to show,
Updates hop in, steady, on the go."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: adding aggressive-OEM detection and battery-optimization reliability features for background updates on specific Android devices.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/e3-r4-oppo-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/UpdateScheduler.kt (1)

49-59: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove setInitialDelay or setExpedited—combining them throws IllegalArgumentException.

WorkManager forbids delaying expedited work requests. Line 52's .setInitialDelay(1, TimeUnit.MINUTES) and line 58's .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) on the same OneTimeWorkRequest will cause an IllegalArgumentException to be thrown immediately when enqueueUniqueWork() executes (line 63–66), crashing the app.

Choose one:

  • Keep the 1-minute grace period: remove .setExpedited() and its comment.
  • Prioritize expedited execution: remove .setInitialDelay().
Option A: Remove expedited (preserve the 1-minute delay)
         .setInitialDelay(1, TimeUnit.MINUTES)
-        // Aggressive-OEM ROMs (Oppo / OnePlus / Realme / Xiaomi)
-        // throttle generic background work hard. Expedited tier
-        // gets more headroom; fall back to non-expedited when
-        // the system has no expedited budget left so the work
-        // still runs eventually rather than failing outright.
-        .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
         .build()
Option B: Remove the delay (preserve expedited)
         .setConstraints(constraints)
-        .setInitialDelay(1, TimeUnit.MINUTES)
         // Aggressive-OEM ROMs (Oppo / OnePlus / Realme / Xiaomi)
         // throttle generic background work hard. Expedited tier
         // gets more headroom; fall back to non-expedited when
         // the system has no expedited budget left so the work
         // still runs eventually rather than failing outright.
         .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
         .build()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/UpdateScheduler.kt`
around lines 49 - 59, The OneTimeWorkRequest built as immediateRequest calls
both .setInitialDelay(1, TimeUnit.MINUTES) and .setExpedited(...), which is
illegal and throws IllegalArgumentException when enqueueUniqueWork(...) runs;
remove the conflicting call to avoid the crash—specifically, delete the
.setInitialDelay(1, TimeUnit.MINUTES) line (and its related comment about OEM
throttling) from the OneTimeWorkRequestBuilder<UpdateCheckWorker>() in
UpdateScheduler.kt so the request remains expedited, keeping the
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) and leaving
enqueueUniqueWork(...) unchanged.
🧹 Nitpick comments (4)
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/AggressiveOemDetector.kt (1)

8-8: 💤 Low value

Document the return-value semantics of openBatteryOptimizationSettings().

The Boolean return value is unspecified — callers and implementors have to guess whether true means "intent was fired", "user is now exempted", or something else. Given the JVM stub returns false and the Android impl presumably returns true on a successful intent launch, a short KDoc would prevent future misinterpretation.

📝 Suggested KDoc
-    fun openBatteryOptimizationSettings(): Boolean
+    /**
+     * Attempts to open the system battery-optimization exemption settings for this app.
+     * `@return` `true` if the settings intent was successfully launched, `false` otherwise
+     *         (e.g. on Desktop where the concept doesn't apply).
+     */
+    fun openBatteryOptimizationSettings(): Boolean
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/AggressiveOemDetector.kt`
at line 8, The function openBatteryOptimizationSettings() has unspecified
Boolean semantics; add a concise KDoc above its declaration in
AggressiveOemDetector.kt that defines what the Boolean represents (e.g., true =
intent was successfully launched, false = intent not launched/unsupported), and
update/align the JVM stub and Android implementation behaviors to match that
documented contract so callers and implementors are unambiguous.
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt (1)

116-122: 💤 Low value

KDoc slightly overstates when the flag flips to true.

The comment says the flag flips when the user "either granted the exemption or explicitly dismissed the prompt", but based on the PR description the auto-hide on exemption is driven by re-checking isBatteryOptimizationIgnored() in onStart, not by writing this flag. Only the "Maybe later" tap should actually write true here. The inaccuracy could mislead future implementors into calling setBatteryOptimizationPromptDismissed(true) on the "granted" path unnecessarily.

📝 Suggested KDoc correction
-     * One-shot watermark for the battery-optimization prompt on
-     * aggressive-OEM ROMs (Oppo / OnePlus / Realme / Xiaomi / vivo /
-     * Honor). `false` until the user has either granted the exemption
-     * or explicitly dismissed the prompt; flips to `true` afterwards
-     * and is never re-shown.
+     * One-shot watermark for the battery-optimization prompt on
+     * aggressive-OEM ROMs (Oppo / OnePlus / Realme / Xiaomi / vivo /
+     * Honor). `false` until the user explicitly dismisses the prompt
+     * ("Maybe later"); flips to `true` afterwards and is never re-shown.
+     * When the user grants the exemption instead, the card auto-hides via
+     * [AggressiveOemDetector.isBatteryOptimizationIgnored] without setting
+     * this flag.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt`
around lines 116 - 122, Update the KDoc for the battery-optimization prompt flag
to be precise: state that setBatteryOptimizationPromptDismissed(true) is written
only when the user explicitly dismisses the prompt (e.g., taps "Maybe later"),
and that granting the exemption is handled separately by re-checking
isBatteryOptimizationIgnored() (called from onStart) rather than by setting this
flag; reference setBatteryOptimizationPromptDismissed,
isBatteryOptimizationIgnored, and onStart in the KDoc to guide future
implementors.
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt (1)

443-462: ⚡ Quick win

runCatching around suspending code swallows CancellationException

Wrapping withTimeoutOrNull (and the DataStore edit call in OnDismissBatteryOptimizationCard at lines 934–941) inside runCatching catches all Throwables, including CancellationException. When viewModelScope is cancelled during ViewModel teardown, this causes:

  1. The cancellation to be logged as an error — misleading noise in crash/log pipelines.
  2. Execution to continue past the cancellation point, performing a _state.update on a dying ViewModel.

This is a known Kotlin coroutines anti-pattern. The fix is to re-throw CancellationException inside onFailure:

♻️ Proposed fix for evaluateBatteryOptimizationCard
 runCatching {
     withTimeoutOrNull(BATTERY_OPT_PREF_READ_TIMEOUT_MS) {
         tweaksRepository.getBatteryOptimizationPromptDismissed().firstOrNull()
     }
 }.onFailure { error ->
+    if (error is CancellationException) throw error
     logger.error(
         "TweaksViewModel: failed to read battery-opt dismissed flag",
         error,
     )
 }.getOrNull() ?: false

Apply the same change to the runCatching block at lines 934–941 in OnDismissBatteryOptimizationCard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`
around lines 443 - 462, The runCatching in evaluateBatteryOptimizationCard is
catching CancellationException and swallowing coroutine cancellation; update the
onFailure handler inside evaluateBatteryOptimizationCard's runCatching so that
if the caught Throwable is a CancellationException you re-throw it (i.e., if (t
is CancellationException) throw t) and only log non-cancellation errors, and
apply the identical change to the runCatching wrapper used in
OnDismissBatteryOptimizationCard (the DataStore edit block) so cancellation is
propagated instead of logged and execution halted.
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt (1)

296-306: ⚡ Quick win

Consider wrapping the card in AnimatedVisibility for a smoother transition.

Without it, the card snaps in/out instantly, which is visually jarring in a mid-list settings layout — especially after "Maybe later" dismissal. AnimatedVisibility with a fadeIn/fadeOut (or expandVertically/shrinkVertically) keeps the experience consistent with the rest of Material 3 Motion.

✨ Proposed change
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.shrinkVertically

-        if (state.showBatteryOptimizationCard) {
-            BatteryOptimizationCard(
-                onOpenSettings = {
-                    onAction(TweaksAction.OnOpenBatteryOptimizationSettings)
-                },
-                onDismiss = {
-                    onAction(TweaksAction.OnDismissBatteryOptimizationCard)
-                },
-            )
-            Spacer(Modifier.height(12.dp))
-        }
+        AnimatedVisibility(
+            visible = state.showBatteryOptimizationCard,
+            enter = expandVertically() + fadeIn(),
+            exit = shrinkVertically() + fadeOut(),
+        ) {
+            Column {
+                BatteryOptimizationCard(
+                    onOpenSettings = {
+                        onAction(TweaksAction.OnOpenBatteryOptimizationSettings)
+                    },
+                    onDismiss = {
+                        onAction(TweaksAction.OnDismissBatteryOptimizationCard)
+                    },
+                )
+                Spacer(Modifier.height(12.dp))
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt`
around lines 296 - 306, Wrap the BatteryOptimizationCard (and its following
Spacer) in an AnimatedVisibility whose visible condition is
state.showBatteryOptimizationCard to animate the card in/out instead of
snapping; use Material motion helpers like fadeIn/fadeOut or
expandVertically/shrinkVertically for enter/exit transitions, preserving the
existing callbacks (onOpenSettings ->
TweaksAction.OnOpenBatteryOptimizationSettings and onDismiss ->
TweaksAction.OnDismissBatteryOptimizationCard) and keep the Spacer inside the
AnimatedVisibility so spacing animates with the card.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`:
- Around line 946-948: The lifecycle resume path doesn't dispatch
TweaksAction.OnReevaluateBatteryOptimizationCard, so the battery card isn't
reevaluated when returning from external settings; modify the DisposableEffect
in TweaksRoot.kt where Lifecycle.Event.ON_RESUME currently dispatches
TweaksAction.OnRefreshCacheSize to also dispatch
TweaksAction.OnReevaluateBatteryOptimizationCard (so the ViewModel's
evaluateBatteryOptimizationCard() runs on resume), ensuring both actions are
sent together in the same resume handler.

---

Outside diff comments:
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/UpdateScheduler.kt`:
- Around line 49-59: The OneTimeWorkRequest built as immediateRequest calls both
.setInitialDelay(1, TimeUnit.MINUTES) and .setExpedited(...), which is illegal
and throws IllegalArgumentException when enqueueUniqueWork(...) runs; remove the
conflicting call to avoid the crash—specifically, delete the .setInitialDelay(1,
TimeUnit.MINUTES) line (and its related comment about OEM throttling) from the
OneTimeWorkRequestBuilder<UpdateCheckWorker>() in UpdateScheduler.kt so the
request remains expedited, keeping the
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) and leaving
enqueueUniqueWork(...) unchanged.

---

Nitpick comments:
In
`@core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt`:
- Around line 116-122: Update the KDoc for the battery-optimization prompt flag
to be precise: state that setBatteryOptimizationPromptDismissed(true) is written
only when the user explicitly dismisses the prompt (e.g., taps "Maybe later"),
and that granting the exemption is handled separately by re-checking
isBatteryOptimizationIgnored() (called from onStart) rather than by setting this
flag; reference setBatteryOptimizationPromptDismissed,
isBatteryOptimizationIgnored, and onStart in the KDoc to guide future
implementors.

In
`@core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/AggressiveOemDetector.kt`:
- Line 8: The function openBatteryOptimizationSettings() has unspecified Boolean
semantics; add a concise KDoc above its declaration in AggressiveOemDetector.kt
that defines what the Boolean represents (e.g., true = intent was successfully
launched, false = intent not launched/unsupported), and update/align the JVM
stub and Android implementation behaviors to match that documented contract so
callers and implementors are unambiguous.

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt`:
- Around line 296-306: Wrap the BatteryOptimizationCard (and its following
Spacer) in an AnimatedVisibility whose visible condition is
state.showBatteryOptimizationCard to animate the card in/out instead of
snapping; use Material motion helpers like fadeIn/fadeOut or
expandVertically/shrinkVertically for enter/exit transitions, preserving the
existing callbacks (onOpenSettings ->
TweaksAction.OnOpenBatteryOptimizationSettings and onDismiss ->
TweaksAction.OnDismissBatteryOptimizationCard) and keep the Spacer inside the
AnimatedVisibility so spacing animates with the card.

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`:
- Around line 443-462: The runCatching in evaluateBatteryOptimizationCard is
catching CancellationException and swallowing coroutine cancellation; update the
onFailure handler inside evaluateBatteryOptimizationCard's runCatching so that
if the caught Throwable is a CancellationException you re-throw it (i.e., if (t
is CancellationException) throw t) and only log non-cancellation errors, and
apply the identical change to the runCatching wrapper used in
OnDismissBatteryOptimizationCard (the DataStore edit block) so cancellation is
propagated instead of logged and execution halted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 12faa370-3987-44e9-9044-126c3519b30e

📥 Commits

Reviewing files that changed from the base of the PR and between cc8c639 and c69d0e8.

📒 Files selected for processing (40)
  • composeApp/src/androidMain/AndroidManifest.xml
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AndroidAggressiveOemDetector.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/UpdateScheduler.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt
  • core/data/src/jvmMain/kotlin/zed/rainxch/core/data/di/PlatformModule.jvm.kt
  • core/data/src/jvmMain/kotlin/zed/rainxch/core/data/services/DesktopAggressiveOemDetector.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/AggressiveOemDetector.kt
  • core/presentation/src/commonMain/composeResources/files/whatsnew/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/17.json
  • core/presentation/src/commonMain/composeResources/values-ar/strings-ar.xml
  • core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml
  • core/presentation/src/commonMain/composeResources/values-es/strings-es.xml
  • core/presentation/src/commonMain/composeResources/values-fr/strings-fr.xml
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
  • core/presentation/src/commonMain/composeResources/values-ko/strings-ko.xml
  • core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-tr/strings-tr.xml
  • core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/tweaks/presentation/build.gradle.kts
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksAction.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksState.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt

@rainxchzed rainxchzed merged commit 513047d into main May 8, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/e3-r4-oppo-downloads branch May 8, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant