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