• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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