Skip to content

Instantly share code, notes, and snippets.

@moopat
Last active January 29, 2024 05:18
Show Gist options
  • Star 38 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save moopat/e9735fa8b5cff69d003353a4feadcdbc to your computer and use it in GitHub Desktop.
Save moopat/e9735fa8b5cff69d003353a4feadcdbc to your computer and use it in GitHub Desktop.
Many Android manufacturers cause trouble by implementing battery optimizations that cause apps to behave unexpectedly at runtime. This util class allows you to show a dialog to users having such phones and sends them to the settings screen where they can turn of battery optimizations for your app.
/*
* MIT License
*
* Copyright (c) 2018 Markus Deutsch
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package at.moop.util;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import java.util.ArrayList;
import java.util.List;
import at.moop.R;
/**
* Get a dialog that informs the user to disable battery optimization for your app.
* <p>
* Use the dialog like that:
* final AlertDialog dialog = BatteryOptimizationUtil.getBatteryOptimizationDialog(context);
* if(dialog != null) dialog.show();
* <p>
* Alter the dialog texts so that they fit your needs. You can provide additional actions that
* should be performed if the positive or negative button are clicked by using the provided method:
* getBatteryOptimizationDialog(Context, OnBatteryOptimizationAccepted, OnBatteryOptimizationCanceled)
* <p>
* Source: https://gist.github.com/moopat/e9735fa8b5cff69d003353a4feadcdbc
* <p>
* @author Markus Deutsch @moopat
*/
public class BatteryOptimizationUtil {
/**
* Get the battery optimization dialog.
* By default the dialog will send the user to the relevant activity if the positive button is
* clicked, and closes the dialog if the negative button is clicked.
*
* @param context Context
* @return the dialog or null if battery optimization is not available on this device
*/
@Nullable
public static AlertDialog getBatteryOptimizationDialog(final Context context) {
return getBatteryOptimizationDialog(context, null, null);
}
/**
* Get the battery optimization dialog.
* By default the dialog will send the user to the relevant activity if the positive button is
* clicked, and closes the dialog if the negative button is clicked. Callbacks can be provided
* to perform additional actions on either button click.
*
* @param context Context
* @param positiveCallback additional callback for the positive button. can be null.
* @param negativeCallback additional callback for the negative button. can be null.
* @return the dialog or null if battery optimization is not available on this device
*/
@Nullable
public static AlertDialog getBatteryOptimizationDialog(
final Context context,
@Nullable final OnBatteryOptimizationAccepted positiveCallback,
@Nullable final OnBatteryOptimizationCanceled negativeCallback) {
/*
* If there is no resolvable component return right away. We do not use
* isBatteryOptimizationAvailable() for this check in order to avoid checking for
* resolvable components twice.
*/
final ComponentName componentName = getResolveableComponentName(context);
if (componentName == null) return null;
return new AlertDialog.Builder(context)
.setTitle(R.string.dialog_battery_title)
.setMessage(R.string.dialog_battery_message)
.setNegativeButton(R.string.dialog_battery_button_negative, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (negativeCallback != null)
negativeCallback.onBatteryOptimizationCanceled();
}
})
.setPositiveButton(R.string.dialog_battery_button_positive, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (positiveCallback != null)
positiveCallback.onBatteryOptimizationAccepted();
final Intent intent = new Intent();
intent.setComponent(componentName);
context.startActivity(intent);
}
}).create();
}
/**
* Find out if battery optimization settings are available on this device.
*
* @param context Context
* @return true if battery optimization is available
*/
public static boolean isBatteryOptimizationAvailable(final Context context) {
return getResolveableComponentName(context) != null;
}
@Nullable
private static ComponentName getResolveableComponentName(final Context context) {
for (ComponentName componentName : getComponentNames()) {
final Intent intent = new Intent();
intent.setComponent(componentName);
if (context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null)
return componentName;
}
return null;
}
/**
* Get a list of all known ComponentNames that provide battery optimization on different
* devices.
* Based on Shivam Oberoi's answer on StackOverflow: https://stackoverflow.com/a/48166241/2143225
*
* @return list of ComponentName
*/
private static List<ComponentName> getComponentNames() {
final List<ComponentName> names = new ArrayList<>();
names.add(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity"));
names.add(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity"));
names.add(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
names.add(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
names.add(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity"));
names.add(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity"));
return names;
}
public interface OnBatteryOptimizationAccepted {
/**
* Called if the user clicks the "OK" button of the battery optimization dialog. This does
* not mean that the user has performed the necessary steps to exclude the app from
* battery optimizations.
*/
void onBatteryOptimizationAccepted();
}
public interface OnBatteryOptimizationCanceled {
/**
* Called if the user clicks the "Cancel" button of the battery optimization dialog.
*/
void onBatteryOptimizationCanceled();
}
}
@moopat
Copy link
Author

moopat commented Mar 8, 2018

Usage

The simplest possible usage. Do not forget to check if the dialog is null: if there are no (known) optimizations that the user can turn off the dialog is null.

final AlertDialog dialog = BatteryOptimizationUtil.getBatteryOptimizationDialog(getContext());
if (dialog != null) dialog.show();

You can also implement custom callbacks to e.g. log dialog events. Those callbacks are called additionally to the default callback of the positive button, which always sends the user to the external settings screen.

final AlertDialog dialog = BatteryOptimizationUtil.getBatteryOptimizationDialog(
        getContext(),
        new BatteryOptimizationUtil.OnBatteryOptimizationAccepted() {
            @Override
            public void onBatteryOptimizationAccepted() {
                FirebaseAnalytics.getInstance(getContext()).logEvent("battery_optimizations_accepted", null);
            }
        },
        new BatteryOptimizationUtil.OnBatteryOptimizationCanceled() {
            @Override
            public void onBatteryOptimizationCanceled() {
                FirebaseAnalytics.getInstance(getContext()).logEvent("battery_optimizations_canceled", null);
            }
        });
if (dialog != null) dialog.show();

Additional Manufacturers

If you have information regarding additional manufacturers that have battery optimizations that can be turned off by the user just let me know.

@Shaarawi
Copy link

@bhavikshah92
Copy link

bhavikshah92 commented Aug 25, 2018

Hello,

Thanks for this util class.

Also I have implemented the callback as you suggested but what if the intent gets open but user presses back button on the intent?

Also I've seen battery optimization in OnePlus devices

@lengchiva
Copy link

lengchiva commented Jul 12, 2019

Sorry it not work with oppo
Error
java.lang.SecurityException: Permission Denial: starting Intent { cmp=com.coloros.safecenter/.startupapp.StartupAppListActivity } from ProcessRecord{8f1fef2 11933:leng.chiva.allowbackground/u0a219} (pid=11933, uid=10219) requires oppo.permission.OPPO_COMPONENT_SAFE

@ankurbhut
Copy link

I want the isBatteryOptimization() method to check the power saver mode is on or off. So we can check from the same class and if available then use this dialog to restart it.

@waseefakhtar
Copy link

Sorry it not work with oppo
Error
java.lang.SecurityException: Permission Denial: starting Intent { cmp=com.coloros.safecenter/.startupapp.StartupAppListActivity } from ProcessRecord{8f1fef2 11933:leng.chiva.allowbackground/u0a219} (pid=11933, uid=10219) requires oppo.permission.OPPO_COMPONENT_SAFE

I agree with @lengchiva, it does not work for OPPO.

@wrozwad
Copy link

wrozwad commented Mar 17, 2020

I agree with @lengchiva, it does not work for OPPO.

+1

@CJxD
Copy link

CJxD commented Apr 8, 2020

For OPPO and some Huawei phones, you need the following permissions:

<uses-permission android:name="oppo.permission.OPPO_COMPONENT_SAFE"/>
<uses-permission android:name="com.huawei.permission.external_app_settings.USE_COMPONENT"/>

@moopat it may be worth wrapping startActivity() in a try-catch to catch the SecurityExceptions that arise from this case. Otherwise apps will just crash on these phones.

@CJxD
Copy link

CJxD commented Apr 8, 2020

@moopat here's the latest batch of ComponentNames. Note that some Huawei phones will throw a SecurityException if you try StartupAppControlActivity, but work if you do StartupNormalAppListActivity, so the order here is important.

names.add(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity"));
names.add(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity"));
names.add(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity"));
names.add(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
names.add(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.appcontrol.activity.StartupAppControlActivity"));
names.add(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
names.add(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity"));
names.add(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity"));
names.add(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity"));
names.add(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager"));
names.add(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity"));
names.add(new ComponentName("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity"));
names.add(new ComponentName("com.htc.pitroad", "com.htc.pitroad.landingpage.activity.LandingPageActivity"));
names.add(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.MainActivity"));

@ishroid
Copy link

ishroid commented Apr 27, 2020

End of this problem :-)
checkout this library 👍

https://github.com/ishroid/AppKillerManager

@CJxD
Copy link

CJxD commented May 18, 2020

@ishroid Thanks, I'll contribute to this when I get time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment