diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto index 8512cb82506..150c2e2ce6a 100644 --- a/protos/fuelgauge_log.proto +++ b/protos/fuelgauge_log.proto @@ -20,6 +20,7 @@ message BatteryOptimizeHistoricalLogEntry { 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 52d7183ebff..334d4e574c5 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -526,6 +526,10 @@ content://com.android.settings.slices/intent/media_output_indicator + + + + diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java index 79df57ab08d..1bb3b4de001 100644 --- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java +++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java @@ -91,11 +91,12 @@ public final class BatteryBackupHelper implements BackupHelper { @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 @@ public final class BatteryBackupHelper implements BackupHelper { 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 @@ public final class BatteryBackupHelper implements BackupHelper { } @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 @@ public final class BatteryBackupHelper implements BackupHelper { } 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 @@ public final class BatteryBackupHelper implements BackupHelper { 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 @@ public final class BatteryBackupHelper implements BackupHelper { 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 @@ public final class BatteryBackupHelper implements BackupHelper { 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 b9ac64dd781..00611de7817 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -31,11 +31,14 @@ import android.util.Log; 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 @@ public class BatteryOptimizeUtils { || powerAllowlistBackend.isDefaultActiveApp(packageName, uid); } + static List 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 c54e6d8c503..4b9e6efaff0 100644 --- a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java +++ b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java @@ -23,16 +23,27 @@ import android.content.Intent; 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 @@ public final class BatterySettingsMigrateChecker extends BroadcastReceiver { 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 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 dfee3e797b9..c34dcecce73 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 @@ package com.android.settings.fuelgauge; 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 @@ public final class BatterySettingsMigrateCheckerTest { 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()); + + verifyNoInteractions(mBatteryOptimizeUtils); + } + private void setScheduledLevel(int scheduledLevel) { TestUtils.setScheduledLevel(mContext, scheduledLevel); } @@ -105,4 +194,24 @@ public final class BatterySettingsMigrateCheckerTest { 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; + } + } }