Skip to content

Commit 9e38302

Browse files
authored
Fix stale/unresponsive battery-optimization item in About (#51)
* fix(about): reflect battery-optimization state; fix stale unresponsive 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. * review: guard refreshMaterialAboutList with isAdded (avoid null adapter) 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.
1 parent 92c45da commit 9e38302

2 files changed

Lines changed: 33 additions & 23 deletions

File tree

app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.nekohasekai.sagernet.ui
22

3-
import android.app.Activity
43
import android.content.Context
54
import android.content.Intent
65
import android.net.Uri
@@ -10,8 +9,6 @@ import android.os.PowerManager
109
import android.provider.Settings
1110
import android.view.View
1211
import android.widget.Toast
13-
import androidx.activity.result.component1
14-
import androidx.activity.result.component2
1512
import androidx.activity.result.contract.ActivityResultContracts
1613
import androidx.core.view.ViewCompat
1714
import androidx.recyclerview.widget.RecyclerView
@@ -51,12 +48,13 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) {
5148

5249
val requestIgnoreBatteryOptimizations = registerForActivityResult(
5350
ActivityResultContracts.StartActivityForResult()
54-
) { (resultCode, _) ->
55-
if (resultCode == Activity.RESULT_OK) {
56-
parentFragmentManager.beginTransaction()
57-
.replace(R.id.about_fragment_holder, AboutContent())
58-
.commitAllowingStateLoss()
59-
}
51+
) {
52+
// The battery-optimization request/settings screen returns RESULT_CANCELED even
53+
// when the user actually granted the exemption, so don't gate on the result code
54+
// — just rebuild the list so the item's on/off subtext reflects the new state.
55+
// Guard with isAdded: the fragment may be detached while the settings screen was
56+
// open, and refreshMaterialAboutList() would otherwise touch a null adapter.
57+
if (isAdded) refreshMaterialAboutList()
6058
}
6159

6260
override fun getMaterialAboutList(activityContext: Context): MaterialAboutList {
@@ -131,22 +129,32 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) {
131129
.apply {
132130
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
133131
val pm = app.getSystemService(Context.POWER_SERVICE) as PowerManager
134-
if (!pm.isIgnoringBatteryOptimizations(app.packageName)) {
135-
addItem(
136-
MaterialAboutActionItem.Builder()
137-
.icon(R.drawable.ic_baseline_running_with_errors_24)
138-
.text(R.string.ignore_battery_optimizations)
139-
.subText(R.string.ignore_battery_optimizations_sum)
140-
.setOnClickAction {
141-
requestIgnoreBatteryOptimizations.launch(
142-
Intent(
143-
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
144-
"package:${app.packageName}".toUri()
145-
)
132+
val ignoring = pm.isIgnoringBatteryOptimizations(app.packageName)
133+
addItem(
134+
MaterialAboutActionItem.Builder()
135+
.icon(R.drawable.ic_baseline_running_with_errors_24)
136+
.text(R.string.ignore_battery_optimizations)
137+
.subText(
138+
if (ignoring) R.string.battery_optimization_enabled
139+
else R.string.battery_optimization_disabled
140+
)
141+
.setOnClickAction {
142+
// The ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
143+
// dialog only appears while the app is still
144+
// optimized; once exempt it is a no-op. So when
145+
// already exempt, send the user to the battery
146+
// settings screen where they can toggle it back off.
147+
val intent = if (ignoring) {
148+
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
149+
} else {
150+
Intent(
151+
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
152+
"package:${app.packageName}".toUri()
146153
)
147154
}
148-
.build())
149-
}
155+
requestIgnoreBatteryOptimizations.launch(intent)
156+
}
157+
.build())
150158
}
151159
}
152160
.build())

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@
309309
<string name="donate_info">I love money</string>
310310
<string name="ignore_battery_optimizations">Ignore battery optimizations</string>
311311
<string name="ignore_battery_optimizations_sum">Remove some restrictions</string>
312+
<string name="battery_optimization_enabled">Enabled — the app can run unrestricted in the background</string>
313+
<string name="battery_optimization_disabled">Disabled — tap to allow background activity</string>
312314
<!-- plugin -->
313315
<string name="plugin">Plugin</string>
314316
<string name="plugin_configure">Configure…</string>

0 commit comments

Comments
 (0)