• 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.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