1 /* 2 * Copyright (C) 2021 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 android.annotation.IntDef; 20 import android.app.AppOpsManager; 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.IPackageManager; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ParceledListSlice; 26 import android.content.pm.UserInfo; 27 import android.os.UserHandle; 28 import android.os.UserManager; 29 import android.util.ArraySet; 30 import android.util.Log; 31 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.settings.R; 35 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; 36 import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; 37 import com.android.settingslib.datastore.DataChangeReason; 38 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.Arrays; 43 import java.util.List; 44 45 /** A utility class for application usage operation. */ 46 public class BatteryOptimizeUtils { 47 private static final String TAG = "BatteryOptimizeUtils"; 48 private static final String UNKNOWN_PACKAGE = "unknown"; 49 50 // Avoid reload the data again since it is predefined in the resource/config. 51 private static List<String> sBatteryOptimizeModeList = null; 52 private static List<String> sBatteryUnrestrictModeList = null; 53 54 @VisibleForTesting AppOpsManager mAppOpsManager; 55 @VisibleForTesting BatteryUtils mBatteryUtils; 56 @VisibleForTesting PowerAllowlistBackend mPowerAllowListBackend; 57 @VisibleForTesting int mMode; 58 @VisibleForTesting boolean mAllowListed; 59 60 private final String mPackageName; 61 private final Context mContext; 62 private final int mUid; 63 64 // If current user is admin, match apps from all users. Otherwise, only match the currect user. 65 private static final int RETRIEVE_FLAG_ADMIN = 66 PackageManager.MATCH_ANY_USER 67 | PackageManager.MATCH_DISABLED_COMPONENTS 68 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 69 private static final int RETRIEVE_FLAG = 70 PackageManager.MATCH_DISABLED_COMPONENTS 71 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 72 73 // Optimization modes. 74 public static final int MODE_UNKNOWN = 0; 75 public static final int MODE_RESTRICTED = 1; 76 public static final int MODE_UNRESTRICTED = 2; 77 public static final int MODE_OPTIMIZED = 3; 78 79 @IntDef( 80 prefix = {"MODE_"}, 81 value = { 82 MODE_UNKNOWN, 83 MODE_RESTRICTED, 84 MODE_UNRESTRICTED, 85 MODE_OPTIMIZED, 86 }) 87 @Retention(RetentionPolicy.SOURCE) 88 static @interface OptimizationMode {} 89 BatteryOptimizeUtils(Context context, int uid, String packageName)90 public BatteryOptimizeUtils(Context context, int uid, String packageName) { 91 mUid = uid; 92 mContext = context; 93 mPackageName = packageName; 94 mAppOpsManager = context.getSystemService(AppOpsManager.class); 95 mBatteryUtils = BatteryUtils.getInstance(context); 96 mPowerAllowListBackend = PowerAllowlistBackend.getInstance(context); 97 mMode = getMode(mAppOpsManager, mUid, mPackageName); 98 mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName, mUid); 99 } 100 101 /** Gets the {@link OptimizationMode} based on mode and allowed list. */ 102 @OptimizationMode getAppOptimizationMode(int mode, boolean isAllowListed)103 public static int getAppOptimizationMode(int mode, boolean isAllowListed) { 104 if (!isAllowListed && mode == AppOpsManager.MODE_IGNORED) { 105 return MODE_RESTRICTED; 106 } else if (isAllowListed && mode == AppOpsManager.MODE_ALLOWED) { 107 return MODE_UNRESTRICTED; 108 } else if (!isAllowListed && mode == AppOpsManager.MODE_ALLOWED) { 109 return MODE_OPTIMIZED; 110 } else { 111 return MODE_UNKNOWN; 112 } 113 } 114 115 /** Gets the {@link OptimizationMode} for associated app. */ 116 @OptimizationMode getAppOptimizationMode(boolean refreshList)117 public int getAppOptimizationMode(boolean refreshList) { 118 if (refreshList) { 119 mPowerAllowListBackend.refreshList(); 120 } 121 mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName, mUid); 122 mMode = 123 mAppOpsManager.checkOpNoThrow( 124 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName); 125 Log.d( 126 TAG, 127 String.format( 128 "refresh %s state, allowlisted = %s, mode = %d", 129 mPackageName, mAllowListed, mMode)); 130 return getAppOptimizationMode(mMode, mAllowListed); 131 } 132 133 /** Gets the {@link OptimizationMode} for associated app. */ 134 @OptimizationMode getAppOptimizationMode()135 public int getAppOptimizationMode() { 136 return getAppOptimizationMode(true); 137 } 138 139 /** Resets optimization mode for all applications. */ resetAppOptimizationMode( Context context, IPackageManager ipm, AppOpsManager aom)140 public static void resetAppOptimizationMode( 141 Context context, IPackageManager ipm, AppOpsManager aom) { 142 AppOptModeSharedPreferencesUtils.clearAll(context); 143 resetAppOptimizationModeInternal( 144 context, 145 ipm, 146 aom, 147 PowerAllowlistBackend.getInstance(context), 148 BatteryUtils.getInstance(context)); 149 } 150 151 /** Sets the {@link OptimizationMode} for associated app. */ setAppUsageState(@ptimizationMode int mode, Action action)152 public void setAppUsageState(@OptimizationMode int mode, Action action) { 153 if (getAppOptimizationMode() == mode) { 154 Log.w(TAG, "set the same optimization mode for: " + mPackageName); 155 return; 156 } 157 setAppUsageStateInternal( 158 mContext, mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend, action); 159 } 160 161 /** Return {@code true} if it is disabled for default optimized mode only. */ isDisabledForOptimizeModeOnly()162 public boolean isDisabledForOptimizeModeOnly() { 163 return getForceBatteryOptimizeModeList(mContext).contains(mPackageName) 164 || mBatteryUtils.getPackageUid(mPackageName) == BatteryUtils.UID_NULL; 165 } 166 167 /** Return {@code true} if this package is system or default active app. */ isSystemOrDefaultApp()168 public boolean isSystemOrDefaultApp() { 169 mPowerAllowListBackend.refreshList(); 170 return isSystemOrDefaultApp(mContext, mPowerAllowListBackend, mPackageName, mUid); 171 } 172 173 /** Return {@code true} if the optimization mode of this package can be changed */ isOptimizeModeMutable()174 public boolean isOptimizeModeMutable() { 175 return !isDisabledForOptimizeModeOnly() && !isSystemOrDefaultApp(); 176 } 177 178 /** 179 * Return {@code true} if the optimization mode is mutable and current state is not restricted 180 */ isSelectorPreferenceEnabled()181 public boolean isSelectorPreferenceEnabled() { 182 // Enable the preference if apps are not set into restricted mode, otherwise disable it 183 return isOptimizeModeMutable() 184 && getAppOptimizationMode() != BatteryOptimizeUtils.MODE_RESTRICTED; 185 } 186 getPackageName()187 String getPackageName() { 188 return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName; 189 } 190 getUid()191 int getUid() { 192 return mUid; 193 } 194 195 /** Gets the list of installed applications. */ getInstalledApplications( Context context, IPackageManager ipm)196 public static ArraySet<ApplicationInfo> getInstalledApplications( 197 Context context, IPackageManager ipm) { 198 final ArraySet<ApplicationInfo> applications = new ArraySet<>(); 199 final UserManager um = context.getSystemService(UserManager.class); 200 for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) { 201 try { 202 @SuppressWarnings("unchecked") 203 final ParceledListSlice<ApplicationInfo> infoList = 204 ipm.getInstalledApplications( 205 userInfo.isAdmin() ? RETRIEVE_FLAG_ADMIN : RETRIEVE_FLAG, 206 userInfo.id); 207 if (infoList != null) { 208 applications.addAll(infoList.getList()); 209 } 210 } catch (Exception e) { 211 Log.e(TAG, "getInstalledApplications() is failed", e); 212 return null; 213 } 214 } 215 // Removes the application which is disabled by the system. 216 applications.removeIf( 217 info -> 218 info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER 219 && !info.enabled); 220 return applications; 221 } 222 223 @VisibleForTesting resetAppOptimizationModeInternal( Context context, IPackageManager ipm, AppOpsManager aom, PowerAllowlistBackend allowlistBackend, BatteryUtils batteryUtils)224 static void resetAppOptimizationModeInternal( 225 Context context, 226 IPackageManager ipm, 227 AppOpsManager aom, 228 PowerAllowlistBackend allowlistBackend, 229 BatteryUtils batteryUtils) { 230 final ArraySet<ApplicationInfo> applications = getInstalledApplications(context, ipm); 231 if (applications == null || applications.isEmpty()) { 232 Log.w(TAG, "no data found in the getInstalledApplications()"); 233 return; 234 } 235 236 // App preferences are already clear when code reach here, and there may be no 237 // setAppUsageStateInternal call to notifyChange. So always trigger notifyChange here. 238 BatterySettingsStorage.get(context).notifyChange(DataChangeReason.DELETE); 239 240 allowlistBackend.refreshList(); 241 // Resets optimization mode for each application. 242 for (ApplicationInfo info : applications) { 243 final int mode = 244 aom.checkOpNoThrow( 245 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName); 246 @OptimizationMode 247 final int optimizationMode = 248 getAppOptimizationMode( 249 mode, allowlistBackend.isAllowlisted(info.packageName, info.uid)); 250 // Ignores default optimized/unknown state or system/default apps. 251 if (optimizationMode == MODE_OPTIMIZED 252 || optimizationMode == MODE_UNKNOWN 253 || isSystemOrDefaultApp( 254 context, allowlistBackend, info.packageName, info.uid)) { 255 continue; 256 } 257 258 // Resets to the default mode: MODE_OPTIMIZED. 259 setAppUsageStateInternal( 260 context, 261 MODE_OPTIMIZED, 262 info.uid, 263 info.packageName, 264 batteryUtils, 265 allowlistBackend, 266 Action.RESET); 267 } 268 } 269 getMode(AppOpsManager appOpsManager, int uid, String packageName)270 static int getMode(AppOpsManager appOpsManager, int uid, String packageName) { 271 return appOpsManager.checkOpNoThrow( 272 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); 273 } 274 isSystemOrDefaultApp( Context context, PowerAllowlistBackend powerAllowlistBackend, String packageName, int uid)275 static boolean isSystemOrDefaultApp( 276 Context context, 277 PowerAllowlistBackend powerAllowlistBackend, 278 String packageName, 279 int uid) { 280 return powerAllowlistBackend.isSysAllowlisted(packageName) 281 // Always forced unrestricted apps are one type of system important apps. 282 || getForceBatteryUnrestrictModeList(context).contains(packageName) 283 || powerAllowlistBackend.isDefaultActiveApp(packageName, uid); 284 } 285 getForceBatteryOptimizeModeList(Context context)286 static List<String> getForceBatteryOptimizeModeList(Context context) { 287 if (sBatteryOptimizeModeList == null) { 288 sBatteryOptimizeModeList = 289 Arrays.asList( 290 context.getResources() 291 .getStringArray( 292 R.array.config_force_battery_optimize_mode_apps)); 293 } 294 return sBatteryOptimizeModeList; 295 } 296 getForceBatteryUnrestrictModeList(Context context)297 static List<String> getForceBatteryUnrestrictModeList(Context context) { 298 if (sBatteryUnrestrictModeList == null) { 299 sBatteryUnrestrictModeList = 300 Arrays.asList( 301 context.getResources() 302 .getStringArray( 303 R.array.config_force_battery_unrestrict_mode_apps)); 304 } 305 return sBatteryUnrestrictModeList; 306 } 307 setAppUsageStateInternal( Context context, @OptimizationMode int mode, int uid, String packageName, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, Action action)308 private static void setAppUsageStateInternal( 309 Context context, 310 @OptimizationMode int mode, 311 int uid, 312 String packageName, 313 BatteryUtils batteryUtils, 314 PowerAllowlistBackend powerAllowlistBackend, 315 Action action) { 316 if (mode == MODE_UNKNOWN) { 317 Log.d(TAG, "set unknown app optimization mode."); 318 return; 319 } 320 321 // MODE_RESTRICTED = AppOpsManager.MODE_IGNORED + !allowListed 322 // MODE_UNRESTRICTED = AppOpsManager.MODE_ALLOWED + allowListed 323 // MODE_OPTIMIZED = AppOpsManager.MODE_ALLOWED + !allowListed 324 final int appOpsManagerMode = 325 mode == MODE_RESTRICTED ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED; 326 final boolean allowListed = mode == MODE_UNRESTRICTED; 327 328 setAppOptimizationModeInternal( 329 context, 330 appOpsManagerMode, 331 allowListed, 332 uid, 333 packageName, 334 batteryUtils, 335 powerAllowlistBackend, 336 action); 337 } 338 setAppOptimizationModeInternal( Context context, int appStandbyMode, boolean allowListed, int uid, String packageName, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, Action action)339 private static void setAppOptimizationModeInternal( 340 Context context, 341 int appStandbyMode, 342 boolean allowListed, 343 int uid, 344 String packageName, 345 BatteryUtils batteryUtils, 346 PowerAllowlistBackend powerAllowlistBackend, 347 Action action) { 348 final String packageNameKey = 349 BatteryOptimizeLogUtils.getPackageNameWithUserId( 350 packageName, UserHandle.myUserId()); 351 try { 352 batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode); 353 if (allowListed) { 354 powerAllowlistBackend.addApp(packageName, uid); 355 } else { 356 powerAllowlistBackend.removeApp(packageName, uid); 357 } 358 } catch (Exception e) { 359 // Error cases, set standby mode as -1 for logging. 360 appStandbyMode = -1; 361 Log.e(TAG, "set OPTIMIZATION MODE failed for " + packageName, e); 362 } 363 BatteryOptimizeLogUtils.writeLog( 364 context, action, packageNameKey, createLogEvent(appStandbyMode, allowListed)); 365 if (action != Action.RESET) { // reset has been notified in resetAppOptimizationMode 366 BatterySettingsStorage.get(context).notifyChange(toChangeReason(action)); 367 } 368 } 369 createLogEvent(int appStandbyMode, boolean allowListed)370 private static String createLogEvent(int appStandbyMode, boolean allowListed) { 371 return appStandbyMode < 0 372 ? "Apply optimize setting ERROR" 373 : String.format( 374 "\tStandbyMode: %s, allowListed: %s, mode: %s", 375 appStandbyMode, 376 allowListed, 377 getAppOptimizationMode(appStandbyMode, allowListed)); 378 } 379 toChangeReason(Action action)380 private static @DataChangeReason int toChangeReason(Action action) { 381 return action == Action.RESTORE ? DataChangeReason.RESTORE : DataChangeReason.UPDATE; 382 } 383 } 384