Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 58 additions & 6 deletions app/src/main/java/com/nextcloud/utils/BatteryOptimizationHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,91 @@
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
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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@
<string name="error_retrieving_templates">Error retrieving templates</string>
<string name="battery_optimization_title">Battery optimization</string>
<string name="battery_optimization_message">Your device may have battery optimization enabled. AutoUpload works only properly if you exclude this app from it.</string>
<string name="battery_optimization_unable_to_open_settings">Unable to open battery optimization settings</string>
<string name="battery_optimization_disable">Disable</string>
<string name="battery_optimization_close">Close</string>
<string name="file_details_no_content">Failed to load details</string>
Expand Down
Loading