Files
packages_apps_Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
Kuan Wang 4028e6123c Reset Optimization Mode of apps when users reset app settings.
Fix the issue that Optimization Mode is not reset when users click the
"Reset apps" button in the setting.

Bug: 222037028
Test: make RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.fuelgauge" + emulator
Change-Id: I22fb8aa19e284e11882b2920b77b544dee4cc33c
2022-05-16 05:01:20 +00:00

286 lines
12 KiB
Java

/*
* Copyright (C) 2021 The Android Open Source Project
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.settings.fuelgauge;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupHelper;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.os.Build;
import android.os.IDeviceIdleController;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** An implementation to backup and restore battery configurations. */
public final class BatteryBackupHelper implements BackupHelper {
/** An inditifier for {@link BackupHelper}. */
public static final String TAG = "BatteryBackupHelper";
private static final String DEVICE_IDLE_SERVICE = "deviceidle";
private static final boolean DEBUG = Build.TYPE.equals("userdebug");
static final String DELIMITER = ",";
static final String DELIMITER_MODE = ":";
static final String KEY_FULL_POWER_LIST = "full_power_list";
static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list";
@VisibleForTesting
ArraySet<ApplicationInfo> mTestApplicationInfoList = null;
@VisibleForTesting
PowerAllowlistBackend mPowerAllowlistBackend;
@VisibleForTesting
IDeviceIdleController mIDeviceIdleController;
@VisibleForTesting
IPackageManager mIPackageManager;
@VisibleForTesting
BatteryOptimizeUtils mBatteryOptimizeUtils;
private final Context mContext;
public BatteryBackupHelper(Context context) {
mContext = context.getApplicationContext();
}
@Override
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
if (!isOwner() || data == null) {
Log.w(TAG, "ignore performBackup() for non-owner or empty data");
return;
}
final List<String> allowlistedApps = backupFullPowerList(data);
if (allowlistedApps != null) {
backupOptimizationMode(data, allowlistedApps);
}
}
@Override
public void restoreEntity(BackupDataInputStream data) {
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];
try {
data.read(dataBytes, 0 /*offset*/, dataSize);
} catch (IOException e) {
Log.e(TAG, "failed to load BackupDataInputStream", e);
return;
}
restoreOptimizationMode(dataBytes);
}
}
@Override
public void writeNewStateDescription(ParcelFileDescriptor newState) {
}
private List<String> backupFullPowerList(BackupDataOutput data) {
final long timestamp = System.currentTimeMillis();
String[] allowlistedApps;
try {
allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist();
} catch (RemoteException e) {
Log.e(TAG, "backupFullPowerList() failed", e);
return null;
}
// Ignores unexpected emptty result case.
if (allowlistedApps == null || allowlistedApps.length == 0) {
Log.w(TAG, "no data found in the getFullPowerList()");
return new ArrayList<>();
}
final String allowedApps = String.join(DELIMITER, allowlistedApps);
writeBackupData(data, KEY_FULL_POWER_LIST, allowedApps);
Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms",
allowlistedApps.length, (System.currentTimeMillis() - timestamp)));
return Arrays.asList(allowlistedApps);
}
@VisibleForTesting
void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) {
final long timestamp = System.currentTimeMillis();
final ArraySet<ApplicationInfo> applications = getInstalledApplications();
if (applications == null || applications.isEmpty()) {
Log.w(TAG, "no data found in the getInstalledApplications()");
return;
}
int backupCount = 0;
final StringBuilder builder = new StringBuilder();
final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
// Converts application into the AppUsageState.
for (ApplicationInfo info : applications) {
final int mode = appOps.checkOpNoThrow(
AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName);
@BatteryOptimizeUtils.OptimizationMode
final int optimizationMode = BatteryOptimizeUtils.getAppOptimizationMode(
mode, allowlistedApps.contains(info.packageName));
// Ignores default optimized/unknown state or system/default apps.
if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
|| optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN
|| isSystemOrDefaultApp(info.packageName)) {
continue;
}
final String packageOptimizeMode =
info.packageName + DELIMITER_MODE + optimizationMode;
builder.append(packageOptimizeMode + DELIMITER);
debugLog(packageOptimizeMode);
backupCount++;
}
writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString());
Log.d(TAG, String.format("backup getInstalledApplications():%d count=%d in %d/ms",
applications.size(), backupCount, (System.currentTimeMillis() - timestamp)));
}
@VisibleForTesting
void 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;
}
final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
if (appConfigurations == null || appConfigurations.length == 0) {
Log.w(TAG, "no data found from the split() processing");
return;
}
int restoreCount = 0;
for (int index = 0; index < appConfigurations.length; index++) {
final String[] results = appConfigurations[index]
.split(BatteryBackupHelper.DELIMITER_MODE);
// Example format: com.android.systemui:2 we should have length=2
if (results == null || results.length != 2) {
Log.w(TAG, "invalid raw data found:" + appConfigurations[index]);
continue;
}
final String packageName = results[0];
// Ignores system/default apps.
if (isSystemOrDefaultApp(packageName)) {
Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName);
continue;
}
@BatteryOptimizeUtils.OptimizationMode
int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
try {
optimizationMode = Integer.parseInt(results[1]);
} catch (NumberFormatException e) {
Log.e(TAG, "failed to parse the optimization mode: "
+ appConfigurations[index], e);
continue;
}
restoreOptimizationMode(packageName, optimizationMode);
restoreCount++;
}
Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms",
restoreCount, (System.currentTimeMillis() - timestamp)));
}
private void restoreOptimizationMode(
String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
if (uid == BatteryUtils.UID_NULL) {
return;
}
final BatteryOptimizeUtils batteryOptimizeUtils =
mBatteryOptimizeUtils != null
? mBatteryOptimizeUtils /*testing only*/
: new BatteryOptimizeUtils(mContext, uid, packageName);
batteryOptimizeUtils.setAppUsageState(mode);
Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode));
}
// Provides an opportunity to inject mock IDeviceIdleController for testing.
private IDeviceIdleController getIDeviceIdleController() {
if (mIDeviceIdleController != null) {
return mIDeviceIdleController;
}
mIDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(DEVICE_IDLE_SERVICE));
return mIDeviceIdleController;
}
private IPackageManager getIPackageManager() {
if (mIPackageManager != null) {
return mIPackageManager;
}
mIPackageManager = AppGlobals.getPackageManager();
return mIPackageManager;
}
private PowerAllowlistBackend getPowerAllowlistBackend() {
if (mPowerAllowlistBackend != null) {
return mPowerAllowlistBackend;
}
mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext);
return mPowerAllowlistBackend;
}
private boolean isSystemOrDefaultApp(String packageName) {
final PowerAllowlistBackend powerAllowlistBackend = getPowerAllowlistBackend();
return powerAllowlistBackend.isSysAllowlisted(packageName)
|| powerAllowlistBackend.isDefaultActiveApp(packageName);
}
private ArraySet<ApplicationInfo> getInstalledApplications() {
if (mTestApplicationInfoList != null) {
return mTestApplicationInfoList;
}
return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager());
}
private void debugLog(String debugContent) {
if (DEBUG) Log.d(TAG, debugContent);
}
private static void writeBackupData(
BackupDataOutput data, String dataKey, String dataContent) {
final byte[] dataContentBytes = dataContent.getBytes();
try {
data.writeEntityHeader(dataKey, dataContentBytes.length);
data.writeEntityData(dataContentBytes, dataContentBytes.length);
} catch (IOException e) {
Log.e(TAG, "writeBackupData() is failed for " + dataKey, e);
}
}
private static boolean isOwner() {
return UserHandle.myUserId() == UserHandle.USER_OWNER;
}
}