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, boolean ignoreUnknownMode)103 public static int getAppOptimizationMode( 104 int mode, boolean isAllowListed, boolean ignoreUnknownMode) { 105 if (!isAllowListed && mode == AppOpsManager.MODE_IGNORED) { 106 return MODE_RESTRICTED; 107 } else if (isAllowListed && mode == AppOpsManager.MODE_ALLOWED) { 108 return MODE_UNRESTRICTED; 109 } else if (!isAllowListed && mode == AppOpsManager.MODE_ALLOWED) { 110 return MODE_OPTIMIZED; 111 } else { 112 // MODE_UNKNOWN = isAllowListed + AppOpsManager.MODE_IGNORED 113 // Return Unrestricted mode for Unknown mode since it is in allowlist. 114 return ignoreUnknownMode ? MODE_UNRESTRICTED : MODE_UNKNOWN; 115 } 116 } 117 118 /** Gets the {@link OptimizationMode} for associated app. */ 119 @OptimizationMode getAppOptimizationMode(boolean refreshList, boolean ignoreUnknownMode)120 public int getAppOptimizationMode(boolean refreshList, boolean ignoreUnknownMode) { 121 if (refreshList) { 122 mPowerAllowListBackend.refreshList(); 123 } 124 mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName, mUid); 125 mMode = 126 mAppOpsManager.checkOpNoThrow( 127 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName); 128 Log.d( 129 TAG, 130 String.format( 131 "refresh %s state, allowlisted = %s, mode = %d", 132 mPackageName, mAllowListed, mMode)); 133 return getAppOptimizationMode(mMode, mAllowListed, ignoreUnknownMode); 134 } 135 136 /** Gets the {@link OptimizationMode} for associated app. */ 137 @OptimizationMode getAppOptimizationMode()138 public int getAppOptimizationMode() { 139 return getAppOptimizationMode(/* refreshList= */ true, /* ignoreUnknownMode= */ true); 140 } 141 142 /** Resets optimization mode for all applications. */ resetAppOptimizationMode( Context context, IPackageManager ipm, AppOpsManager aom)143 public static void resetAppOptimizationMode( 144 Context context, IPackageManager ipm, AppOpsManager aom) { 145 AppOptModeSharedPreferencesUtils.clearAll(context); 146 resetAppOptimizationModeInternal( 147 context, 148 ipm, 149 aom, 150 PowerAllowlistBackend.getInstance(context), 151 BatteryUtils.getInstance(context)); 152 } 153 154 /** Sets the {@link OptimizationMode} for associated app. */ setAppUsageState(@ptimizationMode int mode, Action action, boolean forceMode)155 public void setAppUsageState(@OptimizationMode int mode, Action action, boolean forceMode) { 156 if (!forceMode && getAppOptimizationMode() == mode) { 157 Log.w(TAG, "set the same optimization mode for: " + mPackageName); 158 return; 159 } 160 setAppUsageStateInternal( 161 mContext, mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend, action); 162 } 163 164 /** Sets the {@link OptimizationMode} for associated app. */ setAppUsageState(@ptimizationMode int mode, Action action)165 public void setAppUsageState(@OptimizationMode int mode, Action action) { 166 setAppUsageState(mode, action, /* forceMode= */ false); 167 } 168 169 /** Return {@code true} if it is disabled for default optimized mode only. */ isDisabledForOptimizeModeOnly()170 public boolean isDisabledForOptimizeModeOnly() { 171 return getForceBatteryOptimizeModeList(mContext).contains(mPackageName) 172 || mBatteryUtils.getPackageUid(mPackageName) == BatteryUtils.UID_NULL; 173 } 174 175 /** Return {@code true} if this package is system or default active app. */ isSystemOrDefaultApp()176 public boolean isSystemOrDefaultApp() { 177 mPowerAllowListBackend.refreshList(); 178 return isSystemOrDefaultApp(mContext, mPowerAllowListBackend, mPackageName, mUid); 179 } 180 181 /** Return {@code true} if the optimization mode of this package can be changed */ isOptimizeModeMutable()182 public boolean isOptimizeModeMutable() { 183 return !isDisabledForOptimizeModeOnly() && !isSystemOrDefaultApp(); 184 } 185 186 /** 187 * Return {@code true} if the optimization mode is mutable and current state is not restricted 188 */ isSelectorPreferenceEnabled()189 public boolean isSelectorPreferenceEnabled() { 190 // Enable the preference if apps are not set into restricted mode, otherwise disable it 191 return isOptimizeModeMutable() 192 && getAppOptimizationMode() != BatteryOptimizeUtils.MODE_RESTRICTED; 193 } 194 getPackageName()195 String getPackageName() { 196 return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName; 197 } 198 getUid()199 int getUid() { 200 return mUid; 201 } 202 203 /** Gets the list of installed applications. */ getInstalledApplications( Context context, IPackageManager ipm)204 public static ArraySet<ApplicationInfo> getInstalledApplications( 205 Context context, IPackageManager ipm) { 206 final ArraySet<ApplicationInfo> applications = new ArraySet<>(); 207 final UserManager um = context.getSystemService(UserManager.class); 208 for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) { 209 try { 210 @SuppressWarnings("unchecked") 211 final ParceledListSlice<ApplicationInfo> infoList = 212 ipm.getInstalledApplications( 213 userInfo.isAdmin() ? RETRIEVE_FLAG_ADMIN : RETRIEVE_FLAG, 214 userInfo.id); 215 if (infoList != null) { 216 applications.addAll(infoList.getList()); 217 } 218 } catch (Exception e) { 219 Log.e(TAG, "getInstalledApplications() is failed", e); 220 return null; 221 } 222 } 223 // Removes the application which is disabled by the system. 224 applications.removeIf( 225 info -> 226 info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER 227 && !info.enabled); 228 return applications; 229 } 230 231 @VisibleForTesting resetAppOptimizationModeInternal( Context context, IPackageManager ipm, AppOpsManager aom, PowerAllowlistBackend allowlistBackend, BatteryUtils batteryUtils)232 static void resetAppOptimizationModeInternal( 233 Context context, 234 IPackageManager ipm, 235 AppOpsManager aom, 236 PowerAllowlistBackend allowlistBackend, 237 BatteryUtils batteryUtils) { 238 final ArraySet<ApplicationInfo> applications = getInstalledApplications(context, ipm); 239 if (applications == null || applications.isEmpty()) { 240 Log.w(TAG, "no data found in the getInstalledApplications()"); 241 return; 242 } 243 244 // App preferences are already clear when code reach here, and there may be no 245 // setAppUsageStateInternal call to notifyChange. So always trigger notifyChange here. 246 BatterySettingsStorage.get(context).notifyChange(DataChangeReason.DELETE); 247 248 allowlistBackend.refreshList(); 249 // Resets optimization mode for each application. 250 for (ApplicationInfo info : applications) { 251 final int mode = 252 aom.checkOpNoThrow( 253 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName); 254 @OptimizationMode 255 final int optimizationMode = 256 getAppOptimizationMode( 257 mode, 258 allowlistBackend.isAllowlisted(info.packageName, info.uid), 259 /* ignoreUnknownMode= */ false); 260 // Ignores default optimized state or system/default apps. 261 if (optimizationMode == MODE_OPTIMIZED 262 || isSystemOrDefaultApp( 263 context, allowlistBackend, info.packageName, info.uid)) { 264 continue; 265 } 266 267 // Resets to the default mode: MODE_OPTIMIZED. 268 setAppUsageStateInternal( 269 context, 270 MODE_OPTIMIZED, 271 info.uid, 272 info.packageName, 273 batteryUtils, 274 allowlistBackend, 275 Action.RESET); 276 } 277 } 278 getMode(AppOpsManager appOpsManager, int uid, String packageName)279 static int getMode(AppOpsManager appOpsManager, int uid, String packageName) { 280 return appOpsManager.checkOpNoThrow( 281 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); 282 } 283 isSystemOrDefaultApp( Context context, PowerAllowlistBackend powerAllowlistBackend, String packageName, int uid)284 static boolean isSystemOrDefaultApp( 285 Context context, 286 PowerAllowlistBackend powerAllowlistBackend, 287 String packageName, 288 int uid) { 289 return powerAllowlistBackend.isSysAllowlisted(packageName) 290 // Always forced unrestricted apps are one type of system important apps. 291 || getForceBatteryUnrestrictModeList(context).contains(packageName) 292 || powerAllowlistBackend.isDefaultActiveApp(packageName, uid); 293 } 294 getForceBatteryOptimizeModeList(Context context)295 static List<String> getForceBatteryOptimizeModeList(Context context) { 296 if (sBatteryOptimizeModeList == null) { 297 sBatteryOptimizeModeList = 298 Arrays.asList( 299 context.getResources() 300 .getStringArray( 301 R.array.config_force_battery_optimize_mode_apps)); 302 } 303 return sBatteryOptimizeModeList; 304 } 305 getForceBatteryUnrestrictModeList(Context context)306 static List<String> getForceBatteryUnrestrictModeList(Context context) { 307 if (sBatteryUnrestrictModeList == null) { 308 sBatteryUnrestrictModeList = 309 Arrays.asList( 310 context.getResources() 311 .getStringArray( 312 R.array.config_force_battery_unrestrict_mode_apps)); 313 } 314 return sBatteryUnrestrictModeList; 315 } 316 setAppUsageStateInternal( Context context, @OptimizationMode int mode, int uid, String packageName, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, Action action)317 private static void setAppUsageStateInternal( 318 Context context, 319 @OptimizationMode int mode, 320 int uid, 321 String packageName, 322 BatteryUtils batteryUtils, 323 PowerAllowlistBackend powerAllowlistBackend, 324 Action action) { 325 if (mode == MODE_UNKNOWN) { 326 Log.d(TAG, "set unknown app optimization mode."); 327 return; 328 } 329 330 // MODE_RESTRICTED = AppOpsManager.MODE_IGNORED + !allowListed 331 // MODE_UNRESTRICTED = AppOpsManager.MODE_ALLOWED + allowListed 332 // MODE_OPTIMIZED = AppOpsManager.MODE_ALLOWED + !allowListed 333 final int appOpsManagerMode = 334 mode == MODE_RESTRICTED ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED; 335 final boolean allowListed = mode == MODE_UNRESTRICTED; 336 337 setAppOptimizationModeInternal( 338 context, 339 appOpsManagerMode, 340 allowListed, 341 uid, 342 packageName, 343 batteryUtils, 344 powerAllowlistBackend, 345 action); 346 } 347 setAppOptimizationModeInternal( Context context, int appStandbyMode, boolean allowListed, int uid, String packageName, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, Action action)348 private static void setAppOptimizationModeInternal( 349 Context context, 350 int appStandbyMode, 351 boolean allowListed, 352 int uid, 353 String packageName, 354 BatteryUtils batteryUtils, 355 PowerAllowlistBackend powerAllowlistBackend, 356 Action action) { 357 final String packageNameKey = 358 BatteryOptimizeLogUtils.getPackageNameWithUserId( 359 packageName, UserHandle.myUserId()); 360 try { 361 batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode); 362 if (allowListed) { 363 powerAllowlistBackend.addApp(packageName, uid); 364 } else { 365 powerAllowlistBackend.removeApp(packageName, uid); 366 } 367 } catch (Exception e) { 368 // Error cases, set standby mode as -1 for logging. 369 appStandbyMode = -1; 370 Log.e(TAG, "set OPTIMIZATION MODE failed for " + packageName, e); 371 } 372 BatteryOptimizeLogUtils.writeLog( 373 context, action, packageNameKey, createLogEvent(appStandbyMode, allowListed)); 374 if (action != Action.RESET) { // reset has been notified in resetAppOptimizationMode 375 BatterySettingsStorage.get(context).notifyChange(toChangeReason(action)); 376 } 377 } 378 createLogEvent(int appStandbyMode, boolean allowListed)379 private static String createLogEvent(int appStandbyMode, boolean allowListed) { 380 return appStandbyMode < 0 381 ? "Apply optimize setting ERROR" 382 : String.format( 383 "\tStandbyMode: %s, allowListed: %s, mode: %s", 384 appStandbyMode, 385 allowListed, 386 getAppOptimizationMode( 387 appStandbyMode, allowListed, /* ignoreUnknownMode= */ false)); 388 } 389 toChangeReason(Action action)390 private static @DataChangeReason int toChangeReason(Action action) { 391 return action == Action.RESTORE ? DataChangeReason.RESTORE : DataChangeReason.UPDATE; 392 } 393 } 394