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.settingslib.fuelgauge.PowerAllowlistBackend; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.Arrays; 41 import java.util.List; 42 43 /** A utility class for application usage operation. */ 44 public class BatteryOptimizeUtils { 45 private static final String TAG = "BatteryOptimizeUtils"; 46 private static final String UNKNOWN_PACKAGE = "unknown"; 47 48 @VisibleForTesting AppOpsManager mAppOpsManager; 49 @VisibleForTesting BatteryUtils mBatteryUtils; 50 @VisibleForTesting PowerAllowlistBackend mPowerAllowListBackend; 51 @VisibleForTesting int mMode; 52 @VisibleForTesting boolean mAllowListed; 53 54 private final String mPackageName; 55 private final Context mContext; 56 private final int mUid; 57 58 // If current user is admin, match apps from all users. Otherwise, only match the currect user. 59 private static final int RETRIEVE_FLAG_ADMIN = 60 PackageManager.MATCH_ANY_USER 61 | PackageManager.MATCH_DISABLED_COMPONENTS 62 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 63 private static final int RETRIEVE_FLAG = 64 PackageManager.MATCH_DISABLED_COMPONENTS 65 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 66 67 // Optimization modes. 68 static final int MODE_UNKNOWN = 0; 69 static final int MODE_RESTRICTED = 1; 70 static final int MODE_UNRESTRICTED = 2; 71 static final int MODE_OPTIMIZED = 3; 72 73 @IntDef(prefix = {"MODE_"}, value = { 74 MODE_UNKNOWN, 75 MODE_RESTRICTED, 76 MODE_UNRESTRICTED, 77 MODE_OPTIMIZED, 78 }) 79 @Retention(RetentionPolicy.SOURCE) 80 static @interface OptimizationMode {} 81 BatteryOptimizeUtils(Context context, int uid, String packageName)82 public BatteryOptimizeUtils(Context context, int uid, String packageName) { 83 mUid = uid; 84 mContext = context; 85 mPackageName = packageName; 86 mAppOpsManager = context.getSystemService(AppOpsManager.class); 87 mBatteryUtils = BatteryUtils.getInstance(context); 88 mPowerAllowListBackend = PowerAllowlistBackend.getInstance(context); 89 mMode = getMode(mAppOpsManager, mUid, mPackageName); 90 mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName, mUid); 91 } 92 93 /** Gets the {@link OptimizationMode} based on mode and allowed list. */ 94 @OptimizationMode getAppOptimizationMode(int mode, boolean isAllowListed)95 public static int getAppOptimizationMode(int mode, boolean isAllowListed) { 96 if (!isAllowListed && mode == AppOpsManager.MODE_IGNORED) { 97 return MODE_RESTRICTED; 98 } else if (isAllowListed && mode == AppOpsManager.MODE_ALLOWED) { 99 return MODE_UNRESTRICTED; 100 } else if (!isAllowListed && mode == AppOpsManager.MODE_ALLOWED) { 101 return MODE_OPTIMIZED; 102 } else { 103 return MODE_UNKNOWN; 104 } 105 } 106 107 /** Gets the {@link OptimizationMode} for associated app. */ 108 @OptimizationMode getAppOptimizationMode()109 public int getAppOptimizationMode() { 110 refreshState(); 111 return getAppOptimizationMode(mMode, mAllowListed); 112 } 113 114 /** Resets optimization mode for all applications. */ resetAppOptimizationMode( Context context, IPackageManager ipm, AppOpsManager aom)115 public static void resetAppOptimizationMode( 116 Context context, IPackageManager ipm, AppOpsManager aom) { 117 resetAppOptimizationMode(context, ipm, aom, 118 PowerAllowlistBackend.getInstance(context), BatteryUtils.getInstance(context)); 119 } 120 121 /** Sets the {@link OptimizationMode} for associated app. */ setAppUsageState(@ptimizationMode int mode, Action action)122 public void setAppUsageState(@OptimizationMode int mode, Action action) { 123 if (getAppOptimizationMode() == mode) { 124 Log.w(TAG, "set the same optimization mode for: " + mPackageName); 125 return; 126 } 127 setAppUsageStateInternal( 128 mContext, mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend, action); 129 } 130 131 /** Return {@code true} if it is disabled for default optimized mode only. */ isDisabledForOptimizeModeOnly()132 public boolean isDisabledForOptimizeModeOnly() { 133 return getAllowList(mContext).contains(mPackageName) 134 || mBatteryUtils.getPackageUid(mPackageName) == BatteryUtils.UID_NULL; 135 } 136 137 /** 138 * Return {@code true} if this package is system or default active app. 139 */ isSystemOrDefaultApp()140 public boolean isSystemOrDefaultApp() { 141 mPowerAllowListBackend.refreshList(); 142 return isSystemOrDefaultApp(mPowerAllowListBackend, mPackageName, mUid); 143 } 144 145 /** 146 * Gets the list of installed applications. 147 */ getInstalledApplications( Context context, IPackageManager ipm)148 public static ArraySet<ApplicationInfo> getInstalledApplications( 149 Context context, IPackageManager ipm) { 150 final ArraySet<ApplicationInfo> applications = new ArraySet<>(); 151 final UserManager um = context.getSystemService(UserManager.class); 152 for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) { 153 try { 154 @SuppressWarnings("unchecked") 155 final ParceledListSlice<ApplicationInfo> infoList = ipm.getInstalledApplications( 156 userInfo.isAdmin() ? RETRIEVE_FLAG_ADMIN : RETRIEVE_FLAG, 157 userInfo.id); 158 if (infoList != null) { 159 applications.addAll(infoList.getList()); 160 } 161 } catch (Exception e) { 162 Log.e(TAG, "getInstalledApplications() is failed", e); 163 return null; 164 } 165 } 166 // Removes the application which is disabled by the system. 167 applications.removeIf( 168 info -> info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER 169 && !info.enabled); 170 return applications; 171 } 172 173 @VisibleForTesting resetAppOptimizationMode( Context context, IPackageManager ipm, AppOpsManager aom, PowerAllowlistBackend allowlistBackend, BatteryUtils batteryUtils)174 static void resetAppOptimizationMode( 175 Context context, IPackageManager ipm, AppOpsManager aom, 176 PowerAllowlistBackend allowlistBackend, BatteryUtils batteryUtils) { 177 final ArraySet<ApplicationInfo> applications = getInstalledApplications(context, ipm); 178 if (applications == null || applications.isEmpty()) { 179 Log.w(TAG, "no data found in the getInstalledApplications()"); 180 return; 181 } 182 183 allowlistBackend.refreshList(); 184 // Resets optimization mode for each application. 185 for (ApplicationInfo info : applications) { 186 final int mode = aom.checkOpNoThrow( 187 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName); 188 @OptimizationMode 189 final int optimizationMode = getAppOptimizationMode( 190 mode, allowlistBackend.isAllowlisted(info.packageName, info.uid)); 191 // Ignores default optimized/unknown state or system/default apps. 192 if (optimizationMode == MODE_OPTIMIZED 193 || optimizationMode == MODE_UNKNOWN 194 || isSystemOrDefaultApp(allowlistBackend, info.packageName, info.uid)) { 195 continue; 196 } 197 198 // Resets to the default mode: MODE_OPTIMIZED. 199 setAppUsageStateInternal(context, MODE_OPTIMIZED, info.uid, info.packageName, 200 batteryUtils, allowlistBackend, Action.RESET); 201 } 202 } 203 getPackageName()204 String getPackageName() { 205 return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName; 206 } 207 getMode(AppOpsManager appOpsManager, int uid, String packageName)208 static int getMode(AppOpsManager appOpsManager, int uid, String packageName) { 209 return appOpsManager.checkOpNoThrow( 210 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); 211 } 212 isSystemOrDefaultApp( PowerAllowlistBackend powerAllowlistBackend, String packageName, int uid)213 static boolean isSystemOrDefaultApp( 214 PowerAllowlistBackend powerAllowlistBackend, String packageName, int uid) { 215 return powerAllowlistBackend.isSysAllowlisted(packageName) 216 || powerAllowlistBackend.isDefaultActiveApp(packageName, uid); 217 } 218 getAllowList(Context context)219 static List<String> getAllowList(Context context) { 220 return Arrays.asList(context.getResources().getStringArray( 221 R.array.config_disable_optimization_mode_apps)); 222 } 223 setAppUsageStateInternal( Context context, @OptimizationMode int mode, int uid, String packageName, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, Action action)224 private static void setAppUsageStateInternal( 225 Context context, @OptimizationMode int mode, int uid, String packageName, 226 BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, 227 Action action) { 228 if (mode == MODE_UNKNOWN) { 229 Log.d(TAG, "set unknown app optimization mode."); 230 return; 231 } 232 233 // MODE_RESTRICTED = AppOpsManager.MODE_IGNORED + !allowListed 234 // MODE_UNRESTRICTED = AppOpsManager.MODE_ALLOWED + allowListed 235 // MODE_OPTIMIZED = AppOpsManager.MODE_ALLOWED + !allowListed 236 final int appOpsManagerMode = 237 mode == MODE_RESTRICTED ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED; 238 final boolean allowListed = mode == MODE_UNRESTRICTED; 239 240 setAppOptimizationModeInternal(context, appOpsManagerMode, allowListed, uid, 241 packageName, batteryUtils, powerAllowlistBackend, action); 242 } 243 setAppOptimizationModeInternal( Context context, int appStandbyMode, boolean allowListed, int uid, String packageName, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, Action action)244 private static void setAppOptimizationModeInternal( 245 Context context, int appStandbyMode, boolean allowListed, int uid, String packageName, 246 BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, 247 Action action) { 248 final String packageNameKey = BatteryOptimizeLogUtils 249 .getPackageNameWithUserId(packageName, UserHandle.myUserId()); 250 try { 251 batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode); 252 if (allowListed) { 253 powerAllowlistBackend.addApp(packageName); 254 } else { 255 powerAllowlistBackend.removeApp(packageName); 256 } 257 } catch (Exception e) { 258 // Error cases, set standby mode as -1 for logging. 259 appStandbyMode = -1; 260 Log.e(TAG, "set OPTIMIZATION MODE failed for " + packageName, e); 261 } 262 BatteryOptimizeLogUtils.writeLog( 263 context, 264 action, 265 packageNameKey, 266 createLogEvent(appStandbyMode, allowListed)); 267 } 268 refreshState()269 private void refreshState() { 270 mPowerAllowListBackend.refreshList(); 271 mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName, mUid); 272 mMode = mAppOpsManager 273 .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName); 274 Log.d(TAG, String.format("refresh %s state, allowlisted = %s, mode = %d", 275 mPackageName, mAllowListed, mMode)); 276 } 277 createLogEvent(int appStandbyMode, boolean allowListed)278 private static String createLogEvent(int appStandbyMode, boolean allowListed) { 279 return appStandbyMode < 0 ? "Apply optimize setting ERROR" : 280 String.format("\tStandbyMode: %s, allowListed: %s, mode: %s", 281 appStandbyMode, 282 allowListed, 283 getAppOptimizationMode(appStandbyMode, allowListed)); 284 } 285 } 286