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.overlay.FeatureFactory; 42 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 43 44 import java.io.IOException; 45 import java.io.PrintWriter; 46 import java.nio.charset.StandardCharsets; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 51 /** An implementation to backup and restore battery configurations. */ 52 public final class BatteryBackupHelper implements BackupHelper { 53 /** An inditifier for {@link BackupHelper}. */ 54 public static final String TAG = "BatteryBackupHelper"; 55 // Definition for the device build information. 56 public static final String KEY_BUILD_BRAND = "device_build_brand"; 57 public static final String KEY_BUILD_PRODUCT = "device_build_product"; 58 public static final String KEY_BUILD_MANUFACTURER = "device_build_manufacture"; 59 public static final String KEY_BUILD_FINGERPRINT = "device_build_fingerprint"; 60 // Customized fields for device extra information. 61 public static final String KEY_BUILD_METADATA_1 = "device_build_metadata_1"; 62 public static final String KEY_BUILD_METADATA_2 = "device_build_metadata_2"; 63 64 private static final String DEVICE_IDLE_SERVICE = "deviceidle"; 65 private static final String BATTERY_OPTIMIZE_BACKUP_FILE_NAME = 66 "battery_optimize_backup_historical_logs"; 67 private static final int DEVICE_BUILD_INFO_SIZE = 6; 68 69 static final String DELIMITER = ","; 70 static final String DELIMITER_MODE = ":"; 71 static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list"; 72 73 @VisibleForTesting 74 ArraySet<ApplicationInfo> mTestApplicationInfoList = null; 75 76 @VisibleForTesting 77 PowerAllowlistBackend mPowerAllowlistBackend; 78 @VisibleForTesting 79 IDeviceIdleController mIDeviceIdleController; 80 @VisibleForTesting 81 IPackageManager mIPackageManager; 82 @VisibleForTesting 83 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(ParcelFileDescriptor oldState, BackupDataOutput data, 99 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 = FeatureFactory.getFactory(mContext) 115 .getPowerUsageFeatureProvider(mContext); 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 } 154 getFullPowerList()155 private List<String> getFullPowerList() { 156 final long timestamp = System.currentTimeMillis(); 157 String[] allowlistedApps; 158 try { 159 allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist(); 160 } catch (RemoteException e) { 161 Log.e(TAG, "backupFullPowerList() failed", e); 162 return null; 163 } 164 // Ignores unexpected empty result case. 165 if (allowlistedApps == null || allowlistedApps.length == 0) { 166 Log.w(TAG, "no data found in the getFullPowerList()"); 167 return new ArrayList<>(); 168 } 169 Log.d(TAG, String.format("getFullPowerList() size=%d in %d/ms", 170 allowlistedApps.length, (System.currentTimeMillis() - timestamp))); 171 return Arrays.asList(allowlistedApps); 172 } 173 174 @VisibleForTesting backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps)175 void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) { 176 final long timestamp = System.currentTimeMillis(); 177 final ArraySet<ApplicationInfo> applications = getInstalledApplications(); 178 if (applications == null || applications.isEmpty()) { 179 Log.w(TAG, "no data found in the getInstalledApplications()"); 180 return; 181 } 182 int backupCount = 0; 183 final StringBuilder builder = new StringBuilder(); 184 final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); 185 final SharedPreferences sharedPreferences = getSharedPreferences(mContext); 186 // Converts application into the AppUsageState. 187 for (ApplicationInfo info : applications) { 188 final int mode = BatteryOptimizeUtils.getMode(appOps, info.uid, info.packageName); 189 @BatteryOptimizeUtils.OptimizationMode 190 final int optimizationMode = BatteryOptimizeUtils.getAppOptimizationMode( 191 mode, allowlistedApps.contains(info.packageName)); 192 // Ignores default optimized/unknown state or system/default apps. 193 if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED 194 || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN 195 || isSystemOrDefaultApp(info.packageName, info.uid)) { 196 continue; 197 } 198 final String packageOptimizeMode = 199 info.packageName + DELIMITER_MODE + optimizationMode; 200 builder.append(packageOptimizeMode + DELIMITER); 201 Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode); 202 BatteryOptimizeLogUtils.writeLog( 203 sharedPreferences, Action.BACKUP, info.packageName, 204 /* actionDescription */ "mode: " + optimizationMode); 205 backupCount++; 206 } 207 208 writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString()); 209 Log.d(TAG, String.format("backup getInstalledApplications():%d count=%d in %d/ms", 210 applications.size(), backupCount, (System.currentTimeMillis() - timestamp))); 211 } 212 213 @VisibleForTesting restoreOptimizationMode(byte[] dataBytes)214 int restoreOptimizationMode(byte[] dataBytes) { 215 final long timestamp = System.currentTimeMillis(); 216 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 217 if (dataContent == null || dataContent.isEmpty()) { 218 Log.w(TAG, "no data found in the restoreOptimizationMode()"); 219 return 0; 220 } 221 final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER); 222 if (appConfigurations == null || appConfigurations.length == 0) { 223 Log.w(TAG, "no data found from the split() processing"); 224 return 0; 225 } 226 int restoreCount = 0; 227 for (int index = 0; index < appConfigurations.length; index++) { 228 final String[] results = appConfigurations[index] 229 .split(BatteryBackupHelper.DELIMITER_MODE); 230 // Example format: com.android.systemui:2 we should have length=2 231 if (results == null || results.length != 2) { 232 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]); 233 continue; 234 } 235 final String packageName = results[0]; 236 final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName); 237 // Ignores system/default apps. 238 if (isSystemOrDefaultApp(packageName, uid)) { 239 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName); 240 continue; 241 } 242 @BatteryOptimizeUtils.OptimizationMode 243 int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN; 244 try { 245 optimizationMode = Integer.parseInt(results[1]); 246 } catch (NumberFormatException e) { 247 Log.e(TAG, "failed to parse the optimization mode: " 248 + appConfigurations[index], e); 249 continue; 250 } 251 restoreOptimizationMode(packageName, optimizationMode); 252 restoreCount++; 253 } 254 Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms", 255 restoreCount, (System.currentTimeMillis() - timestamp))); 256 return restoreCount; 257 } 258 performRestoreIfNeeded()259 private void performRestoreIfNeeded() { 260 if (mOptimizationModeBytes == null || mOptimizationModeBytes.length == 0) { 261 return; 262 } 263 final PowerUsageFeatureProvider provider = FeatureFactory.getFactory(mContext) 264 .getPowerUsageFeatureProvider(mContext); 265 if (!provider.isValidToRestoreOptimizationMode(mDeviceBuildInfoMap)) { 266 return; 267 } 268 // Start to restore the app optimization mode data. 269 final int restoreCount = restoreOptimizationMode(mOptimizationModeBytes); 270 if (restoreCount > 0) { 271 BatterySettingsMigrateChecker.verifyOptimizationModes(mContext); 272 } 273 mOptimizationModeBytes = null; // clear data 274 } 275 276 /** Dump the app optimization mode backup history data. */ dumpHistoricalData(Context context, PrintWriter writer)277 public static void dumpHistoricalData(Context context, PrintWriter writer) { 278 BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog( 279 getSharedPreferences(context), writer); 280 } 281 isOwner()282 static boolean isOwner() { 283 return UserHandle.myUserId() == UserHandle.USER_SYSTEM; 284 } 285 newBatteryOptimizeUtils( Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils)286 static BatteryOptimizeUtils newBatteryOptimizeUtils( 287 Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils) { 288 final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName); 289 if (uid == BatteryUtils.UID_NULL) { 290 return null; 291 } 292 final BatteryOptimizeUtils batteryOptimizeUtils = 293 testOptimizeUtils != null 294 ? testOptimizeUtils /*testing only*/ 295 : new BatteryOptimizeUtils(context, uid, packageName); 296 return batteryOptimizeUtils; 297 } 298 299 @VisibleForTesting getSharedPreferences(Context context)300 static SharedPreferences getSharedPreferences(Context context) { 301 return context.getSharedPreferences( 302 BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE); 303 } 304 restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)305 private void restoreOptimizationMode( 306 String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) { 307 final BatteryOptimizeUtils batteryOptimizeUtils = 308 newBatteryOptimizeUtils(mContext, packageName, mBatteryOptimizeUtils); 309 if (batteryOptimizeUtils == null) { 310 return; 311 } 312 batteryOptimizeUtils.setAppUsageState( 313 mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE); 314 Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode)); 315 } 316 317 // Provides an opportunity to inject mock IDeviceIdleController for testing. getIDeviceIdleController()318 private IDeviceIdleController getIDeviceIdleController() { 319 if (mIDeviceIdleController != null) { 320 return mIDeviceIdleController; 321 } 322 mIDeviceIdleController = IDeviceIdleController.Stub.asInterface( 323 ServiceManager.getService(DEVICE_IDLE_SERVICE)); 324 return mIDeviceIdleController; 325 } 326 getIPackageManager()327 private IPackageManager getIPackageManager() { 328 if (mIPackageManager != null) { 329 return mIPackageManager; 330 } 331 mIPackageManager = AppGlobals.getPackageManager(); 332 return mIPackageManager; 333 } 334 getPowerAllowlistBackend()335 private PowerAllowlistBackend getPowerAllowlistBackend() { 336 if (mPowerAllowlistBackend != null) { 337 return mPowerAllowlistBackend; 338 } 339 mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext); 340 return mPowerAllowlistBackend; 341 } 342 isSystemOrDefaultApp(String packageName, int uid)343 private boolean isSystemOrDefaultApp(String packageName, int uid) { 344 return BatteryOptimizeUtils.isSystemOrDefaultApp( 345 getPowerAllowlistBackend(), packageName, uid); 346 } 347 getInstalledApplications()348 private ArraySet<ApplicationInfo> getInstalledApplications() { 349 if (mTestApplicationInfoList != null) { 350 return mTestApplicationInfoList; 351 } 352 return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager()); 353 } 354 restoreBackupData(String dataKey, BackupDataInputStream data)355 private void restoreBackupData(String dataKey, BackupDataInputStream data) { 356 final byte[] dataBytes = getBackupData(dataKey, data); 357 if (dataBytes == null || dataBytes.length == 0) { 358 return; 359 } 360 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 361 mDeviceBuildInfoMap.put(dataKey, dataContent); 362 Log.d(TAG, String.format("restore:%s:%s", dataKey, dataContent)); 363 } 364 getBackupData(String dataKey, BackupDataInputStream data)365 private static byte[] getBackupData(String dataKey, BackupDataInputStream data) { 366 final int dataSize = data.size(); 367 final byte[] dataBytes = new byte[dataSize]; 368 try { 369 data.read(dataBytes, 0 /*offset*/, dataSize); 370 } catch (IOException e) { 371 Log.e(TAG, "failed to getBackupData() " + dataKey, e); 372 return null; 373 } 374 return dataBytes; 375 } 376 writeBackupData( BackupDataOutput data, String dataKey, String dataContent)377 private static void writeBackupData( 378 BackupDataOutput data, String dataKey, String dataContent) { 379 if (dataContent == null || dataContent.isEmpty()) { 380 return; 381 } 382 final byte[] dataContentBytes = dataContent.getBytes(); 383 try { 384 data.writeEntityHeader(dataKey, dataContentBytes.length); 385 data.writeEntityData(dataContentBytes, dataContentBytes.length); 386 } catch (IOException e) { 387 Log.e(TAG, "writeBackupData() is failed for " + dataKey, e); 388 } 389 Log.d(TAG, String.format("backup:%s:%s", dataKey, dataContent)); 390 } 391 } 392