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.AppOpsManager;
22 import android.app.admin.DevicePolicyManager;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 import android.os.IDeviceIdleController;
27 import android.os.RemoteException;
28 import android.os.ServiceManager;
29 import android.provider.DeviceConfig;
30 import android.telecom.DefaultDialerManager;
31 import android.text.TextUtils;
32 import android.util.ArraySet;
33 import android.util.Log;
34 
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.internal.telephony.SmsApplication;
38 import com.android.internal.util.ArrayUtils;
39 
40 /**
41  * Handles getting/changing the allowlist for the exceptions to battery saving features.
42  */
43 public class PowerAllowlistBackend {
44 
45     private static final String TAG = "PowerAllowlistBackend";
46 
47     private static final String DEVICE_IDLE_SERVICE = "deviceidle";
48 
49     private static final String SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED =
50             "system_exempt_power_restrictions_enabled";
51     private static final boolean DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED = true;
52 
53     private static PowerAllowlistBackend sInstance;
54 
55     private final Context mAppContext;
56     private final IDeviceIdleController mDeviceIdleService;
57     private final ArraySet<String> mAllowlistedApps = new ArraySet<>();
58     private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>();
59     private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
60 
PowerAllowlistBackend(Context context)61     public PowerAllowlistBackend(Context context) {
62         this(context, IDeviceIdleController.Stub.asInterface(
63                 ServiceManager.getService(DEVICE_IDLE_SERVICE)));
64     }
65 
66     @VisibleForTesting
PowerAllowlistBackend(Context context, IDeviceIdleController deviceIdleService)67     PowerAllowlistBackend(Context context, IDeviceIdleController deviceIdleService) {
68         mAppContext = context.getApplicationContext();
69         mDeviceIdleService = deviceIdleService;
70         refreshList();
71     }
72 
getAllowlistSize()73     public int getAllowlistSize() {
74         return mAllowlistedApps.size();
75     }
76 
77     /**
78     * Check if target package is in System allow list
79     */
isSysAllowlisted(String pkg)80     public boolean isSysAllowlisted(String pkg) {
81         return mSysAllowlistedApps.contains(pkg);
82     }
83 
84     /**
85      * Check if target package is in allow list
86      */
isAllowlisted(String pkg, int uid)87     public boolean isAllowlisted(String pkg, int uid) {
88         if (mAllowlistedApps.contains(pkg)) {
89             return true;
90         }
91 
92         if (isDefaultActiveApp(pkg, uid)) {
93             return true;
94         }
95 
96         return false;
97     }
98 
99     /**
100      * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..)
101      */
isDefaultActiveApp(String pkg, int uid)102     public boolean isDefaultActiveApp(String pkg, int uid) {
103         // Additionally, check if pkg is default dialer/sms. They are considered essential apps and
104         // should be automatically allowlisted (otherwise user may be able to set restriction on
105         // them, leading to bad device behavior.)
106 
107         if (mDefaultActiveApps.contains(pkg)) {
108             return true;
109         }
110 
111         final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService(
112                 DevicePolicyManager.class);
113         if (devicePolicyManager.packageHasActiveAdmins(pkg)) {
114             return true;
115         }
116 
117         final AppOpsManager appOpsManager = mAppContext.getSystemService(AppOpsManager.class);
118         if (isSystemExemptFlagEnabled() && appOpsManager.checkOpNoThrow(
119                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS, uid, pkg)
120                 == AppOpsManager.MODE_ALLOWED) {
121             return true;
122         }
123 
124         return false;
125     }
126 
isSystemExemptFlagEnabled()127     private static boolean isSystemExemptFlagEnabled() {
128         return DeviceConfig.getBoolean(
129                 NAMESPACE_ACTIVITY_MANAGER,
130                 SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED,
131                 DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED);
132     }
133 
134     /**
135      * Check if target package is in allow list except idle app
136      */
isAllowlistedExceptIdle(String pkg)137     public boolean isAllowlistedExceptIdle(String pkg) {
138         try {
139             return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg);
140         } catch (RemoteException e) {
141             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
142             return true;
143         }
144     }
145 
146     /**
147      *
148      * @param pkgs a list of packageName
149      * @return true when one of package is in allow list
150      */
isAllowlisted(String[] pkgs, int uid)151     public boolean isAllowlisted(String[] pkgs, int uid) {
152         if (ArrayUtils.isEmpty(pkgs)) {
153             return false;
154         }
155         for (String pkg : pkgs) {
156             if (isAllowlisted(pkg, uid)) {
157                 return true;
158             }
159         }
160 
161         return false;
162     }
163 
164     /**
165      * Add app into power save allow list.
166      * @param pkg packageName
167      */
addApp(String pkg)168     public void addApp(String pkg) {
169         try {
170             mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
171             mAllowlistedApps.add(pkg);
172         } catch (RemoteException e) {
173             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
174         }
175     }
176 
177     /**
178      * Remove package from power save allow list.
179      * @param pkg
180      */
removeApp(String pkg)181     public void removeApp(String pkg) {
182         try {
183             mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
184             mAllowlistedApps.remove(pkg);
185         } catch (RemoteException e) {
186             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
187         }
188     }
189 
190     /**
191      * Refresh all of lists
192      */
193     @VisibleForTesting
refreshList()194     public void refreshList() {
195         mSysAllowlistedApps.clear();
196         mAllowlistedApps.clear();
197         mDefaultActiveApps.clear();
198         if (mDeviceIdleService == null) {
199             return;
200         }
201         try {
202             final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist();
203             for (String app : allowlistedApps) {
204                 mAllowlistedApps.add(app);
205             }
206             final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist();
207             for (String app : sysAllowlistedApps) {
208                 mSysAllowlistedApps.add(app);
209             }
210             final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
211                     PackageManager.FEATURE_TELEPHONY);
212             final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext,
213                     true /* updateIfNeeded */);
214             final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(
215                     mAppContext);
216 
217             if (hasTelephony) {
218                 if (defaultSms != null) {
219                     mDefaultActiveApps.add(defaultSms.getPackageName());
220                 }
221                 if (!TextUtils.isEmpty(defaultDialer)) {
222                     mDefaultActiveApps.add(defaultDialer);
223                 }
224             }
225         } catch (RemoteException e) {
226             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
227         }
228     }
229 
230     /**
231      * @param context
232      * @return a PowerAllowlistBackend object
233      */
getInstance(Context context)234     public static PowerAllowlistBackend getInstance(Context context) {
235         if (sInstance == null) {
236             sInstance = new PowerAllowlistBackend(context);
237         }
238         return sInstance;
239     }
240 
241 }
242