Add allowlist mechanism for battery optimization mode
Add a mechanism to add package name into the allowlist to avoid users
change the battery optimization modes for specific apps in the list
https://screenshot.googleplex.com/8hrHCcTh5bNYXqp
Bug: 281566984
Test: make test RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.fuelgauge.*
Change-Id: I8efa6a55646d761f5bee3667a59b38ab68c74bc1
diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto
index 8512cb8..150c2e2 100644
--- a/protos/fuelgauge_log.proto
+++ b/protos/fuelgauge_log.proto
@@ -20,6 +20,7 @@
RESET = 3;
RESTORE = 4;
BACKUP = 5;
+ FORCE_RESET = 6;
}
optional string package_name = 1;
diff --git a/res/values/config.xml b/res/values/config.xml
index 52d7183..334d4e5 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -526,6 +526,10 @@
<item>content://com.android.settings.slices/intent/media_output_indicator</item>
</string-array>
+ <!-- List containing the apps cannot be changed the battery optimize modes -->
+ <string-array name="config_disable_optimization_mode_apps" translatable="false">
+ </string-array>
+
<!-- Uri to query non-public Slice Uris. -->
<string name="config_non_public_slice_query_uri" translatable="false"></string>
diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
index 79df57a..1bb3b4d 100644
--- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
+++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
@@ -91,11 +91,12 @@
@Override
public void restoreEntity(BackupDataInputStream data) {
- BatterySettingsMigrateChecker.verifyConfiguration(mContext);
+ BatterySettingsMigrateChecker.verifySaverConfiguration(mContext);
if (!isOwner() || data == null || data.size() == 0) {
Log.w(TAG, "ignore restoreEntity() for non-owner or empty data");
return;
}
+
if (KEY_OPTIMIZATION_LIST.equals(data.getKey())) {
final int dataSize = data.size();
final byte[] dataBytes = new byte[dataSize];
@@ -105,7 +106,10 @@
Log.e(TAG, "failed to load BackupDataInputStream", e);
return;
}
- restoreOptimizationMode(dataBytes);
+ final int restoreCount = restoreOptimizationMode(dataBytes);
+ if (restoreCount > 0) {
+ BatterySettingsMigrateChecker.verifyOptimizationModes(mContext);
+ }
}
}
@@ -175,17 +179,17 @@
}
@VisibleForTesting
- void restoreOptimizationMode(byte[] dataBytes) {
+ int restoreOptimizationMode(byte[] dataBytes) {
final long timestamp = System.currentTimeMillis();
final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
if (dataContent == null || dataContent.isEmpty()) {
Log.w(TAG, "no data found in the restoreOptimizationMode()");
- return;
+ return 0;
}
final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
if (appConfigurations == null || appConfigurations.length == 0) {
Log.w(TAG, "no data found from the split() processing");
- return;
+ return 0;
}
int restoreCount = 0;
for (int index = 0; index < appConfigurations.length; index++) {
@@ -217,6 +221,7 @@
}
Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms",
restoreCount, (System.currentTimeMillis() - timestamp)));
+ return restoreCount;
}
/** Dump the app optimization mode backup history data. */
@@ -225,6 +230,23 @@
getSharedPreferences(context), writer);
}
+ static boolean isOwner() {
+ return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
+ }
+
+ static BatteryOptimizeUtils newBatteryOptimizeUtils(
+ Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils) {
+ final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName);
+ if (uid == BatteryUtils.UID_NULL) {
+ return null;
+ }
+ final BatteryOptimizeUtils batteryOptimizeUtils =
+ testOptimizeUtils != null
+ ? testOptimizeUtils /*testing only*/
+ : new BatteryOptimizeUtils(context, uid, packageName);
+ return batteryOptimizeUtils;
+ }
+
@VisibleForTesting
static SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences(
@@ -233,14 +255,11 @@
private void restoreOptimizationMode(
String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
- final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
- if (uid == BatteryUtils.UID_NULL) {
+ final BatteryOptimizeUtils batteryOptimizeUtils =
+ newBatteryOptimizeUtils(mContext, packageName, mBatteryOptimizeUtils);
+ if (batteryOptimizeUtils == null) {
return;
}
- final BatteryOptimizeUtils batteryOptimizeUtils =
- mBatteryOptimizeUtils != null
- ? mBatteryOptimizeUtils /*testing only*/
- : new BatteryOptimizeUtils(mContext, uid, packageName);
batteryOptimizeUtils.setAppUsageState(
mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE);
Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode));
@@ -294,8 +313,4 @@
Log.e(TAG, "writeBackupData() is failed for " + dataKey, e);
}
}
-
- private static boolean isOwner() {
- return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
- }
}
diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
index b9ac64d..00611de 100644
--- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
@@ -31,11 +31,14 @@
import androidx.annotation.VisibleForTesting;
+import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
/** A utility class for application usage operation. */
public class BatteryOptimizeUtils {
@@ -214,6 +217,11 @@
|| powerAllowlistBackend.isDefaultActiveApp(packageName, uid);
}
+ static List<String> getAllowList(Context context) {
+ return Arrays.asList(context.getResources().getStringArray(
+ R.array.config_disable_optimization_mode_apps));
+ }
+
private static void setAppUsageStateInternal(
Context context, @OptimizationMode int mode, int uid, String packageName,
BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend,
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
index c54e6d8..4b9e6ef 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
@@ -23,16 +23,27 @@
import android.provider.Settings;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry;
import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
+import java.util.List;
+
/** Execute battery settings migration tasks in the device booting stage. */
public final class BatterySettingsMigrateChecker extends BroadcastReceiver {
private static final String TAG = "BatterySettingsMigrateChecker";
+ @VisibleForTesting
+ static BatteryOptimizeUtils sBatteryOptimizeUtils = null;
+
@Override
public void onReceive(Context context, Intent intent) {
- if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ if (intent != null
+ && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())
+ && BatteryBackupHelper.isOwner()) {
verifyConfiguration(context);
}
}
@@ -40,9 +51,35 @@
static void verifyConfiguration(Context context) {
context = context.getApplicationContext();
verifySaverConfiguration(context);
+ verifyOptimizationModes(context);
}
- private static void verifySaverConfiguration(Context context) {
+ /** Avoid users set important apps into the unexpected battery optimize modes */
+ static void verifyOptimizationModes(Context context) {
+ Log.d(TAG, "invoke verifyOptimizationModes()");
+ verifyOptimizationModes(context, BatteryOptimizeUtils.getAllowList(context));
+ }
+
+ @VisibleForTesting
+ static void verifyOptimizationModes(Context context, List<String> allowList) {
+ allowList.forEach(packageName -> {
+ final BatteryOptimizeUtils batteryOptimizeUtils =
+ BatteryBackupHelper.newBatteryOptimizeUtils(context, packageName,
+ /* testOptimizeUtils */ sBatteryOptimizeUtils);
+ if (batteryOptimizeUtils == null) {
+ return;
+ }
+ if (batteryOptimizeUtils.getAppOptimizationMode() !=
+ BatteryOptimizeUtils.MODE_OPTIMIZED) {
+ Log.w(TAG, "Reset optimization mode for: " + packageName);
+ batteryOptimizeUtils.setAppUsageState(BatteryOptimizeUtils.MODE_OPTIMIZED,
+ BatteryOptimizeHistoricalLogEntry.Action.FORCE_RESET);
+ }
+ });
+ }
+
+ static void verifySaverConfiguration(Context context) {
+ Log.d(TAG, "invoke verifySaverConfiguration()");
final ContentResolver resolver = context.getContentResolver();
final int threshold = Settings.Global.getInt(resolver,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsMigrateCheckerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsMigrateCheckerTest.java
index dfee3e7..c34dcec 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsMigrateCheckerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsMigrateCheckerTest.java
@@ -18,35 +18,76 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.os.UserManager;
import com.android.settings.TestUtils;
+import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry;
import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {BatterySettingsMigrateCheckerTest.ShadowUserHandle.class})
public final class BatterySettingsMigrateCheckerTest {
private static final Intent BOOT_COMPLETED_INTENT =
new Intent(Intent.ACTION_BOOT_COMPLETED);
+ private static final int UID = 2003;
+ private static final String PACKAGE_NAME = "com.android.test.app";
private Context mContext;
private BatterySettingsMigrateChecker mBatterySettingsMigrateChecker;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private BatteryOptimizeUtils mBatteryOptimizeUtils;
+
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(RuntimeEnvironment.application);
+ doReturn(mContext).when(mContext).getApplicationContext();
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(UID).when(mPackageManager)
+ .getPackageUid(PACKAGE_NAME, PackageManager.GET_META_DATA);
+ BatterySettingsMigrateChecker.sBatteryOptimizeUtils = mBatteryOptimizeUtils;
mBatterySettingsMigrateChecker = new BatterySettingsMigrateChecker();
}
+ @After
+ public void resetShadows() {
+ ShadowUserHandle.reset();
+ }
+
@Test
public void onReceive_invalidScheduledLevel_resetScheduledValue() {
final int invalidScheduledLevel = 5;
@@ -98,6 +139,54 @@
assertThat(getScheduledLevel()).isEqualTo(invalidScheduledLevel);
}
+ @Test
+ public void onReceive_nonOwner_noAction() {
+ ShadowUserHandle.setUid(1);
+ final int invalidScheduledLevel = 5;
+ setScheduledLevel(invalidScheduledLevel);
+
+ mBatterySettingsMigrateChecker.onReceive(mContext, BOOT_COMPLETED_INTENT);
+
+ assertThat(getScheduledLevel()).isEqualTo(invalidScheduledLevel);
+ }
+
+ @Test
+ public void verifyOptimizationModes_inAllowList_resetOptimizationMode() throws Exception {
+ doReturn(BatteryOptimizeUtils.MODE_RESTRICTED).when(mBatteryOptimizeUtils)
+ .getAppOptimizationMode();
+
+ mBatterySettingsMigrateChecker.verifyOptimizationModes(
+ mContext, Arrays.asList(PACKAGE_NAME));
+
+ final InOrder inOrder = inOrder(mBatteryOptimizeUtils);
+ inOrder.verify(mBatteryOptimizeUtils).getAppOptimizationMode();
+ inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(
+ BatteryOptimizeUtils.MODE_OPTIMIZED,
+ BatteryOptimizeHistoricalLogEntry.Action.FORCE_RESET);
+ }
+
+ @Test
+ public void verifyOptimizationModes_optimizedMode_noAction() throws Exception {
+ doReturn(BatteryOptimizeUtils.MODE_OPTIMIZED).when(mBatteryOptimizeUtils)
+ .getAppOptimizationMode();
+
+ mBatterySettingsMigrateChecker.verifyOptimizationModes(
+ mContext, Arrays.asList(PACKAGE_NAME));
+
+ verify(mBatteryOptimizeUtils, never()).setAppUsageState(anyInt(), any());
+ }
+
+ @Test
+ public void verifyOptimizationModes_notInAllowList_noAction() throws Exception {
+ doReturn(BatteryOptimizeUtils.MODE_RESTRICTED).when(mBatteryOptimizeUtils)
+ .getAppOptimizationMode();
+
+ mBatterySettingsMigrateChecker.verifyOptimizationModes(
+ mContext, new ArrayList<String>());
+
+ verifyNoInteractions(mBatteryOptimizeUtils);
+ }
+
private void setScheduledLevel(int scheduledLevel) {
TestUtils.setScheduledLevel(mContext, scheduledLevel);
}
@@ -105,4 +194,24 @@
private int getScheduledLevel() {
return TestUtils.getScheduledLevel(mContext);
}
+
+ @Implements(UserHandle.class)
+ public static class ShadowUserHandle {
+ // Sets the default as thte OWNER role.
+ private static int sUid = 0;
+
+ public static void setUid(int uid) {
+ sUid = uid;
+ }
+
+ @Implementation
+ public static int myUserId() {
+ return sUid;
+ }
+
+ @Resetter
+ public static void reset() {
+ sUid = 0;
+ }
+ }
}