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, allowlistedApps.contains(info.packageName)); 200 // Ignores default optimized/unknown state or system/default apps. 201 if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED 202 || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN 203 || isSystemOrDefaultApp(info.packageName, info.uid)) { 204 continue; 205 } 206 final String packageOptimizeMode = info.packageName + DELIMITER_MODE + optimizationMode; 207 builder.append(packageOptimizeMode + DELIMITER); 208 Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode); 209 BatteryOptimizeLogUtils.writeLog( 210 sharedPreferences, 211 Action.BACKUP, 212 info.packageName, 213 /* actionDescription */ "mode: " + optimizationMode); 214 backupCount++; 215 } 216 217 writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString()); 218 Log.d( 219 TAG, 220 String.format( 221 "backup getInstalledApplications():%d count=%d in %d/ms", 222 applications.size(), 223 backupCount, 224 (System.currentTimeMillis() - timestamp))); 225 } 226 227 @VisibleForTesting restoreOptimizationMode(byte[] dataBytes)228 int restoreOptimizationMode(byte[] dataBytes) { 229 final long timestamp = System.currentTimeMillis(); 230 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 231 if (dataContent == null || dataContent.isEmpty()) { 232 Log.w(TAG, "no data found in the restoreOptimizationMode()"); 233 return 0; 234 } 235 final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER); 236 if (appConfigurations == null || appConfigurations.length == 0) { 237 Log.w(TAG, "no data found from the split() processing"); 238 return 0; 239 } 240 int restoreCount = 0; 241 for (int index = 0; index < appConfigurations.length; index++) { 242 final String[] results = 243 appConfigurations[index].split(BatteryBackupHelper.DELIMITER_MODE); 244 // Example format: com.android.systemui:2 we should have length=2 245 if (results == null || results.length != 2) { 246 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]); 247 continue; 248 } 249 final String packageName = results[0]; 250 final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName); 251 // Ignores system/default apps. 252 if (isSystemOrDefaultApp(packageName, uid)) { 253 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName); 254 continue; 255 } 256 @BatteryOptimizeUtils.OptimizationMode 257 int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN; 258 try { 259 optimizationMode = Integer.parseInt(results[1]); 260 } catch (NumberFormatException e) { 261 Log.e(TAG, "failed to parse the optimization mode: " + appConfigurations[index], e); 262 continue; 263 } 264 restoreOptimizationMode(packageName, optimizationMode); 265 restoreCount++; 266 } 267 Log.d( 268 TAG, 269 String.format( 270 "restoreOptimizationMode() count=%d in %d/ms", 271 restoreCount, (System.currentTimeMillis() - timestamp))); 272 return restoreCount; 273 } 274 performRestoreIfNeeded()275 private void performRestoreIfNeeded() { 276 if (mOptimizationModeBytes == null || mOptimizationModeBytes.length == 0) { 277 return; 278 } 279 final PowerUsageFeatureProvider provider = 280 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); 281 if (!provider.isValidToRestoreOptimizationMode(mDeviceBuildInfoMap)) { 282 return; 283 } 284 // Start to restore the app optimization mode data. 285 final int restoreCount = restoreOptimizationMode(mOptimizationModeBytes); 286 if (restoreCount > 0) { 287 BatterySettingsMigrateChecker.verifyBatteryOptimizeModes(mContext); 288 } 289 mOptimizationModeBytes = null; // clear data 290 } 291 292 /** Dump the app optimization mode backup history data. */ dumpHistoricalData(Context context, PrintWriter writer)293 public static void dumpHistoricalData(Context context, PrintWriter writer) { 294 BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog( 295 getSharedPreferences(context), writer); 296 } 297 isOwner()298 static boolean isOwner() { 299 return UserHandle.myUserId() == UserHandle.USER_SYSTEM; 300 } 301 newBatteryOptimizeUtils( Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils)302 static BatteryOptimizeUtils newBatteryOptimizeUtils( 303 Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils) { 304 final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName); 305 if (uid == BatteryUtils.UID_NULL) { 306 return null; 307 } 308 final BatteryOptimizeUtils batteryOptimizeUtils = 309 testOptimizeUtils != null 310 ? testOptimizeUtils /*testing only*/ 311 : new BatteryOptimizeUtils(context, uid, packageName); 312 return batteryOptimizeUtils; 313 } 314 315 @VisibleForTesting getSharedPreferences(Context context)316 static SharedPreferences getSharedPreferences(Context context) { 317 return context.getSharedPreferences( 318 BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE); 319 } 320 restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)321 private void restoreOptimizationMode( 322 String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) { 323 final BatteryOptimizeUtils batteryOptimizeUtils = 324 newBatteryOptimizeUtils(mContext, packageName, mBatteryOptimizeUtils); 325 if (batteryOptimizeUtils == null) { 326 return; 327 } 328 batteryOptimizeUtils.setAppUsageState( 329 mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE); 330 Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode)); 331 } 332 333 // Provides an opportunity to inject mock IDeviceIdleController for testing. getIDeviceIdleController()334 private IDeviceIdleController getIDeviceIdleController() { 335 if (mIDeviceIdleController != null) { 336 return mIDeviceIdleController; 337 } 338 mIDeviceIdleController = 339 IDeviceIdleController.Stub.asInterface( 340 ServiceManager.getService(DEVICE_IDLE_SERVICE)); 341 return mIDeviceIdleController; 342 } 343 getIPackageManager()344 private IPackageManager getIPackageManager() { 345 if (mIPackageManager != null) { 346 return mIPackageManager; 347 } 348 mIPackageManager = AppGlobals.getPackageManager(); 349 return mIPackageManager; 350 } 351 getPowerAllowlistBackend()352 private PowerAllowlistBackend getPowerAllowlistBackend() { 353 if (mPowerAllowlistBackend != null) { 354 return mPowerAllowlistBackend; 355 } 356 mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext); 357 return mPowerAllowlistBackend; 358 } 359 isSystemOrDefaultApp(String packageName, int uid)360 private boolean isSystemOrDefaultApp(String packageName, int uid) { 361 return BatteryOptimizeUtils.isSystemOrDefaultApp( 362 mContext, getPowerAllowlistBackend(), packageName, uid); 363 } 364 getInstalledApplications()365 private ArraySet<ApplicationInfo> getInstalledApplications() { 366 if (mTestApplicationInfoList != null) { 367 return mTestApplicationInfoList; 368 } 369 return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager()); 370 } 371 restoreBackupData(String dataKey, BackupDataInputStream data)372 private void restoreBackupData(String dataKey, BackupDataInputStream data) { 373 final byte[] dataBytes = getBackupData(dataKey, data); 374 if (dataBytes == null || dataBytes.length == 0) { 375 return; 376 } 377 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 378 mDeviceBuildInfoMap.put(dataKey, dataContent); 379 Log.d(TAG, String.format("restore:%s:%s", dataKey, dataContent)); 380 } 381 getBackupData(String dataKey, BackupDataInputStream data)382 private static byte[] getBackupData(String dataKey, BackupDataInputStream data) { 383 final int dataSize = data.size(); 384 final byte[] dataBytes = new byte[dataSize]; 385 try { 386 data.read(dataBytes, 0 /*offset*/, dataSize); 387 } catch (IOException e) { 388 Log.e(TAG, "failed to getBackupData() " + dataKey, e); 389 return null; 390 } 391 return dataBytes; 392 } 393 writeBackupData(BackupDataOutput data, String dataKey, String dataContent)394 private static void writeBackupData(BackupDataOutput data, String dataKey, String dataContent) { 395 if (dataContent == null || dataContent.isEmpty()) { 396 return; 397 } 398 final byte[] dataContentBytes = dataContent.getBytes(); 399 try { 400 data.writeEntityHeader(dataKey, dataContentBytes.length); 401 data.writeEntityData(dataContentBytes, dataContentBytes.length); 402 } catch (IOException e) { 403 Log.e(TAG, "writeBackupData() is failed for " + dataKey, e); 404 } 405 Log.d(TAG, String.format("backup:%s:%s", dataKey, dataContent)); 406 } 407 } 408