Merge "Add allowlist mechanism for battery optimization mode" into udc-dev am: 2b91f1a4cb

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/23216878

Change-Id: Id149ac65a51676e1622a023ab3159ea2c0ecc731
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Treehugger Robot
2023-05-15 08:31:04 +00:00
committed by Automerger Merge Worker
6 changed files with 193 additions and 19 deletions

View File

@@ -20,6 +20,7 @@ message BatteryOptimizeHistoricalLogEntry {
RESET = 3; RESET = 3;
RESTORE = 4; RESTORE = 4;
BACKUP = 5; BACKUP = 5;
FORCE_RESET = 6;
} }
optional string package_name = 1; optional string package_name = 1;

View File

@@ -526,6 +526,10 @@
<item>content://com.android.settings.slices/intent/media_output_indicator</item> <item>content://com.android.settings.slices/intent/media_output_indicator</item>
</string-array> </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. --> <!-- Uri to query non-public Slice Uris. -->
<string name="config_non_public_slice_query_uri" translatable="false"></string> <string name="config_non_public_slice_query_uri" translatable="false"></string>

View File

@@ -91,11 +91,12 @@ public final class BatteryBackupHelper implements BackupHelper {
@Override @Override
public void restoreEntity(BackupDataInputStream data) { public void restoreEntity(BackupDataInputStream data) {
BatterySettingsMigrateChecker.verifyConfiguration(mContext); BatterySettingsMigrateChecker.verifySaverConfiguration(mContext);
if (!isOwner() || data == null || data.size() == 0) { if (!isOwner() || data == null || data.size() == 0) {
Log.w(TAG, "ignore restoreEntity() for non-owner or empty data"); Log.w(TAG, "ignore restoreEntity() for non-owner or empty data");
return; return;
} }
if (KEY_OPTIMIZATION_LIST.equals(data.getKey())) { if (KEY_OPTIMIZATION_LIST.equals(data.getKey())) {
final int dataSize = data.size(); final int dataSize = data.size();
final byte[] dataBytes = new byte[dataSize]; final byte[] dataBytes = new byte[dataSize];
@@ -105,7 +106,10 @@ public final class BatteryBackupHelper implements BackupHelper {
Log.e(TAG, "failed to load BackupDataInputStream", e); Log.e(TAG, "failed to load BackupDataInputStream", e);
return; 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 @VisibleForTesting
void restoreOptimizationMode(byte[] dataBytes) { int restoreOptimizationMode(byte[] dataBytes) {
final long timestamp = System.currentTimeMillis(); final long timestamp = System.currentTimeMillis();
final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
if (dataContent == null || dataContent.isEmpty()) { if (dataContent == null || dataContent.isEmpty()) {
Log.w(TAG, "no data found in the restoreOptimizationMode()"); Log.w(TAG, "no data found in the restoreOptimizationMode()");
return; return 0;
} }
final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER); final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
if (appConfigurations == null || appConfigurations.length == 0) { if (appConfigurations == null || appConfigurations.length == 0) {
Log.w(TAG, "no data found from the split() processing"); Log.w(TAG, "no data found from the split() processing");
return; return 0;
} }
int restoreCount = 0; int restoreCount = 0;
for (int index = 0; index < appConfigurations.length; index++) { 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", Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms",
restoreCount, (System.currentTimeMillis() - timestamp))); restoreCount, (System.currentTimeMillis() - timestamp)));
return restoreCount;
} }
/** Dump the app optimization mode backup history data. */ /** Dump the app optimization mode backup history data. */
@@ -225,6 +230,23 @@ public final class BatteryBackupHelper implements BackupHelper {
getSharedPreferences(context), writer); 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 @VisibleForTesting
static SharedPreferences getSharedPreferences(Context context) { static SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences( return context.getSharedPreferences(
@@ -233,14 +255,11 @@ public final class BatteryBackupHelper implements BackupHelper {
private void restoreOptimizationMode( private void restoreOptimizationMode(
String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) { String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName); final BatteryOptimizeUtils batteryOptimizeUtils =
if (uid == BatteryUtils.UID_NULL) { newBatteryOptimizeUtils(mContext, packageName, mBatteryOptimizeUtils);
if (batteryOptimizeUtils == null) {
return; return;
} }
final BatteryOptimizeUtils batteryOptimizeUtils =
mBatteryOptimizeUtils != null
? mBatteryOptimizeUtils /*testing only*/
: new BatteryOptimizeUtils(mContext, uid, packageName);
batteryOptimizeUtils.setAppUsageState( batteryOptimizeUtils.setAppUsageState(
mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE); mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE);
Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode)); 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); Log.e(TAG, "writeBackupData() is failed for " + dataKey, e);
} }
} }
private static boolean isOwner() {
return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
}
} }

