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.content.pm.PackageManager; 28 import android.content.pm.ParceledListSlice; 29 import android.content.pm.UserInfo; 30 import android.os.Build; 31 import android.os.IDeviceIdleController; 32 import android.os.ParcelFileDescriptor; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.util.Log; 38 39 import androidx.annotation.VisibleForTesting; 40 41 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 42 43 import java.io.IOException; 44 import java.nio.charset.StandardCharsets; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.List; 48 49 /** An implementation to backup and restore battery configurations. */ 50 public final class BatteryBackupHelper implements BackupHelper { 51 /** An inditifier for {@link BackupHelper}. */ 52 public static final String TAG = "BatteryBackupHelper"; 53 private static final String DEVICE_IDLE_SERVICE = "deviceidle"; 54 private static final boolean DEBUG = Build.TYPE.equals("userdebug"); 55 56 // Only the owner can see all apps. 57 private static final int RETRIEVE_FLAG_ADMIN = 58 PackageManager.MATCH_ANY_USER | 59 PackageManager.MATCH_DISABLED_COMPONENTS | 60 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 61 private static final int RETRIEVE_FLAG = 62 PackageManager.MATCH_DISABLED_COMPONENTS | 63 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 64 65 static final String DELIMITER = ","; 66 static final String DELIMITER_MODE = ":"; 67 static final String KEY_FULL_POWER_LIST = "full_power_list"; 68 static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list"; 69 70 @VisibleForTesting 71 PowerAllowlistBackend mPowerAllowlistBackend; 72 @VisibleForTesting 73 IDeviceIdleController mIDeviceIdleController; 74 @VisibleForTesting 75 IPackageManager mIPackageManager; 76 @VisibleForTesting 77 BatteryOptimizeUtils mBatteryOptimizeUtils; 78 79 private final Context mContext; 80 BatteryBackupHelper(Context context)81 public BatteryBackupHelper(Context context) { 82 mContext = context.getApplicationContext(); 83 } 84 85 @Override performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)86 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 87 ParcelFileDescriptor newState) { 88 if (!isOwner() || data == null) { 89 Log.w(TAG, "ignore performBackup() for non-owner or empty data"); 90 return; 91 } 92 final List<String> allowlistedApps = backupFullPowerList(data); 93 if (allowlistedApps != null) { 94 backupOptimizationMode(data, allowlistedApps); 95 } 96 } 97 98 @Override restoreEntity(BackupDataInputStream data)99 public void restoreEntity(BackupDataInputStream data) { 100 if (!isOwner() || data == null || data.size() == 0) { 101 Log.w(TAG, "ignore restoreEntity() for non-owner or empty data"); 102 return; 103 } 104 if (KEY_OPTIMIZATION_LIST.equals(data.getKey())) { 105 final int dataSize = data.size(); 106 final byte[] dataBytes = new byte[dataSize]; 107 try { 108 data.read(dataBytes, 0 /*offset*/, dataSize); 109 } catch (IOException e) { 110 Log.e(TAG, "failed to load BackupDataInputStream", e); 111 return; 112 } 113 restoreOptimizationMode(dataBytes); 114 } 115 } 116 117 @Override writeNewStateDescription(ParcelFileDescriptor newState)118 public void writeNewStateDescription(ParcelFileDescriptor newState) { 119 } 120 backupFullPowerList(BackupDataOutput data)121 private List<String> backupFullPowerList(BackupDataOutput data) { 122 final long timestamp = System.currentTimeMillis(); 123 String[] allowlistedApps; 124 try { 125 allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist(); 126 } catch (RemoteException e) { 127 Log.e(TAG, "backupFullPowerList() failed", e); 128 return null; 129 } 130 // Ignores unexpected emptty result case. 131 if (allowlistedApps == null || allowlistedApps.length == 0) { 132 Log.w(TAG, "no data found in the getFullPowerList()"); 133 return new ArrayList<>(); 134 } 135 136 final String allowedApps = String.join(DELIMITER, allowlistedApps); 137 writeBackupData(data, KEY_FULL_POWER_LIST, allowedApps); 138 Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms", 139 allowlistedApps.length, (System.currentTimeMillis() - timestamp))); 140 return Arrays.asList(allowlistedApps); 141 } 142 143 @VisibleForTesting backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps)144 void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) { 145 final long timestamp = System.currentTimeMillis(); 146 final List<ApplicationInfo> applications = getInstalledApplications(); 147 if (applications == null || applications.isEmpty()) { 148 Log.w(TAG, "no data found in the getInstalledApplications()"); 149 return; 150 } 151 int backupCount = 0; 152 final StringBuilder builder = new StringBuilder(); 153 final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); 154 // Converts application into the AppUsageState. 155 for (ApplicationInfo info : applications) { 156 final int mode = appOps.checkOpNoThrow( 157 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName); 158 @BatteryOptimizeUtils.OptimizationMode 159 final int optimizationMode = BatteryOptimizeUtils.getAppOptimizationMode( 160 mode, allowlistedApps.contains(info.packageName)); 161 // Ignores default optimized/unknown state or system/default apps. 162 if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED 163 || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN 164 || isSystemOrDefaultApp(info.packageName)) { 165 continue; 166 } 167 final String packageOptimizeMode = 168 info.packageName + DELIMITER_MODE + optimizationMode; 169 builder.append(packageOptimizeMode + DELIMITER); 170 debugLog(packageOptimizeMode); 171 backupCount++; 172 } 173 174 writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString()); 175 Log.d(TAG, String.format("backup getInstalledApplications():%d count=%d in %d/ms", 176 applications.size(), backupCount, (System.currentTimeMillis() - timestamp))); 177 } 178 179 @VisibleForTesting restoreOptimizationMode(byte[] dataBytes)180 void restoreOptimizationMode(byte[] dataBytes) { 181 final long timestamp = System.currentTimeMillis(); 182 final String dataContent = new String(dataBytes, StandardCharsets.UTF_8); 183 if (dataContent == null || dataContent.isEmpty()) { 184 Log.w(TAG, "no data found in the restoreOptimizationMode()"); 185 return; 186 } 187 final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER); 188 if (appConfigurations == null || appConfigurations.length == 0) { 189 Log.w(TAG, "no data found from the split() processing"); 190 return; 191 } 192 int restoreCount = 0; 193 for (int index = 0; index < appConfigurations.length; index++) { 194 final String[] results = appConfigurations[index] 195 .split(BatteryBackupHelper.DELIMITER_MODE); 196 // Example format: com.android.systemui:2 we should have length=2 197 if (results == null || results.length != 2) { 198 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]); 199 continue; 200 } 201 final String packageName = results[0]; 202 // Ignores system/default apps. 203 if (isSystemOrDefaultApp(packageName)) { 204 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName); 205 continue; 206 } 207 @BatteryOptimizeUtils.OptimizationMode 208 int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN; 209 try { 210 optimizationMode = Integer.parseInt(results[1]); 211 } catch (NumberFormatException e) { 212 Log.e(TAG, "failed to parse the optimization mode: " 213 + appConfigurations[index], e); 214 continue; 215 } 216 restoreOptimizationMode(packageName, optimizationMode); 217 restoreCount++; 218 } 219 Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms", 220 restoreCount, (System.currentTimeMillis() - timestamp))); 221 } 222 restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)223 private void restoreOptimizationMode( 224 String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) { 225 final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName); 226 if (uid == BatteryUtils.UID_NULL) { 227 return; 228 } 229 final BatteryOptimizeUtils batteryOptimizeUtils = 230 mBatteryOptimizeUtils != null 231 ? mBatteryOptimizeUtils /*testing only*/ 232 : new BatteryOptimizeUtils(mContext, uid, packageName); 233 batteryOptimizeUtils.setAppUsageState(mode); 234 Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode)); 235 } 236 237 // Provides an opportunity to inject mock IDeviceIdleController for testing. getIDeviceIdleController()238 private IDeviceIdleController getIDeviceIdleController() { 239 if (mIDeviceIdleController != null) { 240 return mIDeviceIdleController; 241 } 242 mIDeviceIdleController = IDeviceIdleController.Stub.asInterface( 243 ServiceManager.getService(DEVICE_IDLE_SERVICE)); 244 return mIDeviceIdleController; 245 } 246 getIPackageManager()247 private IPackageManager getIPackageManager() { 248 if (mIPackageManager != null) { 249 return mIPackageManager; 250 } 251 mIPackageManager = AppGlobals.getPackageManager(); 252 return mIPackageManager; 253 } 254 getPowerAllowlistBackend()255 private PowerAllowlistBackend getPowerAllowlistBackend() { 256 if (mPowerAllowlistBackend != null) { 257 return mPowerAllowlistBackend; 258 } 259 mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext); 260 return mPowerAllowlistBackend; 261 } 262 isSystemOrDefaultApp(String packageName)263 private boolean isSystemOrDefaultApp(String packageName) { 264 final PowerAllowlistBackend powerAllowlistBackend = getPowerAllowlistBackend(); 265 return powerAllowlistBackend.isSysAllowlisted(packageName) 266 || powerAllowlistBackend.isDefaultActiveApp(packageName); 267 } 268 getInstalledApplications()269 private List<ApplicationInfo> getInstalledApplications() { 270 final List<ApplicationInfo> applications = new ArrayList<>(); 271 final UserManager um = mContext.getSystemService(UserManager.class); 272 for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) { 273 try { 274 @SuppressWarnings("unchecked") 275 final ParceledListSlice<ApplicationInfo> infoList = 276 getIPackageManager().getInstalledApplications( 277 userInfo.isAdmin() ? RETRIEVE_FLAG_ADMIN : RETRIEVE_FLAG, 278 userInfo.id); 279 if (infoList != null) { 280 applications.addAll(infoList.getList()); 281 } 282 } catch (Exception e) { 283 Log.e(TAG, "getInstalledApplications() is failed", e); 284 return null; 285 } 286 } 287 // Removes the application which is disabled by the system. 288 for (int index = applications.size() - 1; index >= 0; index--) { 289 final ApplicationInfo info = applications.get(index); 290 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER 291 && !info.enabled) { 292 applications.remove(index); 293 } 294 } 295 return applications; 296 } 297 debugLog(String debugContent)298 private void debugLog(String debugContent) { 299 if (DEBUG) Log.d(TAG, debugContent); 300 } 301 writeBackupData( BackupDataOutput data, String dataKey, String dataContent)302 private static void writeBackupData( 303 BackupDataOutput data, String dataKey, String dataContent) { 304 final byte[] dataContentBytes = dataContent.getBytes(); 305 try { 306 data.writeEntityHeader(dataKey, dataContentBytes.length); 307 data.writeEntityData(dataContentBytes, dataContentBytes.length); 308 } catch (IOException e) { 309 Log.e(TAG, "writeBackupData() is failed for " + dataKey, e); 310 } 311 } 312 isOwner()313 private static boolean isOwner() { 314 return UserHandle.myUserId() == UserHandle.USER_OWNER; 315 } 316 } 317