Skip to content

Fix stale/unresponsive battery-optimization item in About#51

Merged
hawkff merged 2 commits into
mainfrom
fix/battery-optimization-state
Jun 21, 2026
Merged

Fix stale/unresponsive battery-optimization item in About#51
hawkff merged 2 commits into
mainfrom
fix/battery-optimization-state

Conversation

@hawkff

@hawkff hawkff commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a confusing UX bug on the About screen's Ignore battery optimizations item.

The bug

  • The item was only shown while the app was still battery-optimized, and its result callback
    recreated the list only on RESULT_OK.
  • But Android's ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS dialog returns RESULT_CANCELED
    even when the user grants the exemption. So after granting, the item never refreshed: it
    stayed visible, and tapping it again did nothing (the app is already exempt, so the request
    dialog is a no-op).

The fix

  • Always show the item, with subtext reflecting current state:
    • exempt → "Enabled — the app can run unrestricted in the background"
    • not exempt → "Disabled — tap to allow background activity"
  • When already exempt, tapping opens ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS (the system
    battery-optimization list) so the user can actually toggle it back off — the request dialog
    can't do that.
  • Refresh the list via refreshMaterialAboutList() regardless of result code, so the subtext
    updates immediately on return (works around the always-CANCELED result).

Testing

  • CodeRabbit CLI: No findings; compiles clean.
  • On-device visual confirmation pending — the device was PIN-locked when I went to verify; the
    app reported the package is in the deviceidle whitelist (exempt), so the item should now read
    "Enabled …". Will confirm after unlock.

Notes

  • REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission already declared; the settings-list action
    needs none.
  • ignore_battery_optimizations_sum ("Remove some restrictions") is now unused but left in place
    (harmless; removing across locales risks orphaned-translation lint with warningsAsErrors).

Greptile Summary

This PR fixes a UX bug on the About screen where the battery-optimization preference item was stale: the Android system always returns RESULT_CANCELED from ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS even when the user grants the exemption, so the old RESULT_OK-gated fragment replacement never fired, leaving the item permanently visible and non-functional after granting.

  • Always shows the battery optimization item with live subtext (battery_optimization_enabled / battery_optimization_disabled), and rebuilds the list on every return from the system screen regardless of result code.
  • Routes the click to ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS when already exempt (so the user can revoke), and to ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS otherwise.
  • Adds an isAdded guard to refreshMaterialAboutList() to avoid touching a null adapter if the fragment was detached while the system screen was open.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to the battery-optimization item on the About screen and introduces no new risky interactions.

Both the request-path and the already-exempt path are correctly handled. The isAdded guard prevents the previously flagged null-adapter crash. The ignoring closure is always fresh because refreshMaterialAboutList() re-invokes getMaterialAboutList(), which re-evaluates isIgnoringBatteryOptimizations() and rebuilds the click listener with the current state. No other code paths are affected.

No files require special attention.

Important Files Changed

Filename Overview
app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt Rewrites the battery-optimization item: always shown with state-aware subtext, correct intent routing by exemption state, and isAdded-guarded refreshMaterialAboutList() replacing the old RESULT_OK-gated fragment transaction.
app/src/main/res/values/strings.xml Adds two new string resources (battery_optimization_enabled, battery_optimization_disabled) for the new state-aware subtext; existing unused string left in place intentionally.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant AboutContent
    participant Android

    User->>AboutContent: Open About screen
    AboutContent->>Android: isIgnoringBatteryOptimizations()
    Android-->>AboutContent: ignoring (true/false)
    AboutContent->>User: Show item with state subtext

    alt "App is NOT exempt (ignoring = false)"
        User->>AboutContent: Tap item
        AboutContent->>Android: Launch ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
        User->>Android: Grant or deny exemption
        Android-->>AboutContent: onActivityResult (always RESULT_CANCELED)
        Note over AboutContent: isAdded guard passes
        AboutContent->>Android: isIgnoringBatteryOptimizations() (fresh)
        AboutContent->>User: Refresh list with updated subtext
    else "App IS exempt (ignoring = true)"
        User->>AboutContent: Tap item
        AboutContent->>Android: Launch ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
        User->>Android: Optionally revoke exemption
        Android-->>AboutContent: onActivityResult (RESULT_CANCELED)
        Note over AboutContent: isAdded guard passes
        AboutContent->>Android: isIgnoringBatteryOptimizations() (fresh)
        AboutContent->>User: Refresh list with updated subtext
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant AboutContent
    participant Android

    User->>AboutContent: Open About screen
    AboutContent->>Android: isIgnoringBatteryOptimizations()
    Android-->>AboutContent: ignoring (true/false)
    AboutContent->>User: Show item with state subtext

    alt "App is NOT exempt (ignoring = false)"
        User->>AboutContent: Tap item
        AboutContent->>Android: Launch ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
        User->>Android: Grant or deny exemption
        Android-->>AboutContent: onActivityResult (always RESULT_CANCELED)
        Note over AboutContent: isAdded guard passes
        AboutContent->>Android: isIgnoringBatteryOptimizations() (fresh)
        AboutContent->>User: Refresh list with updated subtext
    else "App IS exempt (ignoring = true)"
        User->>AboutContent: Tap item
        AboutContent->>Android: Launch ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
        User->>Android: Optionally revoke exemption
        Android-->>AboutContent: onActivityResult (RESULT_CANCELED)
        Note over AboutContent: isAdded guard passes
        AboutContent->>Android: isIgnoringBatteryOptimizations() (fresh)
        AboutContent->>User: Refresh list with updated subtext
    end
Loading

Reviews (2): Last reviewed commit: "review: guard refreshMaterialAboutList w..." | Re-trigger Greptile

…e item

The 'Ignore battery optimizations' item was only shown while the app was still
optimized, and its result callback recreated the list only on RESULT_OK. But Android's
ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS dialog returns RESULT_CANCELED even when the
user grants the exemption, so the item never refreshed: it stayed visible and a second
tap did nothing (already exempt -> the request dialog is a no-op).

Now:
- always show the item, with subtext reflecting state (Enabled / Disabled);
- when already exempt, tapping opens ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS so the
  user can toggle it back off (the request dialog can't);
- refresh the list via refreshMaterialAboutList() regardless of result code so the
  subtext updates immediately on return.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@hawkff, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 13 minutes and 28 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2be30903-abdc-4c43-abd0-f4eae60ea6c1

📥 Commits

Reviewing files that changed from the base of the PR and between 92c45da and f9c283e.

📒 Files selected for processing (2)
  • app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
  • app/src/main/res/values/strings.xml

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

Comment thread app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
Per Greptile: if the fragment is detached while the battery-settings screen is open,
the activity-result callback's refreshMaterialAboutList() could dereference a null
adapter. Guard with isAdded, matching the checkUpdate() pattern.
@hawkff hawkff merged commit 9e38302 into main Jun 21, 2026
5 checks passed
@hawkff hawkff deleted the fix/battery-optimization-state branch June 21, 2026 01:08
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