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.SharedPreferences; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.IPackageManager; 28 import android.os.Build; 29 import android.os.IDeviceIdleController; 30 import android.os.ParcelFileDescriptor; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.UserHandle; 34 import android.util.ArrayMap; 35 import android.util.ArraySet; 36 import android.util.Log; 37 38 import androidx.annotation.VisibleForTesting; 39 40 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; 41 import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; 42 import com.android.settings.fuelgauge.batteryusage.AppOptimizationModeEvent; 43 import com.android.settings.overlay.FeatureFactory; 44 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 45 46 import java.io.IOException; 47 import java.io.PrintWriter; 48 import java.nio.charset.StandardCharsets; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.stream.Collectors; 54 55 /** An implementation to backup and restore battery configurations. */ 56 public final class BatteryBackupHelper implements BackupHelper { 57 /** An inditifier for {@link BackupHelper}. */ 58 public static final String TAG = "BatteryBackupHelper"; 59 60 // Definition for the device build information. 61 public static final String KEY_BUILD_BRAND = "device_build_brand"; 62 public static final String KEY_BUILD_PRODUCT = "device_build_product"; 63 public static final String KEY_BUILD_MANUFACTURER = "device_build_manufacture"; 64 public static final String KEY_BUILD_FINGERPRINT = "device_build_fingerprint"; 65 // Customized fields for device extra information. 66 public static final String KEY_BUILD_METADATA_1 = "device_build_metadata_1"; 67 public static final String KEY_BUILD_METADATA_2 = "device_build_metadata_2"; 68 69 private static final String DEVICE_IDLE_SERVICE = "deviceidle"; 70 private static final String BATTERY_OPTIMIZE_BACKUP_FILE_NAME = 71 "battery_optimize_backup_historical_logs"; 72 private static final int DEVICE_BUILD_INFO_SIZE = 6; 73 74 static final String DELIMITER = ","; 75 static final String DELIMITER_MODE = ":"; 76 static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list"; 77 78 @VisibleForTesting ArraySet<ApplicationInfo> mTestApplicationInfoList = null; 79 80 @VisibleForTesting PowerAllowlistBackend mPowerAllowlistBackend; 81 @VisibleForTesting IDeviceIdleController mIDeviceIdleController; 82 @VisibleForTesting IPackageManager mIPackageManager; 83 @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils; 84 85 private byte[] mOptimizationModeBytes; 86 private boolean mVerifyMigrateConfiguration = false; 87 88 private final Context mContext; 89 // Device information map from the restoreEntity() method. 90 private final ArrayMap<String, String> mDeviceBuildInfoMap = 91 new ArrayMap<>(DEVICE_BUILD_INFO_SIZE); 92 BatteryBackupHelper(Context context)93 public BatteryBackupHelper(Context context) { 94 mContext = context.getApplicationContext(); 95 } 96 97 @Override performBackup( ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)98 public void performBackup( 99 ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { 100 if (!isOwner() || data == null) { 101 Log.w(TAG, "ignore performBackup() for non-owner or empty data"); 102 return; 103 } 104 final List<String> allowlistedApps = getFullPowerList(); 105 if (allowlistedApps == null) { 106 return; 107 } 108 109 writeBackupData(data, KEY_BUILD_BRAND, Build.BRAND); 110 writeBackupData(data, KEY_BUILD_PRODUCT, Build.PRODUCT); 111 writeBackupData(data, KEY_BUILD_MANUFACTURER, Build.MANUFACTURER); 112 writeBackupData(data, KEY_BUILD_FINGERPRINT, Build.FINGERPRINT); 113 // Add customized device build metadata fields. 114 final PowerUsageFeatureProvider provider = 115 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); 116 writeBackupData(data, KEY_BUILD_METADATA_1, provider.getBuildMetadata1(mContext)); 117 writeBackupData(data, KEY_BUILD_METADATA_2, provider.getBuildMetadata2(mContext)); 118 119 backupOptimizationMode(data, allowlistedApps); 120 } 121 122 @Override restoreEntity(BackupDataInputStream data)123 public void restoreEntity(BackupDataInputStream data) { 124 // Ensure we only verify the migrate configuration one time. 125 if (!mVerifyMigrateConfiguration) { 126 mVerifyMigrateConfiguration = true; 127 BatterySettingsMigrateChecker.verifySaverConfiguration(mContext); 128 } 129 if (!isOwner() || data == null || data.size() == 0) { 130 Log.w(TAG, "ignore restoreEntity() for non-owner or empty data"); 131 return; 132 } 133 final String dataKey = data.getKey(); 134 switch (dataKey) { 135 case KEY_BUILD_BRAND: 136 case KEY_BUILD_PRODUCT: 137 case KEY_BUILD_MANUFACTURER: 138 case KEY_BUILD_FINGERPRINT: 139 case KEY_BUILD_METADATA_1: 140 case KEY_BUILD_METADATA_2: 141 restoreBackupData(dataKey, data); 142 break; 143 case KEY_OPTIMIZATION_LIST: 144 // Hold the optimization mode data until all conditions are matched. 145 mOptimizationModeBytes = getBackupData(dataKey, data); 146 break; 147 } 148 performRestoreIfNeeded(); 149 } 150 151 @Override writeNewStateDescription(ParcelFileDescriptor newState)152 public void writeNewStateDescription(ParcelFileDescriptor newState) {} 153 getFullPowerList()154 private List<String> getFullPowerList() { 155 final long timestamp = System.currentTimeMillis(); 156 String[] allowlistedApps; 157 try { 158 allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist(); 159 } catch (RemoteException e) { 160 Log.e(TAG, "backupFullPowerList() failed", e); 161 return null; 162 } 163 // Ignores unexpected empty result case. 164 if (allowlistedApps == null || allowlistedApps.length == 0) { 165 Log.w(TAG, "no data found in the getFullPowerList()"); 166 return new ArrayList<>(); 167 } 168 Log.d( 169 TAG, 170 String.format( 171 "getFullPowerList() size=%d in %d/ms", 172 allowlistedApps.length, (System.currentTimeMillis() - timestamp))); 173 return Arrays.asList(allowlistedApps); 174 } 175 176 @VisibleForTesting backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps)177 void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) { 178 final long timestamp = System.currentTimeMillis(); 179 final ArraySet<ApplicationInfo> applications = getInstalledApplications(); 180 if (applications == null || applications.isEmpty()) { 181 Log.w(TAG, "no data found in the getInstalledApplications()"); 182 return; 183 } 184 int backupCount = 0; 185 final StringBuilder builder = new StringBuilder(); 186 final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); 187 final SharedPreferences sharedPreferences = getSharedPreferences(mContext); 188 final Map<Integer, AppOptimizationModeEvent> appOptModeMap = 189 AppOptModeSharedPreferencesUtils.getAllEvents(mContext).stream() 190 .collect(Collectors.toMap(AppOptimizationModeEvent::getUid, e -> e)); 191 // Converts application into the AppUsageState. 192 for (ApplicationInfo info : applications) { 193 final int mode = BatteryOptimizeUtils.getMode(appOps, info.uid, info.packageName); 194 @BatteryOptimizeUtils.OptimizationMode 195 final int optimizationMode = 196 appOptModeMap.containsKey(info.uid) 197 ? (int) appOptModeMap.get(info.uid).getResetOptimizationMode() 198 : BatteryOptimizeUtils.getAppOptimizationMode( 199 mode, 200 allowlistedApps.contains(info.packageName), 201 /* ignoreUnknownMode= */ false); 202 // Ignores default optimized/unknown state or system/default apps. 203 if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED 204 || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN 205 || isSystemOrDefaultApp(info.packageName, info.uid)) { 206 continue; 207 } 208 final String packageOptimizeMode = info.packageName + DELIMITER_MODE + optimizationMode; 209 builder.append(packageOptimizeMode + DELIMITER); 210 Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode); 211 BatteryOptimizeLogUtils.writeLog( 212 sharedPreferences, 213 Action.BACKUP, 214 info.packageName, 215 /* actionDescription */ "mode: " + optimizationMode); 216 backupCount++; 217 } 218 219 writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString()); 220 Log.d( 221 TAG, 222 String.format( 223 "backup getInstalledApplications():%d count=%d in %d/ms", 224 applications.size(), 225 backupCount, 226 (System.currentTimeMillis() - timestamp))); 227 } 228 229 @VisibleForTesting restoreOptimizationMode(byte[] dataBytes)230 int restoreOptimizationMode(byte[] dataBytes) { 231 final long timestamp = System.currentTimeMillis(); 232 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 233 if (dataContent == null || dataContent.isEmpty()) { 234 Log.w(TAG, "no data found in the restoreOptimizationMode()"); 235 return 0; 236 } 237 final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER); 238 if (appConfigurations == null || appConfigurations.length == 0) { 239 Log.w(TAG, "no data found from the split() processing"); 240 return 0; 241 } 242 int restoreCount = 0; 243 for (int index = 0; index < appConfigurations.length; index++) { 244 final String[] results = 245 appConfigurations[index].split(BatteryBackupHelper.DELIMITER_MODE); 246 // Example format: com.android.systemui:2 we should have length=2 247 if (results == null || results.length != 2) { 248 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]); 249 continue; 250 } 251 final String packageName = results[0]; 252 final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName); 253 // Ignores system/default apps. 254 if (isSystemOrDefaultApp(packageName, uid)) { 255 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName); 256 continue; 257 } 258 @BatteryOptimizeUtils.OptimizationMode 259 int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN; 260 try { 261 optimizationMode = Integer.parseInt(results[1]); 262 } catch (NumberFormatException e) { 263 Log.e(TAG, "failed to parse the optimization mode: " + appConfigurations[index], e); 264 continue; 265 } 266 restoreOptimizationMode(packageName, optimizationMode); 267 restoreCount++; 268 } 269 Log.d( 270 TAG, 271 String.format( 272 "restoreOptimizationMode() count=%d in %d/ms", 273 restoreCount, (System.currentTimeMillis() - timestamp))); 274 return restoreCount; 275 } 276 performRestoreIfNeeded()277 private void performRestoreIfNeeded() { 278 if (mOptimizationModeBytes == null || mOptimizationModeBytes.length == 0) { 279 return; 280 } 281 final PowerUsageFeatureProvider provider = 282 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); 283 if (!provider.isValidToRestoreOptimizationMode(mDeviceBuildInfoMap)) { 284 return; 285 } 286 // Start to restore the app optimization mode data. 287 final int restoreCount = restoreOptimizationMode(mOptimizationModeBytes); 288 if (restoreCount > 0) { 289 BatterySettingsMigrateChecker.verifyBatteryOptimizeModes(mContext); 290 } 291 mOptimizationModeBytes = null; // clear data 292 } 293 294 /** Dump the app optimization mode backup history data. */ dumpHistoricalData(Context context, PrintWriter writer)295 public static void dumpHistoricalData(Context context, PrintWriter writer) { 296 BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog( 297 getSharedPreferences(context), writer); 298 } 299 isOwner()300 static boolean isOwner() { 301 return UserHandle.myUserId() == UserHandle.USER_SYSTEM; 302 } 303 newBatteryOptimizeUtils( Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils)304 static BatteryOptimizeUtils newBatteryOptimizeUtils( 305 Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils) { 306 final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName); 307 if (uid == BatteryUtils.UID_NULL) { 308 return null; 309 } 310 final BatteryOptimizeUtils batteryOptimizeUtils = 311 testOptimizeUtils != null 312 ? testOptimizeUtils /*testing only*/ 313 : new BatteryOptimizeUtils(context, uid, packageName); 314 return batteryOptimizeUtils; 315 } 316 317 @VisibleForTesting getSharedPreferences(Context context)318 static SharedPreferences getSharedPreferences(Context context) { 319 return context.getSharedPreferences( 320 BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE); 321 } 322 restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)323 private void restoreOptimizationMode( 324 String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) { 325 final BatteryOptimizeUtils batteryOptimizeUtils = 326 newBatteryOptimizeUtils(mContext, packageName, mBatteryOptimizeUtils); 327 if (batteryOptimizeUtils == null) { 328 return; 329 } 330 batteryOptimizeUtils.setAppUsageState( 331 mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE); 332 Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode)); 333 } 334 335 // Provides an opportunity to inject mock IDeviceIdleController for testing. getIDeviceIdleController()336 private IDeviceIdleController getIDeviceIdleController() { 337 if (mIDeviceIdleController != null) { 338 return mIDeviceIdleController; 339 } 340 mIDeviceIdleController = 341 IDeviceIdleController.Stub.asInterface( 342 ServiceManager.getService(DEVICE_IDLE_SERVICE)); 343 return mIDeviceIdleController; 344 } 345 getIPackageManager()346 private IPackageManager getIPackageManager() { 347 if (mIPackageManager != null) { 348 return mIPackageManager; 349 } 350 mIPackageManager = AppGlobals.getPackageManager(); 351 return mIPackageManager; 352 } 353 getPowerAllowlistBackend()354 private PowerAllowlistBackend getPowerAllowlistBackend() { 355 if (mPowerAllowlistBackend != null) { 356 return mPowerAllowlistBackend; 357 } 358 mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext); 359 return mPowerAllowlistBackend; 360 } 361 isSystemOrDefaultApp(String packageName, int uid)362 private boolean isSystemOrDefaultApp(String packageName, int uid) { 363 return BatteryOptimizeUtils.isSystemOrDefaultApp( 364 mContext, getPowerAllowlistBackend(), packageName, uid); 365 } 366 getInstalledApplications()367 private ArraySet<ApplicationInfo> getInstalledApplications() { 368 if (mTestApplicationInfoList != null) { 369 return mTestApplicationInfoList; 370 } 371 return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager()); 372 } 373 restoreBackupData(String dataKey, BackupDataInputStream data)374 private void restoreBackupData(String dataKey, BackupDataInputStream data) { 375 final byte[] dataBytes = getBackupData(dataKey, data); 376 if (dataBytes == null || dataBytes.length == 0) { 377 return; 378 } 379 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 380 mDeviceBuildInfoMap.put(dataKey, dataContent); 381 Log.d(TAG, String.format("restore:%s:%s", dataKey, dataContent)); 382 } 383 getBackupData(String dataKey, BackupDataInputStream data)384 private static byte[] getBackupData(String dataKey, BackupDataInputStream data) { 385 final int dataSize = data.size(); 386 final byte[] dataBytes = new byte[dataSize]; 387 try { 388 data.read(dataBytes, 0 /*offset*/, dataSize); 389 } catch (IOException e) { 390 Log.e(TAG, "failed to getBackupData() " + dataKey, e); 391 return null; 392 } 393 return dataBytes; 394 } 395 writeBackupData(BackupDataOutput data, String dataKey, String dataContent)396 private static void writeBackupData(BackupDataOutput data, String dataKey, String dataContent) { 397 if (dataContent == null || dataContent.isEmpty()) { 398 return; 399 } 400 final byte[] dataContentBytes = dataContent.getBytes(); 401 try { 402 data.writeEntityHeader(dataKey, dataContentBytes.length); 403 data.writeEntityData(dataContentBytes, dataContentBytes.length); 404 } catch (IOException e) { 405 Log.e(TAG, "writeBackupData() is failed for " + dataKey, e); 406 } 407 Log.d(TAG, String.format("backup:%s:%s", dataKey, dataContent)); 408 } 409 } 410