1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 * 15 */ 16 17 package com.android.settings.fuelgauge; 18 19 import android.app.AppGlobals; 20 import android.app.AppOpsManager; 21 import android.app.backup.BackupDataInputStream; 22 import android.app.backup.BackupDataOutput; 23 import android.app.backup.BackupHelper; 24 import android.content.Context; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.IPackageManager; 27 import android.os.Build; 28 import android.os.IDeviceIdleController; 29 import android.os.ParcelFileDescriptor; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.UserHandle; 33 import android.util.ArraySet; 34 import android.util.Log; 35 36 import androidx.annotation.VisibleForTesting; 37 38 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 39 40 import java.io.IOException; 41 import java.nio.charset.StandardCharsets; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.List; 45 46 /** An implementation to backup and restore battery configurations. */ 47 public final class BatteryBackupHelper implements BackupHelper { 48 /** An inditifier for {@link BackupHelper}. */ 49 public static final String TAG = "BatteryBackupHelper"; 50 private static final String DEVICE_IDLE_SERVICE = "deviceidle"; 51 private static final boolean DEBUG = Build.TYPE.equals("userdebug"); 52 53 static final String DELIMITER = ","; 54 static final String DELIMITER_MODE = ":"; 55 static final String KEY_FULL_POWER_LIST = "full_power_list"; 56 static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list"; 57 58 @VisibleForTesting 59 ArraySet<ApplicationInfo> mTestApplicationInfoList = null; 60 61 @VisibleForTesting 62 PowerAllowlistBackend mPowerAllowlistBackend; 63 @VisibleForTesting 64 IDeviceIdleController mIDeviceIdleController; 65 @VisibleForTesting 66 IPackageManager mIPackageManager; 67 @VisibleForTesting 68 BatteryOptimizeUtils mBatteryOptimizeUtils; 69 70 private final Context mContext; 71 BatteryBackupHelper(Context context)72 public BatteryBackupHelper(Context context) { 73 mContext = context.getApplicationContext(); 74 } 75 76 @Override performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)77 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 78 ParcelFileDescriptor newState) { 79 if (!isOwner() || data == null) { 80 Log.w(TAG, "ignore performBackup() for non-owner or empty data"); 81 return; 82 } 83 final List<String> allowlistedApps = backupFullPowerList(data); 84 if (allowlistedApps != null) { 85 backupOptimizationMode(data, allowlistedApps); 86 } 87 } 88 89 @Override restoreEntity(BackupDataInputStream data)90 public void restoreEntity(BackupDataInputStream data) { 91 if (!isOwner() || data == null || data.size() == 0) { 92 Log.w(TAG, "ignore restoreEntity() for non-owner or empty data"); 93 return; 94 } 95 if (KEY_OPTIMIZATION_LIST.equals(data.getKey())) { 96 final int dataSize = data.size(); 97 final byte[] dataBytes = new byte[dataSize]; 98 try { 99 data.read(dataBytes, 0 /*offset*/, dataSize); 100 } catch (IOException e) { 101 Log.e(TAG, "failed to load BackupDataInputStream", e); 102 return; 103 } 104 restoreOptimizationMode(dataBytes); 105 } 106 } 107 108 @Override writeNewStateDescription(ParcelFileDescriptor newState)109 public void writeNewStateDescription(ParcelFileDescriptor newState) { 110 } 111 backupFullPowerList(BackupDataOutput data)112 private List<String> backupFullPowerList(BackupDataOutput data) { 113 final long timestamp = System.currentTimeMillis(); 114 String[] allowlistedApps; 115 try { 116 allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist(); 117 } catch (RemoteException e) { 118 Log.e(TAG, "backupFullPowerList() failed", e); 119 return null; 120 } 121 // Ignores unexpected emptty result case. 122 if (allowlistedApps == null || allowlistedApps.length == 0) { 123 Log.w(TAG, "no data found in the getFullPowerList()"); 124 return new ArrayList<>(); 125 } 126 127 final String allowedApps = String.join(DELIMITER, allowlistedApps); 128 writeBackupData(data, KEY_FULL_POWER_LIST, allowedApps); 129 Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms", 130 allowlistedApps.length, (System.currentTimeMillis() - timestamp))); 131 return Arrays.asList(allowlistedApps); 132 } 133 134 @VisibleForTesting backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps)135 void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) { 136 final long timestamp = System.currentTimeMillis(); 137 final ArraySet<ApplicationInfo> applications = getInstalledApplications(); 138 if (applications == null || applications.isEmpty()) { 139 Log.w(TAG, "no data found in the getInstalledApplications()"); 140 return; 141 } 142 int backupCount = 0; 143 final StringBuilder builder = new StringBuilder(); 144 final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); 145 // Converts application into the AppUsageState. 146 for (ApplicationInfo info : applications) { 147 final int mode = appOps.checkOpNoThrow( 148 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName); 149 @BatteryOptimizeUtils.OptimizationMode 150 final int optimizationMode = BatteryOptimizeUtils.getAppOptimizationMode( 151 mode, allowlistedApps.contains(info.packageName)); 152 // Ignores default optimized/unknown state or system/default apps. 153 if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED 154 || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN 155 || isSystemOrDefaultApp(info.packageName)) { 156 continue; 157 } 158 final String packageOptimizeMode = 159 info.packageName + DELIMITER_MODE + optimizationMode; 160 builder.append(packageOptimizeMode + DELIMITER); 161 debugLog(packageOptimizeMode); 162 backupCount++; 163 } 164 165 writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString()); 166 Log.d(TAG, String.format("backup getInstalledApplications():%d count=%d in %d/ms", 167 applications.size(), backupCount, (System.currentTimeMillis() - timestamp))); 168 } 169 170 @VisibleForTesting restoreOptimizationMode(byte[] dataBytes)171 void restoreOptimizationMode(byte[] dataBytes) { 172 final long timestamp = System.currentTimeMillis(); 173 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 174 if (dataContent == null || dataContent.isEmpty()) { 175 Log.w(TAG, "no data found in the restoreOptimizationMode()"); 176 return; 177 } 178 final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER); 179 if (appConfigurations == null || appConfigurations.length == 0) { 180 Log.w(TAG, "no data found from the split() processing"); 181 return; 182 } 183 int restoreCount = 0; 184 for (int index = 0; index < appConfigurations.length; index++) { 185 final String[] results = appConfigurations[index] 186 .split(BatteryBackupHelper.DELIMITER_MODE); 187 // Example format: com.android.systemui:2 we should have length=2 188 if (results == null || results.length != 2) { 189 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]); 190 continue; 191 } 192 final String packageName = results[0]; 193 // Ignores system/default apps. 194 if (isSystemOrDefaultApp(packageName)) { 195 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName); 196 continue; 197 } 198 @BatteryOptimizeUtils.OptimizationMode 199 int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN; 200 try { 201 optimizationMode = Integer.parseInt(results[1]); 202 } catch (NumberFormatException e) { 203 Log.e(TAG, "failed to parse the optimization mode: " 204 + appConfigurations[index], e); 205 continue; 206 } 207 restoreOptimizationMode(packageName, optimizationMode); 208 restoreCount++; 209 } 210 Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms", 211 restoreCount, (System.currentTimeMillis() - timestamp))); 212 } 213 restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)214 private void restoreOptimizationMode( 215 String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) { 216 final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName); 217 if (uid == BatteryUtils.UID_NULL) { 218 return; 219 } 220 final BatteryOptimizeUtils batteryOptimizeUtils = 221 mBatteryOptimizeUtils != null 222 ? mBatteryOptimizeUtils /*testing only*/ 223 : new BatteryOptimizeUtils(mContext, uid, packageName); 224 batteryOptimizeUtils.setAppUsageState(mode); 225 Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode)); 226 } 227 228 // Provides an opportunity to inject mock IDeviceIdleController for testing. getIDeviceIdleController()229 private IDeviceIdleController getIDeviceIdleController() { 230 if (mIDeviceIdleController != null) { 231 return mIDeviceIdleController; 232 } 233 mIDeviceIdleController = IDeviceIdleController.Stub.asInterface( 234 ServiceManager.getService(DEVICE_IDLE_SERVICE)); 235 return mIDeviceIdleController; 236 } 237 getIPackageManager()238 private IPackageManager getIPackageManager() { 239 if (mIPackageManager != null) { 240 return mIPackageManager; 241 } 242 mIPackageManager = AppGlobals.getPackageManager(); 243 return mIPackageManager; 244 } 245 getPowerAllowlistBackend()246 private PowerAllowlistBackend getPowerAllowlistBackend() { 247 if (mPowerAllowlistBackend != null) { 248 return mPowerAllowlistBackend; 249 } 250 mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext); 251 return mPowerAllowlistBackend; 252 } 253 isSystemOrDefaultApp(String packageName)254 private boolean isSystemOrDefaultApp(String packageName) { 255 final PowerAllowlistBackend powerAllowlistBackend = getPowerAllowlistBackend(); 256 return powerAllowlistBackend.isSysAllowlisted(packageName) 257 || powerAllowlistBackend.isDefaultActiveApp(packageName); 258 } 259 getInstalledApplications()260 private ArraySet<ApplicationInfo> getInstalledApplications() { 261 if (mTestApplicationInfoList != null) { 262 return mTestApplicationInfoList; 263 } 264 return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager()); 265 } 266 debugLog(String debugContent)267 private void debugLog(String debugContent) { 268 if (DEBUG) Log.d(TAG, debugContent); 269 } 270 writeBackupData( BackupDataOutput data, String dataKey, String dataContent)271 private static void writeBackupData( 272 BackupDataOutput data, String dataKey, String dataContent) { 273 final byte[] dataContentBytes = dataContent.getBytes(); 274 try { 275 data.writeEntityHeader(dataKey, dataContentBytes.length); 276 data.writeEntityData(dataContentBytes, dataContentBytes.length); 277 } catch (IOException e) { 278 Log.e(TAG, "writeBackupData() is failed for " + dataKey, e); 279 } 280 } 281 isOwner()282 private static boolean isOwner() { 283 return UserHandle.myUserId() == UserHandle.USER_OWNER; 284 } 285 } 286