• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.settingslib.fuelgauge;
18 
19 import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER;
20 
21 import android.app.ActivityManager;
22 import android.app.AppOpsManager;
23 import android.app.admin.DevicePolicyManager;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.os.IDeviceIdleController;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.os.UserHandle;
33 import android.provider.DeviceConfig;
34 import android.telecom.DefaultDialerManager;
35 import android.text.TextUtils;
36 import android.util.ArraySet;
37 import android.util.Log;
38 
39 import androidx.annotation.GuardedBy;
40 import androidx.annotation.VisibleForTesting;
41 
42 import com.android.internal.telephony.SmsApplication;
43 import com.android.internal.util.ArrayUtils;
44 
45 /**
46  * Handles getting/changing the allowlist for the exceptions to battery saving features.
47  */
48 public class PowerAllowlistBackend {
49 
50     private static final String TAG = "PowerAllowlistBackend";
51 
52     private static final String DEVICE_IDLE_SERVICE = "deviceidle";
53 
54     private static final String SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED =
55             "system_exempt_power_restrictions_enabled";
56     private static final boolean DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED = true;
57 
58     private static PowerAllowlistBackend sInstance;
59 
60     private final Object mAllowlistedAppsLock = new Object();
61     private final Object mSysAllowlistedAppsLock = new Object();
62     private final Object mDefaultActiveAppsLock = new Object();
63 
64     private final Context mAppContext;
65     private final IDeviceIdleController mDeviceIdleService;
66 
67     @GuardedBy("mAllowlistedAppsLock")
68     private final ArraySet<String> mAllowlistedApps = new ArraySet<>();
69     @GuardedBy("mSysAllowlistedAppsLock")
70     private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>();
71     @GuardedBy("mDefaultActiveAppsLock")
72     private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
73 
74     @VisibleForTesting
PowerAllowlistBackend(Context context)75     PowerAllowlistBackend(Context context) {
76         this(context, IDeviceIdleController.Stub.asInterface(
77                 ServiceManager.getService(DEVICE_IDLE_SERVICE)));
78     }
79 
80     @VisibleForTesting
PowerAllowlistBackend(Context context, IDeviceIdleController deviceIdleService)81     PowerAllowlistBackend(Context context, IDeviceIdleController deviceIdleService) {
82         mAppContext = context.getApplicationContext();
83         mDeviceIdleService = deviceIdleService;
84         refreshList();
85     }
86 
getAllowlistSize()87     public int getAllowlistSize() {
88         synchronized (mAllowlistedAppsLock) {
89             return mAllowlistedApps.size();
90         }
91     }
92 
93     /** Check if target package is in System allow list */
isSysAllowlisted(String pkg)94     public boolean isSysAllowlisted(String pkg) {
95         synchronized (mSysAllowlistedAppsLock) {
96             return mSysAllowlistedApps.contains(pkg);
97         }
98     }
99 
100     /** Check if target package is in allow list */
isAllowlisted(String pkg, int uid)101     public boolean isAllowlisted(String pkg, int uid) {
102         synchronized (mAllowlistedAppsLock) {
103             if (mAllowlistedApps.contains(pkg)) {
104                 return true;
105             }
106         }
107         if (isDefaultActiveApp(pkg, uid)) {
108             return true;
109         }
110 
111         return false;
112     }
113 
114     /** Check if it is default active app in multiple area */
isDefaultActiveApp(String pkg, int uid)115     public boolean isDefaultActiveApp(String pkg, int uid) {
116         // Additionally, check if pkg is default dialer/sms. They are considered essential apps and
117         // should be automatically allowlisted (otherwise user may be able to set restriction on
118         // them, leading to bad device behavior.)
119 
120         synchronized (mDefaultActiveAppsLock) {
121             if (mDefaultActiveApps.contains(pkg)) {
122                 return true;
123             }
124         }
125 
126         final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService(
127                 DevicePolicyManager.class);
128         if (devicePolicyManager.packageHasActiveAdmins(pkg)) {
129             return true;
130         }
131 
132         final AppOpsManager appOpsManager = mAppContext.getSystemService(AppOpsManager.class);
133         if (isSystemExemptFlagEnabled() && appOpsManager.checkOpNoThrow(
134                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS, uid, pkg)
135                 == AppOpsManager.MODE_ALLOWED) {
136             return true;
137         }
138 
139         // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
140         final int userId = UserHandle.getUserId(uid);
141         if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
142             return true;
143         }
144 
145         return false;
146     }
147 
isSystemExemptFlagEnabled()148     private static boolean isSystemExemptFlagEnabled() {
149         return DeviceConfig.getBoolean(
150                 NAMESPACE_ACTIVITY_MANAGER,
151                 SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED,
152                 DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED);
153     }
154 
155     /** Check if target package is in allow list except idle app */
isAllowlistedExceptIdle(String pkg)156     public boolean isAllowlistedExceptIdle(String pkg) {
157         try {
158             return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg);
159         } catch (RemoteException e) {
160             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
161             return true;
162         }
163     }
164 
165     /**
166      * Check if target package is in allow list except idle app
167      *
168      * @param pkgs a list of packageName
169      * @return true when one of package is in allow list
170      */
isAllowlisted(String[] pkgs, int uid)171     public boolean isAllowlisted(String[] pkgs, int uid) {
172         if (ArrayUtils.isEmpty(pkgs)) {
173             return false;
174         }
175         for (String pkg : pkgs) {
176             if (isAllowlisted(pkg, uid)) {
177                 return true;
178             }
179         }
180 
181         return false;
182     }
183 
184     /**
185      * Add app into power save allow list
186      *
187      * @param pkg packageName of the app
188      */
addApp(String pkg)189     public void addApp(String pkg) {
190         addApp(pkg, Process.INVALID_UID);
191     }
192 
193     /**
194      * Add app into power save allow list
195      *
196      * @param pkg packageName of the app
197      * @param uid uid of the app
198      */
addApp(String pkg, int uid)199     public synchronized void addApp(String pkg, int uid) {
200         try {
201             if (android.app.Flags.appRestrictionsApi()) {
202                 if (uid == Process.INVALID_UID) {
203                     uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
204                 }
205                 final boolean wasInList = isAllowlisted(pkg, uid);
206 
207                 if (!wasInList) {
208                     mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
209                             pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
210                             true, ActivityManager.RESTRICTION_REASON_USER,
211                             "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0);
212                 }
213             }
214 
215             mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
216             synchronized (mAllowlistedAppsLock) {
217                 mAllowlistedApps.add(pkg);
218             }
219         } catch (RemoteException e) {
220             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
221         } catch (NameNotFoundException e) {
222             Log.w(TAG, "Unable to find package", e);
223         }
224     }
225 
226     /**
227      * Remove package from power save allow list
228      *
229      * @param pkg packageName of the app
230      */
removeApp(String pkg)231     public void removeApp(String pkg) {
232         removeApp(pkg, Process.INVALID_UID);
233     }
234 
235     /**
236      * Remove package from power save allow list.
237      *
238      * @param pkg packageName of the app
239      * @param uid uid of the app
240      */
removeApp(String pkg, int uid)241     public synchronized void removeApp(String pkg, int uid) {
242         try {
243             if (android.app.Flags.appRestrictionsApi()) {
244                 if (uid == Process.INVALID_UID) {
245                     uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
246                 }
247                 final boolean wasInList = isAllowlisted(pkg, uid);
248                 if (wasInList) {
249                     mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
250                             pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
251                             false, ActivityManager.RESTRICTION_REASON_USER,
252                             "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0L);
253                 }
254             }
255 
256             mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
257             synchronized (mAllowlistedAppsLock) {
258                 mAllowlistedApps.remove(pkg);
259             }
260         } catch (RemoteException e) {
261             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
262         } catch (NameNotFoundException e) {
263             Log.w(TAG, "Unable to find package", e);
264         }
265     }
266 
267     /** Refresh all of lists */
268     @VisibleForTesting
refreshList()269     public synchronized void refreshList() {
270         synchronized (mSysAllowlistedAppsLock) {
271             mSysAllowlistedApps.clear();
272         }
273         synchronized (mAllowlistedAppsLock) {
274             mAllowlistedApps.clear();
275         }
276         synchronized (mDefaultActiveAppsLock) {
277             mDefaultActiveApps.clear();
278         }
279         if (mDeviceIdleService == null) {
280             return;
281         }
282         try {
283             final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist();
284             synchronized (mAllowlistedAppsLock) {
285                 for (String app : allowlistedApps) {
286                     mAllowlistedApps.add(app);
287                 }
288             }
289             final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist();
290             synchronized (mSysAllowlistedAppsLock) {
291                 for (String app : sysAllowlistedApps) {
292                     mSysAllowlistedApps.add(app);
293                 }
294             }
295             final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
296                     PackageManager.FEATURE_TELEPHONY);
297             final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext,
298                     true /* updateIfNeeded */);
299             final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(
300                     mAppContext);
301 
302             if (hasTelephony) {
303                 if (defaultSms != null) {
304                     synchronized (mDefaultActiveAppsLock) {
305                         mDefaultActiveApps.add(defaultSms.getPackageName());
306                     }
307                 }
308                 if (!TextUtils.isEmpty(defaultDialer)) {
309                     synchronized (mDefaultActiveAppsLock) {
310                         mDefaultActiveApps.add(defaultDialer);
311                     }
312                 }
313             }
314         } catch (Exception e) {
315             Log.e(TAG, "Failed to invoke refreshList()", e);
316         }
317     }
318 
319     /** Get the {@link PowerAllowlistBackend} instance */
getInstance(Context context)320     public static PowerAllowlistBackend getInstance(Context context) {
321         synchronized (PowerAllowlistBackend.class) {
322             if (sInstance == null) {
323                 sInstance = new PowerAllowlistBackend(context);
324             }
325             return sInstance;
326         }
327     }
328 
329     /** Testing only. Reset the instance to avoid tests affecting each other. */
330     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
resetInstance()331     public static void resetInstance() {
332         synchronized (PowerAllowlistBackend.class) {
333             sInstance = null;
334         }
335     }
336 }
337