diff --git a/app/src/main/java/com/nextcloud/utils/BatteryOptimizationHelper.kt b/app/src/main/java/com/nextcloud/utils/BatteryOptimizationHelper.kt
index eb4add0298c1..6bc1bb27cef8 100644
--- a/app/src/main/java/com/nextcloud/utils/BatteryOptimizationHelper.kt
+++ b/app/src/main/java/com/nextcloud/utils/BatteryOptimizationHelper.kt
@@ -8,6 +8,8 @@
package com.nextcloud.utils
import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.PowerManager
@@ -15,32 +17,82 @@ import android.provider.Settings
import androidx.core.net.toUri
import com.owncloud.android.lib.common.utils.Log_OC
+/**
+ * Helper for checking and requesting Android battery optimization exemptions.
+ *
+ * Methods allow checking whether the app is impacted by battery optimizations and
+ * guide the user to the appropriate settings screen to grant exemption.
+ */
object BatteryOptimizationHelper {
private const val TAG = "BatteryOptimizationHelper"
+ /**
+ * Returns true if battery optimization is currently enabled for the app.
+ */
fun isBatteryOptimizationEnabled(context: Context): Boolean {
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
return !pm.isIgnoringBatteryOptimizations(context.packageName)
}
+ /**
+ * Guides the user to disable battery optimization for this app.
+ * Returns true if a settings screen was opened, false if not possible.
+ */
@Suppress("TooGenericExceptionCaught")
@SuppressLint("BatteryLife")
- fun openBatteryOptimizationSettings(context: Context) {
- try {
+ fun openBatteryOptimizationSettings(context: Context): Boolean {
+ return runCatching {
+ tryOpenIgnoreBatteryOptimizationsScreen(context)
+ || tryOpenGeneralBatteryOptimizationSettings(context)
+ }.getOrElse { e ->
+ Log_OC.w(TAG, "Failed to open battery optimization settings", e)
+ false
+ }
+ }
+
+ private fun tryOpenIgnoreBatteryOptimizationsScreen(context: Context): Boolean {
+ return try {
val intent = Intent(
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
"package:${context.packageName}".toUri()
)
+ // Add ONLY if non-Activity context (for best navigation UX)
+ if (context !is Activity) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ if (intent.resolveActivity(context.packageManager) != null) {
+ context.startActivity(intent)
+ true
+ } else {
+ false
+ }
+ } catch (e: ActivityNotFoundException) {
+ Log_OC.w(TAG, "Direct battery optimization exemption screen not found", e)
+ false
+ }
+ }
+
+ private fun tryOpenGeneralBatteryOptimizationSettings(context: Context): Boolean {
+ return try {
+ val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
+
+ // Add ONLY if non-Activity context (for best navigation UX)
+ if (context !is Activity) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
+ true
} else {
- // Fallback to generic battery optimization settings
- context.startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
+ false
}
- } catch (e: Exception) {
- Log_OC.d(TAG, "open battery optimization settings: ", e)
+ } catch (e: ActivityNotFoundException) {
+ Log_OC.e(TAG, "General battery optimization settings screen not found", e)
+ false
}
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
index c12acc61e2c5..ad5d6c9d333a 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
@@ -849,22 +849,38 @@ class SyncedFoldersActivity :
}
private fun showBatteryOptimizationDialog() {
+ // Only show dialog if activity is in resumed state (prevents crashes)
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
Log_OC.w(TAG, "Activity not resumed, skipping battery dialog")
return
}
+ // Build the dialog with NO automatic positive button listener
val dialog = MaterialAlertDialogBuilder(this, R.style.Theme_ownCloud_Dialog)
.setTitle(R.string.battery_optimization_title)
.setMessage(R.string.battery_optimization_message)
- .setPositiveButton(R.string.battery_optimization_disable) { _, _ ->
- BatteryOptimizationHelper.openBatteryOptimizationSettings(this)
- }
+ .setPositiveButton(R.string.battery_optimization_disable, null) // Custom handler set below
.setNeutralButton(R.string.battery_optimization_close, null)
.setIcon(R.drawable.ic_battery_alert)
+ viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialog)
+
val alertDialog = dialog.show()
+ // Try to open battery optimization settings. If it fails, stay open for visibillity.
+ alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
+ val success = BatteryOptimizationHelper.openBatteryOptimizationSettings(this)
+ if (success) {
+ // We dismiss here because we successfully sent the user to Settings.
+ // If they come back without changing settings, the dialog stays gone
+ // until the next time showBatteryOptimizationDialogIfNeeded() runs.
+ alertDialog.dismiss()
+ } else {
+ showSnackMessage(getString(R.string.battery_optimization_unable_to_open_settings))
+ }
+ }
+
+ // Consistently style the dialog's buttons
viewThemeUtils.platform.colorTextButtons(
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 97f04a8862af..6e74bbfcc460 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1076,6 +1076,7 @@
Error retrieving templates
Battery optimization
Your device may have battery optimization enabled. AutoUpload works only properly if you exclude this app from it.
+ Unable to open battery optimization settings
Disable
Close
Failed to load details