1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.fuelgauge; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import android.app.AppGlobals; 22 import android.app.AppOpsManager; 23 import android.app.Application; 24 import android.content.Context; 25 import android.content.SharedPreferences; 26 import android.content.pm.ApplicationInfo; 27 import android.os.Build; 28 import android.os.IDeviceIdleController; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.os.UserHandle; 32 import android.util.ArrayMap; 33 import android.util.ArraySet; 34 import android.util.Log; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 39 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; 40 import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; 41 import com.android.settings.fuelgauge.batteryusage.AppOptimizationModeEvent; 42 import com.android.settings.overlay.FeatureFactory; 43 import com.android.settingslib.datastore.BackupCodec; 44 import com.android.settingslib.datastore.BackupContext; 45 import com.android.settingslib.datastore.BackupRestoreEntity; 46 import com.android.settingslib.datastore.BackupRestoreStorageManager; 47 import com.android.settingslib.datastore.EntityBackupResult; 48 import com.android.settingslib.datastore.ObservableBackupRestoreStorage; 49 import com.android.settingslib.datastore.RestoreContext; 50 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 51 52 import java.io.IOException; 53 import java.io.InputStream; 54 import java.io.OutputStream; 55 import java.util.Arrays; 56 import java.util.Collections; 57 import java.util.Comparator; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.stream.Collectors; 61 62 /** An implementation to backup and restore battery configurations. */ 63 public final class BatterySettingsStorage extends ObservableBackupRestoreStorage { 64 private static final String NAME = "BatteryBackupHelper"; 65 private static final String TAG = "BatterySettingsStorage"; 66 67 // Definition for the device build information. 68 public static final String KEY_BUILD_BRAND = "device_build_brand"; 69 public static final String KEY_BUILD_PRODUCT = "device_build_product"; 70 public static final String KEY_BUILD_MANUFACTURER = "device_build_manufacture"; 71 public static final String KEY_BUILD_FINGERPRINT = "device_build_fingerprint"; 72 // Customized fields for device extra information. 73 public static final String KEY_BUILD_METADATA_1 = "device_build_metadata_1"; 74 public static final String KEY_BUILD_METADATA_2 = "device_build_metadata_2"; 75 76 private static final String DEVICE_IDLE_SERVICE = "deviceidle"; 77 private static final String BATTERY_OPTIMIZE_BACKUP_FILE_NAME = 78 "battery_optimize_backup_historical_logs"; 79 private static final int DEVICE_BUILD_INFO_SIZE = 6; 80 81 static final String DELIMITER = ","; 82 static final String DELIMITER_MODE = ":"; 83 static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list"; 84 85 @Nullable private byte[] mOptimizationModeBytes; 86 87 private final Application mApplication; 88 // Device information map from restore. 89 private final ArrayMap<String, String> mDeviceBuildInfoMap = 90 new ArrayMap<>(DEVICE_BUILD_INFO_SIZE); 91 92 /** 93 * Returns the {@link BatterySettingsStorage} registered to {@link BackupRestoreStorageManager}. 94 */ get(@onNull Context context)95 public static @NonNull BatterySettingsStorage get(@NonNull Context context) { 96 return (BatterySettingsStorage) 97 BackupRestoreStorageManager.getInstance(context).getOrThrow(NAME); 98 } 99 BatterySettingsStorage(@onNull Context context)100 public BatterySettingsStorage(@NonNull Context context) { 101 mApplication = (Application) context.getApplicationContext(); 102 } 103 104 @NonNull 105 @Override getName()106 public String getName() { 107 return NAME; 108 } 109 110 @Override enableBackup(@onNull BackupContext backupContext)111 public boolean enableBackup(@NonNull BackupContext backupContext) { 112 return isOwner(); 113 } 114 115 @Override enableRestore()116 public boolean enableRestore() { 117 return isOwner(); 118 } 119 isOwner()120 static boolean isOwner() { 121 return UserHandle.myUserId() == UserHandle.USER_SYSTEM; 122 } 123 124 @NonNull 125 @Override createBackupRestoreEntities()126 public List<BackupRestoreEntity> createBackupRestoreEntities() { 127 List<String> allowlistedApps = getFullPowerList(); 128 if (allowlistedApps == null) { 129 return Collections.emptyList(); 130 } 131 PowerUsageFeatureProvider provider = 132 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); 133 return Arrays.asList( 134 new StringEntity(KEY_BUILD_BRAND, Build.BRAND), 135 new StringEntity(KEY_BUILD_PRODUCT, Build.PRODUCT), 136 new StringEntity(KEY_BUILD_MANUFACTURER, Build.MANUFACTURER), 137 new StringEntity(KEY_BUILD_FINGERPRINT, Build.FINGERPRINT), 138 new StringEntity(KEY_BUILD_METADATA_1, provider.getBuildMetadata1(mApplication)), 139 new StringEntity(KEY_BUILD_METADATA_2, provider.getBuildMetadata2(mApplication)), 140 new OptimizationModeEntity(allowlistedApps)); 141 } 142 getFullPowerList()143 private @Nullable List<String> getFullPowerList() { 144 final long timestamp = System.currentTimeMillis(); 145 String[] allowlistedApps; 146 try { 147 IDeviceIdleController deviceIdleController = 148 IDeviceIdleController.Stub.asInterface( 149 ServiceManager.getService(DEVICE_IDLE_SERVICE)); 150 allowlistedApps = deviceIdleController.getFullPowerWhitelist(); 151 } catch (RemoteException e) { 152 Log.e(TAG, "backupFullPowerList() failed", e); 153 return null; 154 } 155 // Ignores unexpected empty result case. 156 if (allowlistedApps == null || allowlistedApps.length == 0) { 157 Log.w(TAG, "no data found in the getFullPowerList()"); 158 return Collections.emptyList(); 159 } 160 Log.d( 161 TAG, 162 String.format( 163 "getFullPowerList() size=%d in %d/ms", 164 allowlistedApps.length, (System.currentTimeMillis() - timestamp))); 165 return Arrays.asList(allowlistedApps); 166 } 167 168 @NonNull 169 @Override wrapBackupOutputStream( @onNull BackupCodec codec, @NonNull OutputStream outputStream)170 public OutputStream wrapBackupOutputStream( 171 @NonNull BackupCodec codec, @NonNull OutputStream outputStream) { 172 // not using any codec for backward compatibility 173 return outputStream; 174 } 175 176 @NonNull 177 @Override wrapRestoreInputStream( @onNull BackupCodec codec, @NonNull InputStream inputStream)178 public InputStream wrapRestoreInputStream( 179 @NonNull BackupCodec codec, @NonNull InputStream inputStream) { 180 // not using any codec for backward compatibility 181 return inputStream; 182 } 183 184 @Override onRestoreFinished()185 public void onRestoreFinished() { 186 BatterySettingsMigrateChecker.verifySaverConfiguration(mApplication); 187 performRestoreIfNeeded(); 188 } 189 performRestoreIfNeeded()190 private void performRestoreIfNeeded() { 191 byte[] bytes = mOptimizationModeBytes; 192 mOptimizationModeBytes = null; // clear data 193 if (bytes == null || bytes.length == 0) { 194 return; 195 } 196 final PowerUsageFeatureProvider provider = 197 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); 198 if (!provider.isValidToRestoreOptimizationMode(mDeviceBuildInfoMap)) { 199 return; 200 } 201 // Start to restore the app optimization mode data. 202 final int restoreCount = restoreOptimizationMode(bytes); 203 if (restoreCount > 0) { 204 BatterySettingsMigrateChecker.verifyBatteryOptimizeModes(mApplication); 205 } 206 } 207 restoreOptimizationMode(byte[] dataBytes)208 int restoreOptimizationMode(byte[] dataBytes) { 209 final long timestamp = System.currentTimeMillis(); 210 final String dataContent = new String(dataBytes, UTF_8); 211 if (dataContent.isEmpty()) { 212 Log.w(TAG, "no data found in the restoreOptimizationMode()"); 213 return 0; 214 } 215 final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER); 216 if (appConfigurations.length == 0) { 217 Log.w(TAG, "no data found from the split() processing"); 218 return 0; 219 } 220 int restoreCount = 0; 221 for (String appConfiguration : appConfigurations) { 222 final String[] results = appConfiguration.split(BatteryBackupHelper.DELIMITER_MODE); 223 // Example format: com.android.systemui:2 we should have length=2 224 if (results.length != 2) { 225 Log.w(TAG, "invalid raw data found:" + appConfiguration); 226 continue; 227 } 228 final String packageName = results[0]; 229 final int uid = BatteryUtils.getInstance(mApplication).getPackageUid(packageName); 230 // Ignores system/default apps. 231 if (isSystemOrDefaultApp(packageName, uid)) { 232 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName); 233 continue; 234 } 235 @BatteryOptimizeUtils.OptimizationMode int optimizationMode; 236 try { 237 optimizationMode = Integer.parseInt(results[1]); 238 } catch (NumberFormatException e) { 239 Log.e(TAG, "failed to parse the optimization mode: " + appConfiguration, e); 240 continue; 241 } 242 restoreOptimizationMode(packageName, optimizationMode); 243 restoreCount++; 244 } 245 Log.d( 246 TAG, 247 String.format( 248 "restoreOptimizationMode() count=%d in %d/ms", 249 restoreCount, (System.currentTimeMillis() - timestamp))); 250 return restoreCount; 251 } 252 restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)253 private void restoreOptimizationMode( 254 String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) { 255 final BatteryOptimizeUtils batteryOptimizeUtils = 256 newBatteryOptimizeUtils(mApplication, packageName); 257 if (batteryOptimizeUtils == null) { 258 return; 259 } 260 batteryOptimizeUtils.setAppUsageState( 261 mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE); 262 Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode)); 263 } 264 265 @Nullable newBatteryOptimizeUtils(Context context, String packageName)266 static BatteryOptimizeUtils newBatteryOptimizeUtils(Context context, String packageName) { 267 final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName); 268 return uid == BatteryUtils.UID_NULL 269 ? null 270 : new BatteryOptimizeUtils(context, uid, packageName); 271 } 272 isSystemOrDefaultApp(String packageName, int uid)273 private boolean isSystemOrDefaultApp(String packageName, int uid) { 274 return BatteryOptimizeUtils.isSystemOrDefaultApp( 275 mApplication, PowerAllowlistBackend.getInstance(mApplication), packageName, uid); 276 } 277 278 private class StringEntity implements BackupRestoreEntity { 279 private final String mKey; 280 private final String mValue; 281 StringEntity(String key, String value)282 StringEntity(String key, String value) { 283 this.mKey = key; 284 this.mValue = value; 285 } 286 287 @NonNull 288 @Override getKey()289 public String getKey() { 290 return mKey; 291 } 292 293 @Override backup( @onNull BackupContext backupContext, @NonNull OutputStream outputStream)294 public @NonNull EntityBackupResult backup( 295 @NonNull BackupContext backupContext, @NonNull OutputStream outputStream) 296 throws IOException { 297 Log.d(TAG, String.format("backup:%s:%s", mKey, mValue)); 298 outputStream.write(mValue.getBytes(UTF_8)); 299 return EntityBackupResult.UPDATE; 300 } 301 302 @Override restore( @onNull RestoreContext restoreContext, @NonNull InputStream inputStream)303 public void restore( 304 @NonNull RestoreContext restoreContext, @NonNull InputStream inputStream) 305 throws IOException { 306 String dataContent = new String(inputStream.readAllBytes(), UTF_8); 307 mDeviceBuildInfoMap.put(mKey, dataContent); 308 Log.d(TAG, String.format("restore:%s:%s", mKey, dataContent)); 309 } 310 } 311 312 private class OptimizationModeEntity implements BackupRestoreEntity { 313 private final List<String> mAllowlistedApps; 314 OptimizationModeEntity(List<String> allowlistedApps)315 private OptimizationModeEntity(List<String> allowlistedApps) { 316 this.mAllowlistedApps = allowlistedApps; 317 } 318 319 @NonNull 320 @Override getKey()321 public String getKey() { 322 return KEY_OPTIMIZATION_LIST; 323 } 324 325 @Override backup( @onNull BackupContext backupContext, @NonNull OutputStream outputStream)326 public @NonNull EntityBackupResult backup( 327 @NonNull BackupContext backupContext, @NonNull OutputStream outputStream) 328 throws IOException { 329 final long timestamp = System.currentTimeMillis(); 330 final ApplicationInfo[] applications = getInstalledApplications(); 331 if (applications.length == 0) { 332 Log.w(TAG, "no data found in the getInstalledApplications()"); 333 return EntityBackupResult.DELETE; 334 } 335 int backupCount = 0; 336 final StringBuilder builder = new StringBuilder(); 337 final AppOpsManager appOps = mApplication.getSystemService(AppOpsManager.class); 338 final SharedPreferences sharedPreferences = getSharedPreferences(mApplication); 339 final Map<Integer, AppOptimizationModeEvent> appOptModeMap = 340 AppOptModeSharedPreferencesUtils.getAllEvents(mApplication).stream() 341 .collect(Collectors.toMap(AppOptimizationModeEvent::getUid, e -> e)); 342 // Converts application into the AppUsageState. 343 for (ApplicationInfo info : applications) { 344 final int mode = BatteryOptimizeUtils.getMode(appOps, info.uid, info.packageName); 345 @BatteryOptimizeUtils.OptimizationMode 346 final int optimizationMode = 347 appOptModeMap.containsKey(info.uid) 348 ? (int) appOptModeMap.get(info.uid).getResetOptimizationMode() 349 : BatteryOptimizeUtils.getAppOptimizationMode( 350 mode, mAllowlistedApps.contains(info.packageName)); 351 // Ignores default optimized/unknown state or system/default apps. 352 if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED 353 || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN 354 || isSystemOrDefaultApp(info.packageName, info.uid)) { 355 continue; 356 } 357 final String packageOptimizeMode = 358 info.packageName + DELIMITER_MODE + optimizationMode; 359 builder.append(packageOptimizeMode).append(DELIMITER); 360 Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode); 361 BatteryOptimizeLogUtils.writeLog( 362 sharedPreferences, 363 Action.BACKUP, 364 info.packageName, 365 /* actionDescription */ "mode: " + optimizationMode); 366 backupCount++; 367 } 368 369 outputStream.write(builder.toString().getBytes(UTF_8)); 370 Log.d( 371 TAG, 372 String.format( 373 "backup getInstalledApplications():%d count=%d in %d/ms", 374 applications.length, 375 backupCount, 376 (System.currentTimeMillis() - timestamp))); 377 return EntityBackupResult.UPDATE; 378 } 379 getInstalledApplications()380 private ApplicationInfo[] getInstalledApplications() { 381 ArraySet<ApplicationInfo> installedApplications = 382 BatteryOptimizeUtils.getInstalledApplications( 383 mApplication, AppGlobals.getPackageManager()); 384 ApplicationInfo[] applicationInfos = new ApplicationInfo[0]; 385 if (installedApplications == null || installedApplications.isEmpty()) { 386 return applicationInfos; 387 } 388 applicationInfos = installedApplications.toArray(applicationInfos); 389 // sort the list to ensure backup data is stable 390 Arrays.sort(applicationInfos, Comparator.comparing(info -> info.packageName)); 391 return applicationInfos; 392 } 393 getSharedPreferences(Context context)394 static @NonNull SharedPreferences getSharedPreferences(Context context) { 395 return context.getSharedPreferences( 396 BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE); 397 } 398 399 @Override restore( @onNull RestoreContext restoreContext, @NonNull InputStream inputStream)400 public void restore( 401 @NonNull RestoreContext restoreContext, @NonNull InputStream inputStream) 402 throws IOException { 403 mOptimizationModeBytes = inputStream.readAllBytes(); 404 } 405 } 406 } 407