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