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.applications; 18 19 import android.Manifest; 20 import android.app.AlarmManager; 21 import android.app.compat.CompatChanges; 22 import android.content.Context; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.os.PowerExemptionManager; 26 import android.os.UserHandle; 27 import android.util.Log; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.util.ArrayUtils; 31 import com.android.settingslib.applications.ApplicationsState; 32 import com.android.settingslib.applications.ApplicationsState.AppEntry; 33 import com.android.settingslib.applications.ApplicationsState.AppFilter; 34 35 import java.util.List; 36 37 /** 38 * Connects app op info to the ApplicationsState. Extends {@link AppStateAppOpsBridge} to tailor 39 * to the semantics of {@link Manifest.permission#SCHEDULE_EXACT_ALARM}. 40 * Also provides app filters that can use the info. 41 */ 42 public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge { 43 private static final String SEA_PERMISSION = Manifest.permission.SCHEDULE_EXACT_ALARM; 44 private static final String UEA_PERMISSION = Manifest.permission.USE_EXACT_ALARM; 45 private static final String TAG = "AlarmsAndRemindersBridge"; 46 47 @VisibleForTesting 48 AlarmManager mAlarmManager; 49 @VisibleForTesting 50 PowerExemptionManager mPowerExemptionManager; 51 @VisibleForTesting 52 PackageManager mPackageManager; 53 AppStateAlarmsAndRemindersBridge(Context context, ApplicationsState appState, Callback callback)54 public AppStateAlarmsAndRemindersBridge(Context context, ApplicationsState appState, 55 Callback callback) { 56 super(appState, callback); 57 58 mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class); 59 mAlarmManager = context.getSystemService(AlarmManager.class); 60 mPackageManager = context.getPackageManager(); 61 } 62 isChangeEnabled(String packageName, int userId)63 private boolean isChangeEnabled(String packageName, int userId) { 64 return CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, 65 packageName, UserHandle.of(userId)); 66 } 67 isUeaChangeEnabled(String packageName, int userId)68 private boolean isUeaChangeEnabled(String packageName, int userId) { 69 return CompatChanges.isChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, packageName, 70 UserHandle.of(userId)); 71 } 72 getRequestedPermissions(String packageName, int userId)73 private String[] getRequestedPermissions(String packageName, int userId) { 74 try { 75 final PackageInfo info = mPackageManager.getPackageInfoAsUser(packageName, 76 PackageManager.GET_PERMISSIONS, userId); 77 return info.requestedPermissions; 78 } catch (PackageManager.NameNotFoundException e) { 79 Log.e(TAG, "Could not find package " + packageName, e); 80 } 81 return null; 82 } 83 84 /** 85 * Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given 86 * package and uid. 87 */ createPermissionState(String packageName, int uid)88 public AlarmsAndRemindersState createPermissionState(String packageName, int uid) { 89 final int userId = UserHandle.getUserId(uid); 90 91 final String[] requestedPermissions = getRequestedPermissions(packageName, userId); 92 93 final boolean seaRequested = ArrayUtils.contains(requestedPermissions, SEA_PERMISSION) 94 && isChangeEnabled(packageName, userId); 95 final boolean ueaRequested = ArrayUtils.contains(requestedPermissions, UEA_PERMISSION) 96 && isUeaChangeEnabled(packageName, userId); 97 98 final boolean seaGranted = mAlarmManager.hasScheduleExactAlarm(packageName, userId); 99 final boolean allowListed = mPowerExemptionManager.isAllowListed(packageName, true); 100 101 return new AlarmsAndRemindersState(seaRequested, ueaRequested, seaGranted, allowListed); 102 } 103 104 @Override updateExtraInfo(AppEntry app, String pkg, int uid)105 protected void updateExtraInfo(AppEntry app, String pkg, int uid) { 106 app.extraInfo = createPermissionState(pkg, uid); 107 } 108 109 @Override loadAllExtraInfo()110 protected void loadAllExtraInfo() { 111 final List<AppEntry> allApps = mAppSession.getAllApps(); 112 for (int i = 0; i < allApps.size(); i++) { 113 final AppEntry currentEntry = allApps.get(i); 114 updateExtraInfo(currentEntry, currentEntry.info.packageName, currentEntry.info.uid); 115 } 116 } 117 118 public static final AppFilter FILTER_CLOCK_APPS = new AppFilter() { 119 120 @Override 121 public void init() { 122 } 123 124 @Override 125 public boolean filterApp(AppEntry info) { 126 if (info.extraInfo instanceof AlarmsAndRemindersState) { 127 final AlarmsAndRemindersState state = (AlarmsAndRemindersState) info.extraInfo; 128 return state.shouldBeVisible(); 129 } 130 return false; 131 } 132 }; 133 134 /** 135 * Class to denote the state of an app regarding "Alarms and Reminders" permission. 136 * This permission state is a combination of {@link Manifest.permission#SCHEDULE_EXACT_ALARM}, 137 * {@link Manifest.permission#USE_EXACT_ALARM} and the power allowlist state. 138 */ 139 public static class AlarmsAndRemindersState { 140 private boolean mSeaPermissionRequested; 141 private boolean mUeaPermissionRequested; 142 private boolean mSeaPermissionGranted; 143 private boolean mAllowListed; 144 AlarmsAndRemindersState(boolean seaPermissionRequested, boolean ueaPermissionRequested, boolean seaPermissionGranted, boolean allowListed)145 AlarmsAndRemindersState(boolean seaPermissionRequested, boolean ueaPermissionRequested, 146 boolean seaPermissionGranted, boolean allowListed) { 147 mSeaPermissionRequested = seaPermissionRequested; 148 mUeaPermissionRequested = ueaPermissionRequested; 149 mSeaPermissionGranted = seaPermissionGranted; 150 mAllowListed = allowListed; 151 } 152 153 /** Should the app associated with this state appear on the Settings screen */ shouldBeVisible()154 public boolean shouldBeVisible() { 155 return mSeaPermissionRequested && !mUeaPermissionRequested && !mAllowListed; 156 } 157 158 /** Is the permission granted to the app associated with this state */ isAllowed()159 public boolean isAllowed() { 160 return mSeaPermissionGranted || mUeaPermissionRequested || mAllowListed; 161 } 162 } 163 } 164