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