View File

@@ -31,11 +31,14 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
import com.android.settingslib.fuelgauge.PowerAllowlistBackend; import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;
/** A utility class for application usage operation. */ /** A utility class for application usage operation. */
public class BatteryOptimizeUtils { public class BatteryOptimizeUtils {
@@ -214,6 +217,11 @@ public class BatteryOptimizeUtils {
|| powerAllowlistBackend.isDefaultActiveApp(packageName, uid); || 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( private static void setAppUsageStateInternal(
Context context, @OptimizationMode int mode, int uid, String packageName, Context context, @OptimizationMode int mode, int uid, String packageName,
BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend,

View File

@@ -23,16 +23,27 @@ import android.content.Intent;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; 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.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController;
import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.fuelgauge.BatterySaverUtils;
import java.util.List;
/** Execute battery settings migration tasks in the device booting stage. */ /** Execute battery settings migration tasks in the device booting stage. */
public final class BatterySettingsMigrateChecker extends BroadcastReceiver { public final class BatterySettingsMigrateChecker extends BroadcastReceiver {
private static final String TAG = "BatterySettingsMigrateChecker"; private static final String TAG = "BatterySettingsMigrateChecker";
@VisibleForTesting
static BatteryOptimizeUtils sBatteryOptimizeUtils = null;
@Override @Override
public void onReceive(Context context, Intent intent) { 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); verifyConfiguration(context);
} }
} }
@@ -40,9 +51,35 @@ public final class BatterySettingsMigrateChecker extends BroadcastReceiver {
static void verifyConfiguration(Context context) { static void verifyConfiguration(Context context) {
context = context.getApplicationContext(); context = context.getApplicationContext();
verifySaverConfiguration(context); 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 ContentResolver resolver = context.getContentResolver();
final int threshold = Settings.Global.getInt(resolver, final int threshold = Settings.Global.getInt(resolver,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);

View File

@@ -18,35 +18,76 @@ package com.android.settings.fuelgauge;
import static com.google.common.truth.Truth.assertThat; 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.Context;
import android.content.Intent; 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.TestUtils;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry;
import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController; import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; 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) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {BatterySettingsMigrateCheckerTest.ShadowUserHandle.class})
public final class BatterySettingsMigrateCheckerTest { public final class BatterySettingsMigrateCheckerTest {
private static final Intent BOOT_COMPLETED_INTENT = private static final Intent BOOT_COMPLETED_INTENT =
new Intent(Intent.ACTION_BOOT_COMPLETED); 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 Context mContext;
private BatterySettingsMigrateChecker mBatterySettingsMigrateChecker; private BatterySettingsMigrateChecker mBatterySettingsMigrateChecker;
@Mock
private PackageManager mPackageManager;
@Mock
private BatteryOptimizeUtils mBatteryOptimizeUtils;
@Before @Before
public void setUp() { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); 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(); mBatterySettingsMigrateChecker = new BatterySettingsMigrateChecker();
} }
@After
public void resetShadows() {
ShadowUserHandle.reset();
}
@Test @Test
public void onReceive_invalidScheduledLevel_resetScheduledValue() { public void onReceive_invalidScheduledLevel_resetScheduledValue() {
final int invalidScheduledLevel = 5; final int invalidScheduledLevel = 5;
@@ -98,6 +139,54 @@ public final class BatterySettingsMigrateCheckerTest {
assertThat(getScheduledLevel()).isEqualTo(invalidScheduledLevel); 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) { private void setScheduledLevel(int scheduledLevel) {
TestUtils.setScheduledLevel(mContext, scheduledLevel); TestUtils.setScheduledLevel(mContext, scheduledLevel);
} }
@@ -105,4 +194,24 @@ public final class BatterySettingsMigrateCheckerTest {
private int getScheduledLevel() { private int getScheduledLevel() {
return TestUtils.getScheduledLevel(mContext); 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;
}
}
} }