From 38d5d66c80859cbc03f76fe3a8f73f467aaa4344 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Fri, 2 Apr 2021 00:13:43 -0700 Subject: [PATCH] More changes for the exact alarm permission Adding a deny list option, which changes the default grant state of the permission to denied. This means now alarm manager is the source of truth of the permission state and exposes an API that clients like Settings should call. Also using the same API to cap the minimum standby-bucket for apps with this permission to WORKING_SET. Adding an app to the deny list may mean revocation of its permission, in which case its exact alarms will be removed. Meanwhile, performed some long needed refactoring on the remove methods, so they re-use common code and are easier to maintain. Also, changing strings for the Settings UI to be clearer. Test: atest FrameworksMockingServicesTests:com.android.server.alarm atest CtsAlarmManagerTestCases Bug: 171306433 Bug: 130444055 Bug: 181894091 Bug: 181152252 Change-Id: I9410f988343800b7322963fecf3eafac9561b093 --- .../java/android/app/AlarmManager.java | 26 +- .../java/android/app/IAlarmManager.aidl | 2 +- .../server/alarm/AlarmManagerService.java | 372 ++++++++---------- .../server/usage/AppStandbyController.java | 11 + core/api/test-current.txt | 4 + packages/SettingsLib/res/values/strings.xml | 30 +- .../android/server/AlarmManagerInternal.java | 9 + .../server/alarm/AlarmManagerServiceTest.java | 341 ++++++++++++---- .../usage/AppStandbyControllerTests.java | 40 +- 9 files changed, 549 insertions(+), 286 deletions(-) diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 3ea1922547d59..88f3df8fa2c4b 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -23,6 +23,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; @@ -1132,12 +1133,31 @@ public class AlarmManager { } /** - * Called to check if the caller has permission to use alarms set via {@link } - * @return + * Called to check if the caller has the permission + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM}. + * + * Apps can start {@link android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM} to + * request this from the user. + * + * @return {@code true} if the caller has the permission, {@code false} otherwise. + * @see android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM */ public boolean canScheduleExactAlarms() { + return hasScheduleExactAlarm(mContext.getOpPackageName(), mContext.getUserId()); + } + + /** + * Called to check if the given package in the given user has the permission + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM}. + * + *

Note: This is only for use by system components. + * + * @hide + */ + @TestApi + public boolean hasScheduleExactAlarm(@NonNull String packageName, int userId) { try { - return mService.canScheduleExactAlarms(); + return mService.hasScheduleExactAlarm(packageName, userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl index a10cbbee19d1e..cd7c1e8ab4fde 100644 --- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl +++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl @@ -41,6 +41,6 @@ interface IAlarmManager { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) AlarmManager.AlarmClockInfo getNextAlarmClock(int userId); long currentNetworkTimeMillis(); - boolean canScheduleExactAlarms(); + boolean hasScheduleExactAlarm(String packageName, int userId); int getConfigVersion(); } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 08e1a952603c0..7a3614172dff9 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -126,6 +126,8 @@ import com.android.server.SystemServiceManager; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; +import libcore.util.EmptyArray; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.text.SimpleDateFormat; @@ -139,6 +141,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Random; +import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; import java.util.function.Predicate; @@ -194,6 +197,7 @@ public class AlarmManagerService extends SystemService { DeviceIdleInternal mLocalDeviceIdleController; private UsageStatsManagerInternal mUsageStatsManagerInternal; private ActivityManagerInternal mActivityManagerInternal; + private PackageManagerInternal mPackageManagerInternal; final Object mLock = new Object(); @@ -262,10 +266,12 @@ public class AlarmManagerService extends SystemService { interface Stats { int REORDER_ALARMS_FOR_STANDBY = 0; + int HAS_SCHEDULE_EXACT_ALARM = 1; } private final StatLogger mStatLogger = new StatLogger("Alarm manager stats", new String[]{ "REORDER_ALARMS_FOR_STANDBY", + "HAS_SCHEDULE_EXACT_ALARM", }); BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic(); @@ -383,6 +389,9 @@ public class AlarmManagerService extends SystemService { */ @VisibleForTesting final class Constants implements DeviceConfig.OnPropertiesChangedListener { + @VisibleForTesting + static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250; + // Key names stored in the settings value. @VisibleForTesting static final String KEY_MIN_FUTURITY = "min_futurity"; @@ -437,6 +446,8 @@ public class AlarmManagerService extends SystemService { static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps"; @VisibleForTesting static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay"; + @VisibleForTesting + static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; @@ -537,6 +548,12 @@ public class AlarmManagerService extends SystemService { */ public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY; + /** + * Set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for + * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. + */ + public Set EXACT_ALARM_DENY_LIST = Collections.emptySet(); + private long mLastAllowWhileIdleWhitelistDuration = -1; private int mVersion = 0; @@ -686,6 +703,21 @@ public class AlarmManagerService extends SystemService { PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY, DEFAULT_PRIORITY_ALARM_DELAY); break; + case KEY_EXACT_ALARM_DENY_LIST: + final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST, + ""); + final String[] values = rawValue.isEmpty() + ? EmptyArray.STRING + : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1); + if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) { + Slog.w(TAG, "Deny list too long, truncating to " + + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements."); + updateExactAlarmDenyList( + Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE)); + } else { + updateExactAlarmDenyList(values); + } + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -699,6 +731,21 @@ public class AlarmManagerService extends SystemService { } } + private void updateExactAlarmDenyList(String[] newDenyList) { + if (newDenyList.length == 0) { + EXACT_ALARM_DENY_LIST = Collections.emptySet(); + } else { + final Set oldSet = EXACT_ALARM_DENY_LIST; + final Set newlyAdded = new ArraySet<>(newDenyList); + EXACT_ALARM_DENY_LIST = Collections.unmodifiableSet(new ArraySet<>(newlyAdded)); + newlyAdded.removeAll(oldSet); + if (newlyAdded.size() > 0) { + mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_CHANGED, newlyAdded) + .sendToTarget(); + } + } + } + private void migrateAlarmsToNewStoreLocked() { final AlarmStore newStore = LAZY_BATCHING ? new LazyAlarmStore() : new BatchingAlarmStore(); @@ -841,6 +888,9 @@ public class AlarmManagerService extends SystemService { TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw); pw.println(); + pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST); + pw.println(); + pw.decreaseIndent(); } @@ -1428,8 +1478,9 @@ public class AlarmManagerService extends SystemService { mInjector.setKernelTime(systemBuildTime); } + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); // Determine SysUI's uid - mSystemUiUid = mInjector.getSystemUiUid(); + mSystemUiUid = mInjector.getSystemUiUid(mPackageManagerInternal); if (mSystemUiUid <= 0) { Slog.wtf(TAG, "SysUI package not found!"); } @@ -1504,10 +1555,7 @@ public class AlarmManagerService extends SystemService { if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) { return; } - final int mode = mAppOpsService.checkOperation(op, uid, - packageName); - if (mode != AppOpsManager.MODE_ALLOWED - && mode != AppOpsManager.MODE_DEFAULT) { + if (!hasScheduleExactAlarmInternal(packageName, uid)) { mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS, uid, 0, packageName).sendToTarget(); } @@ -2028,6 +2076,11 @@ public class AlarmManagerService extends SystemService { mHandler.obtainMessage(AlarmHandler.REMOVE_FOR_CANCELED, pi).sendToTarget(); } + @Override + public boolean hasScheduleExactAlarm(String packageName, int uid) { + return hasScheduleExactAlarmInternal(packageName, uid); + } + @Override public void registerInFlightListener(InFlightListener callback) { synchronized (mLock) { @@ -2036,6 +2089,21 @@ public class AlarmManagerService extends SystemService { } } + boolean hasScheduleExactAlarmInternal(String packageName, int uid) { + final long start = mStatLogger.getTime(); + // No locking needed as EXACT_ALARM_DENY_LIST is immutable. + final boolean isOnDenyList = mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); + if (isOnDenyList && mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, + packageName) != AppOpsManager.MODE_ALLOWED) { + return false; + } + final boolean has = PermissionChecker.checkPermissionForPreflight(getContext(), + Manifest.permission.SCHEDULE_EXACT_ALARM, -1, uid, packageName) + == PermissionChecker.PERMISSION_GRANTED; + mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start); + return has; + } + /** * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact, * allow-while-idle alarms. @@ -2056,11 +2124,16 @@ public class AlarmManagerService extends SystemService { int type, long triggerAtTime, long windowLength, long interval, int flags, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) { - final int callingUid = Binder.getCallingUid(); + final int callingUid = mInjector.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); // make sure the caller is not lying about which package should be blamed for // wakelock time spent in alarm delivery - mAppOps.checkPackage(callingUid, callingPackage); + if (callingUid != mPackageManagerInternal.getPackageUid(callingPackage, 0, + callingUserId)) { + throw new SecurityException("Package " + callingPackage + + " does not belong to the calling uid " + callingUid); + } final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0; final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT); @@ -2077,7 +2150,7 @@ public class AlarmManagerService extends SystemService { final boolean needsPermission; boolean lowerQuota; if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, - callingPackage, UserHandle.getUserHandleForUid(callingUid))) { + callingPackage, UserHandle.of(callingUserId))) { needsPermission = exact; lowerQuota = !exact; idleOptions = exact ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle(); @@ -2086,7 +2159,7 @@ public class AlarmManagerService extends SystemService { lowerQuota = allowWhileIdle; idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null; } - if (needsPermission && !canScheduleExactAlarms()) { + if (needsPermission && !hasScheduleExactAlarmInternal(callingPackage, callingUid)) { if (alarmClock != null || !isExemptFromExactAlarmPermission(callingUid)) { final String errorMessage = "Caller " + callingPackage + " needs to hold " + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set " @@ -2165,10 +2238,18 @@ public class AlarmManagerService extends SystemService { } @Override - public boolean canScheduleExactAlarms() { - return PermissionChecker.checkCallingOrSelfPermissionForPreflight(getContext(), - Manifest.permission.SCHEDULE_EXACT_ALARM) - == PermissionChecker.PERMISSION_GRANTED; + public boolean hasScheduleExactAlarm(String packageName, int userId) { + final int callingUid = mInjector.getCallingUid(); + if (UserHandle.getUserId(callingUid) != userId) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, "hasScheduleExactAlarm"); + } + final int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId); + if (callingUid != uid && !UserHandle.isCore(callingUid)) { + throw new SecurityException("Uid " + callingUid + + " cannot query hasScheduleExactAlarm for uid " + uid); + } + return (uid > 0) ? hasScheduleExactAlarmInternal(packageName, uid) : false; } @Override @@ -3071,28 +3152,64 @@ public class AlarmManagerService extends SystemService { } } + /** + * Called when some packages are added to the {@link Constants#EXACT_ALARM_DENY_LIST}, as this + * may cause some of them to lose their permission. + * + * Note that these packages don't need to be installed on the device, but if they do have an + * exact alarm scheduled and they lose the permission, this alarm will be canceled. + * + * This is not expected to get called frequently. + */ + void handlePackagesAddedToExactAlarmsDenyListLocked(ArraySet packageNames) { + Slog.w(TAG, "Packages " + packageNames + " added to the exact alarm deny list."); + final Predicate whichAlarms = a -> { + if (!packageNames.contains(a.packageName) || a.windowLength != 0) { + return false; + } + if (!CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, + a.packageName, UserHandle.getUserHandleForUid(a.uid))) { + return false; + } + if (a.alarmClock == null && isExemptFromExactAlarmPermission(a.uid)) { + return false; + } + return !hasScheduleExactAlarmInternal(a.packageName, a.uid); + }; + removeAlarmsInternalLocked(whichAlarms); + } + /** * Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms * that the app is no longer eligible to use. + * + * This is not expected to get called frequently. * TODO (b/179541791): Add revocation history to dumpsys. */ void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) { - if (isExemptFromExactAlarmPermission(uid)) { - return; - } + Slog.w(TAG, "Package " + packageName + ", uid " + uid + " lost SCHEDULE_EXACT_ALARM!"); if (!CompatChanges.isChangeEnabled( AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, packageName, UserHandle.getUserHandleForUid(uid))) { return; } - final Predicate whichAlarms = - a -> (a.uid == uid && a.packageName.equals(packageName) - && a.windowLength == AlarmManager.WINDOW_EXACT); - final ArrayList removed = mAlarmStore.remove(whichAlarms); - final boolean didRemove = !removed.isEmpty(); + final Predicate whichAlarms = a -> { + if (a.uid == uid && a.packageName.equals(packageName) && a.windowLength == 0) { + return a.alarmClock != null || !isExemptFromExactAlarmPermission(uid); + } + return false; + }; + removeAlarmsInternalLocked(whichAlarms); + } + + private void removeAlarmsInternalLocked(Predicate whichAlarms) { + final ArrayList removedAlarms = mAlarmStore.remove(whichAlarms); + final boolean didRemove = !removedAlarms.isEmpty(); if (didRemove) { - decrementAlarmCount(uid, removed.size()); + for (final Alarm removed : removedAlarms) { + decrementAlarmCount(removed.uid, 1); + } } for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { @@ -3119,21 +3236,22 @@ public class AlarmManagerService extends SystemService { } if (didRemove) { + boolean idleUntilUpdated = false; + if (mPendingIdleUntil != null && whichAlarms.test(mPendingIdleUntil)) { + mPendingIdleUntil = null; + idleUntilUpdated = true; + } if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) { mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); if (mPendingIdleUntil != null) { - final boolean idleUntilUpdated = mAlarmStore.updateAlarmDeliveries(alarm -> { - if (alarm != mPendingIdleUntil) { - return false; - } - return adjustIdleUntilTime(alarm); - }); - if (idleUntilUpdated) { - mAlarmStore.updateAlarmDeliveries( - alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm)); - } + idleUntilUpdated |= mAlarmStore.updateAlarmDeliveries(alarm -> + (alarm == mPendingIdleUntil && adjustIdleUntilTime(alarm))); } } + if (idleUntilUpdated) { + mAlarmStore.updateAlarmDeliveries( + alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm)); + } rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); } @@ -3147,63 +3265,7 @@ public class AlarmManagerService extends SystemService { } return; } - - final ArrayList removedAlarms = mAlarmStore.remove( - a -> a.matches(operation, directReceiver)); - for (final Alarm removed : removedAlarms) { - decrementAlarmCount(removed.uid, 1); - } - final boolean didRemove = !removedAlarms.isEmpty(); - - for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { - final ArrayList alarmsForUid = mPendingBackgroundAlarms.valueAt(i); - for (int j = alarmsForUid.size() - 1; j >= 0; j--) { - final Alarm alarm = alarmsForUid.get(j); - if (alarm.matches(operation, directReceiver)) { - // Don't set didRemove, since this doesn't impact the scheduled alarms. - alarmsForUid.remove(j); - decrementAlarmCount(alarm.uid, 1); - } - } - if (alarmsForUid.size() == 0) { - mPendingBackgroundAlarms.removeAt(i); - } - } - for (int i = mPendingNonWakeupAlarms.size() - 1; i >= 0; i--) { - final Alarm a = mPendingNonWakeupAlarms.get(i); - if (a.matches(operation, directReceiver)) { - // Don't set didRemove, since this doesn't impact the scheduled alarms. - mPendingNonWakeupAlarms.remove(i); - decrementAlarmCount(a.uid, 1); - } - } - if (didRemove) { - if (DEBUG_BATCH) { - Slog.v(TAG, "remove(operation) changed bounds; rebatching"); - } - boolean idleUntilUpdated = false; - if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) { - mPendingIdleUntil = null; - idleUntilUpdated = true; - } - if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) { - mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); - if (mPendingIdleUntil != null) { - idleUntilUpdated |= mAlarmStore.updateAlarmDeliveries(alarm -> { - if (alarm != mPendingIdleUntil) { - return false; - } - return adjustIdleUntilTime(alarm); - }); - } - } - if (idleUntilUpdated) { - mAlarmStore.updateAlarmDeliveries( - alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm)); - } - rescheduleKernelAlarmsLocked(); - updateNextAlarmClockLocked(); - } + removeAlarmsInternalLocked(a -> a.matches(operation, directReceiver)); } void removeLocked(final int uid) { @@ -3211,55 +3273,7 @@ public class AlarmManagerService extends SystemService { // If a force-stop occurs for a system-uid package, ignore it. return; } - - final ArrayList removed = mAlarmStore.remove(a -> a.uid == uid); - final boolean didRemove = !removed.isEmpty(); - if (didRemove) { - decrementAlarmCount(uid, removed.size()); - } - - for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { - final ArrayList alarmsForUid = mPendingBackgroundAlarms.valueAt(i); - for (int j = alarmsForUid.size() - 1; j >= 0; j--) { - if (alarmsForUid.get(j).uid == uid) { - alarmsForUid.remove(j); - decrementAlarmCount(uid, 1); - } - } - if (alarmsForUid.size() == 0) { - mPendingBackgroundAlarms.removeAt(i); - } - } - // If we're currently using this app's alarms to come out of doze, - // make sure to reset to any remaining WAKE_FROM_IDLE alarms. - boolean idleUntilUpdated = false; - if (mNextWakeFromIdle != null && mNextWakeFromIdle.uid == uid) { - mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); - if (mPendingIdleUntil != null) { - idleUntilUpdated |= mAlarmStore.updateAlarmDeliveries(alarm -> { - if (alarm != mPendingIdleUntil) { - return false; - } - return adjustIdleUntilTime(alarm); - }); - } - } - if (mPendingIdleUntil != null && mPendingIdleUntil.uid == uid) { - // Should never happen - only the system uid is allowed to set idle-until alarms - Slog.wtf(TAG, "Removed app uid " + uid + " set idle-until alarm!"); - mPendingIdleUntil = null; - idleUntilUpdated = true; - } - if (idleUntilUpdated) { - mAlarmStore.updateAlarmDeliveries(alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm)); - } - if (didRemove) { - if (DEBUG_BATCH) { - Slog.v(TAG, "remove(uid) changed bounds; rebatching"); - } - rescheduleKernelAlarmsLocked(); - updateNextAlarmClockLocked(); - } + removeAlarmsInternalLocked(a -> a.uid == uid); } void removeLocked(final String packageName) { @@ -3270,50 +3284,7 @@ public class AlarmManagerService extends SystemService { } return; } - - final ArrayList removed = mAlarmStore.remove(a -> a.matches(packageName)); - final boolean didRemove = !removed.isEmpty(); - if (didRemove) { - decrementAlarmCount(removed.get(0).uid, removed.size()); - } - - for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { - final ArrayList alarmsForUid = mPendingBackgroundAlarms.valueAt(i); - for (int j = alarmsForUid.size() - 1; j >= 0; j--) { - final Alarm alarm = alarmsForUid.get(j); - if (alarm.matches(packageName)) { - alarmsForUid.remove(j); - decrementAlarmCount(alarm.uid, 1); - } - } - if (alarmsForUid.size() == 0) { - mPendingBackgroundAlarms.removeAt(i); - } - } - // If we're currently using this app's alarms to come out of doze, - // make sure to reset to any remaining WAKE_FROM_IDLE alarms. - if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(packageName)) { - mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); - if (mPendingIdleUntil != null) { - final boolean updated = mAlarmStore.updateAlarmDeliveries(alarm -> { - if (alarm != mPendingIdleUntil) { - return false; - } - return adjustIdleUntilTime(alarm); - }); - if (updated) { - mAlarmStore.updateAlarmDeliveries( - alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm)); - } - } - } - if (didRemove) { - if (DEBUG_BATCH) { - Slog.v(TAG, "remove(package) changed bounds; rebatching"); - } - rescheduleKernelAlarmsLocked(); - updateNextAlarmClockLocked(); - } + removeAlarmsInternalLocked(a -> a.matches(packageName)); } // Only called for ephemeral apps @@ -3324,28 +3295,7 @@ public class AlarmManagerService extends SystemService { } final Predicate whichAlarms = (a) -> (a.uid == uid && mActivityManagerInternal.isAppStartModeDisabled(uid, a.packageName)); - final ArrayList removed = mAlarmStore.remove(whichAlarms); - final boolean didRemove = !removed.isEmpty(); - if (didRemove) { - decrementAlarmCount(uid, removed.size()); - } - - for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { - if (mPendingBackgroundAlarms.keyAt(i) == uid) { - final ArrayList toRemove = mPendingBackgroundAlarms.valueAt(i); - if (toRemove != null) { - decrementAlarmCount(uid, toRemove.size()); - } - mPendingBackgroundAlarms.removeAt(i); - } - } - if (didRemove) { - if (DEBUG_BATCH) { - Slog.v(TAG, "remove(package) changed bounds; rebatching"); - } - rescheduleKernelAlarmsLocked(); - updateNextAlarmClockLocked(); - } + removeAlarmsInternalLocked(whichAlarms); } void removeUserLocked(int userHandle) { @@ -3720,6 +3670,10 @@ public class AlarmManagerService extends SystemService { } } + int getCallingUid() { + return Binder.getCallingUid(); + } + long getNextAlarm(int type) { return AlarmManagerService.getNextAlarm(mNativeData, type); } @@ -3751,8 +3705,7 @@ public class AlarmManagerService extends SystemService { return pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*alarm*"); } - int getSystemUiUid() { - PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); + int getSystemUiUid(PackageManagerInternal pm) { return pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(), MATCH_SYSTEM_ONLY, USER_SYSTEM); } @@ -3962,6 +3915,7 @@ public class AlarmManagerService extends SystemService { public static final int CHARGING_STATUS_CHANGED = 6; public static final int REMOVE_FOR_CANCELED = 7; public static final int REMOVE_EXACT_ALARMS = 8; + public static final int EXACT_ALARM_DENY_LIST_CHANGED = 9; AlarmHandler() { super(Looper.myLooper()); @@ -4048,7 +4002,11 @@ public class AlarmManagerService extends SystemService { removeExactAlarmsOnPermissionRevokedLocked(uid, packageName); } break; - + case EXACT_ALARM_DENY_LIST_CHANGED: + synchronized (mLock) { + handlePackagesAddedToExactAlarmsDenyListLocked((ArraySet) msg.obj); + } + break; default: // nope, just ignore it break; diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index a62e258153a03..d17df63c43639 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -111,6 +111,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; +import com.android.server.AlarmManagerInternal; import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -1191,6 +1192,10 @@ public class AppStandbyController if (mInjector.isWellbeingPackage(packageName)) { return STANDBY_BUCKET_WORKING_SET; } + + if (mInjector.hasScheduleExactAlarm(packageName, UserHandle.getUid(userId, appId))) { + return STANDBY_BUCKET_WORKING_SET; + } } // Check this last, as it can be the most expensive check @@ -2007,6 +2012,7 @@ public class AppStandbyController private PowerManager mPowerManager; private IDeviceIdleController mDeviceIdleController; private CrossProfileAppsInternal mCrossProfileAppsInternal; + private AlarmManagerInternal mAlarmManagerInternal; int mBootPhase; /** * The minimum amount of time required since the last user interaction before an app can be @@ -2047,6 +2053,7 @@ public class AppStandbyController mBatteryManager = mContext.getSystemService(BatteryManager.class); mCrossProfileAppsInternal = LocalServices.getService( CrossProfileAppsInternal.class); + mAlarmManagerInternal = LocalServices.getService(AlarmManagerInternal.class); final ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -2110,6 +2117,10 @@ public class AppStandbyController return mWellbeingApp != null && mWellbeingApp.equals(packageName); } + boolean hasScheduleExactAlarm(String packageName, int uid) { + return mAlarmManagerInternal.hasScheduleExactAlarm(packageName, uid); + } + void updatePowerWhitelistCache() { try { // Don't call out to DeviceIdleController with the lock held. diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 9931bf9d78c23..f207c039d5a28 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -204,6 +204,10 @@ package android.app { method public void onTaskRemovalStarted(int); } + public class AlarmManager { + method public boolean hasScheduleExactAlarm(@NonNull String, int); + } + public class AppDetailsActivity extends android.app.Activity { ctor public AppDetailsActivity(); } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index bcc22f6605a54..bba1e86c437dc 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1275,18 +1275,32 @@ Alarms and reminders - Allow to set alarms or reminders + Allow setting alarms and reminders - Alarms and reminders + Alarms & reminders - - Allow this app to schedule alarms or other timing based events. - This will allow the app to wake up and run even when you are not using the device. - Note that revoking this permission may cause the app to malfunction, specifically any alarms - that the app has scheduled will no longer work. + + Allow this app to set alarms and schedule other actions. + This app may be used when you\u2019re not using your phone, which may use more battery. + If this permission is off, this app may not function normally, + and its alarms won\u2019t work as scheduled. + + + + Allow this app to set alarms and schedule other actions. + This app may be used when you\u2019re not using your tablet, which may use more battery. + If this permission is off, this app may not function normally, + and its alarms won\u2019t work as scheduled. + + + + Allow this app to set alarms and schedule other actions. + This app may be used when you\u2019re not using your device, which may use more battery. + If this permission is off, this app may not function normally, + and its alarms won\u2019t work as scheduled. - schedule, alarm, reminder, event + schedule, alarm, reminder, clock Turn on diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java index 0a735029eead0..e5ae77a26246e 100644 --- a/services/core/java/com/android/server/AlarmManagerInternal.java +++ b/services/core/java/com/android/server/AlarmManagerInternal.java @@ -24,13 +24,16 @@ public interface AlarmManagerInternal { public interface InFlightListener { /** There is now an alarm pending delivery to the given app */ void broadcastAlarmPending(int recipientUid); + /** A broadcast alarm targeted to the given app has completed delivery */ void broadcastAlarmComplete(int recipientUid); } /** Returns true if AlarmManager is delaying alarms due to device idle. */ boolean isIdling(); + public void removeAlarmsForUid(int uid); + public void registerInFlightListener(InFlightListener callback); /** @@ -38,4 +41,10 @@ public interface AlarmManagerInternal { * {@link android.app.PendingIntent#equals(java.lang.Object) PendingIntent.equals} */ void remove(PendingIntent rec); + + /** + * Returns if the given package in the given user holds + * {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} + */ + boolean hasScheduleExactAlarm(String packageName, int uid); } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 84819619ad79e..edfc21d477673 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -28,6 +28,10 @@ import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.AlarmManager.WINDOW_EXACT; import static android.app.AlarmManager.WINDOW_HEURISTIC; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.AppOpsManager.MODE_ERRORED; +import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; @@ -49,6 +53,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; +import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA; @@ -57,6 +62,7 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_W import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_CRASH_NON_CLOCK_APPS; +import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL; @@ -64,6 +70,7 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUT import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY; +import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE; import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX; import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY; import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; @@ -87,8 +94,10 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.Manifest; import android.app.ActivityManager; @@ -106,6 +115,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.content.Context; import android.content.Intent; import android.content.PermissionChecker; +import android.content.pm.PackageManagerInternal; import android.os.BatteryManager; import android.os.Bundle; import android.os.Handler; @@ -113,13 +123,13 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.Settings; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -176,6 +186,8 @@ public class AlarmManagerServiceTest { @Mock private IAppOpsService mIAppOpsService; @Mock + private AppOpsManager mAppOpsManager; + @Mock private DeviceIdleInternal mDeviceIdleInternal; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @@ -184,6 +196,8 @@ public class AlarmManagerServiceTest { @Mock private ActivityManagerInternal mActivityManagerInternal; @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock private AppStateTrackerImpl mAppStateTracker; @Mock private AlarmManagerService.ClockReceiver mClockReceiver; @@ -269,6 +283,11 @@ public class AlarmManagerServiceTest { // Do nothing. } + @Override + int getCallingUid() { + return TEST_CALLING_UID; + } + @Override void setAlarm(int type, long millis) { mTestTimer.set(type, millis); @@ -279,7 +298,7 @@ public class AlarmManagerServiceTest { } @Override - int getSystemUiUid() { + int getSystemUiUid(PackageManagerInternal unused) { return SYSTEM_UI_UID; } @@ -344,6 +363,8 @@ public class AlarmManagerServiceTest { () -> LocalServices.getService(DeviceIdleInternal.class)); doReturn(mActivityManagerInternal).when( () -> LocalServices.getService(ActivityManagerInternal.class)); + doReturn(mPackageManagerInternal).when( + () -> LocalServices.getService(PackageManagerInternal.class)); doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class)); doReturn(mAppStandbyInternal).when( () -> LocalServices.getService(AppStandbyInternal.class)); @@ -376,8 +397,10 @@ public class AlarmManagerServiceTest { () -> DeviceConfig.getProperties( eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.any())); - when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn( - mock(AppOpsManager.class)); + when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); + + when(mPackageManagerInternal.getPackageUid(eq(TEST_CALLING_PACKAGE), anyInt(), + eq(TEST_CALLING_USER))).thenReturn(TEST_CALLING_UID); mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); @@ -464,8 +487,15 @@ public class AlarmManagerServiceTest { private void setTestAlarm(int type, long triggerTime, long windowLength, PendingIntent operation, long interval, int flags, int callingUid, Bundle idleOptions) { + setTestAlarm(type, triggerTime, windowLength, operation, interval, flags, callingUid, + TEST_CALLING_PACKAGE, idleOptions); + } + + private void setTestAlarm(int type, long triggerTime, long windowLength, + PendingIntent operation, long interval, int flags, int callingUid, + String callingPackage, Bundle idleOptions) { mService.setImpl(type, triggerTime, windowLength, interval, operation, null, "test", flags, - null, null, callingUid, TEST_CALLING_PACKAGE, idleOptions); + null, null, callingUid, callingPackage, idleOptions); } private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { @@ -503,6 +533,12 @@ public class AlarmManagerServiceTest { mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } + private void setDeviceConfigString(String key, String val) { + mDeviceConfigKeys.add(key); + doReturn(val).when(mDeviceConfigProperties).getString(eq(key), anyString()); + mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); + } + /** * Lowers quotas to make testing feasible. Careful while calling as this will replace any * existing settings for the calling test. @@ -601,6 +637,47 @@ public class AlarmManagerServiceTest { assertEquals(55, mService.mConstants.PRIORITY_ALARM_DELAY); } + @Test + public void updatingExactAlarmDenyList() { + ArraySet denyListed = new ArraySet<>(new String[]{ + "com.example.package1", + "com.example.package2", + "com.example.package3", + }); + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, + "com.example.package1,com.example.package2,com.example.package3"); + assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); + + + denyListed = new ArraySet<>(new String[]{ + "com.example.package1", + "com.example.package4", + }); + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, + "com.example.package1,com.example.package4"); + assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); + + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); + assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size()); + } + + @Test + public void exactAlarmDenyListMaxSize() { + final ArraySet expectedSet = new ArraySet<>(); + final StringBuilder sb = new StringBuilder("package1"); + expectedSet.add("package1"); + for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) { + sb.append(",package"); + sb.append(i); + if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) { + expectedSet.add("package" + i); + } + } + assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size()); + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString()); + assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST); + } + @Test public void positiveWhileIdleQuotas() { setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3); @@ -1717,21 +1794,70 @@ public class AlarmManagerServiceTest { } @Test - public void canScheduleExactAlarms() throws RemoteException { + public void hasScheduleExactAlarmBinderCallEmptyDenyList() throws RemoteException { doReturn(PermissionChecker.PERMISSION_GRANTED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); - assertTrue(mBinder.canScheduleExactAlarms()); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); - assertFalse(mBinder.canScheduleExactAlarms()); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + } - doReturn(PermissionChecker.PERMISSION_SOFT_DENIED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); - assertFalse(mBinder.canScheduleExactAlarms()); + @Test + public void hasScheduleExactAlarmBinderCallWithDenyList() throws RemoteException { + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); + + when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(MODE_ERRORED); + + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); + + when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(MODE_DEFAULT); + + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); + + when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(MODE_IGNORED); + + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); + + when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(MODE_ALLOWED); + + doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + + doReturn(PermissionChecker.PERMISSION_GRANTED).when( + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + + assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), times(2)); } @Test @@ -1757,8 +1883,9 @@ public class AlarmManagerServiceTest { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null); - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM), never()); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); } @@ -1775,7 +1902,7 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), - eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); } @Test @@ -1792,7 +1919,7 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), - eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -1812,7 +1939,7 @@ public class AlarmManagerServiceTest { final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), anyLong(), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), - isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -1832,7 +1959,7 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), - isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull()); + isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); } @Test @@ -1842,8 +1969,9 @@ public class AlarmManagerServiceTest { anyString(), any(UserHandle.class))); doReturn(PermissionChecker.PERMISSION_GRANTED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); final PendingIntent alarmPi = getNewMockPendingIntent(); final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class); @@ -1851,14 +1979,15 @@ public class AlarmManagerServiceTest { alarmPi, null, null, null, alarmClock); // Correct permission checks are invoked. - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), - isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), + isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); @@ -1874,8 +2003,9 @@ public class AlarmManagerServiceTest { anyString(), any(UserHandle.class))); doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -1887,9 +2017,9 @@ public class AlarmManagerServiceTest { } catch (SecurityException se) { // Expected. } - - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); } @@ -1904,15 +2034,16 @@ public class AlarmManagerServiceTest { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), - eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -1926,24 +2057,24 @@ public class AlarmManagerServiceTest { anyString(), any(UserHandle.class))); // If permission is denied, only then allowlist will be checked. doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); - verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid())); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); - final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), - eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); - System.out.println("what got captured: " + bundleCaptor.getValue()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); } @Test @@ -1957,15 +2088,16 @@ public class AlarmManagerServiceTest { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(), - eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -1979,23 +2111,25 @@ public class AlarmManagerServiceTest { anyString(), any(UserHandle.class))); // If permission is denied, only then allowlist will be checked. doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); - verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid())); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), - eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -2011,8 +2145,9 @@ public class AlarmManagerServiceTest { anyString(), any(UserHandle.class))); doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2030,8 +2165,9 @@ public class AlarmManagerServiceTest { } catch (SecurityException se) { // Expected. } - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM), times(2)); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), times(2)); verify(mDeviceIdleInternal, times(2)).isAppOnWhitelist(anyInt()); } @@ -2047,14 +2183,15 @@ public class AlarmManagerServiceTest { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM), never()); + verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), - isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -2068,10 +2205,11 @@ public class AlarmManagerServiceTest { anyString(), any(UserHandle.class))); doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, - Manifest.permission.SCHEDULE_EXACT_ALARM)); + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); - when(mAppStateTracker.isUidPowerSaveUserExempt(Process.myUid())).thenReturn(true); + when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, @@ -2081,7 +2219,7 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_STANDALONE), isNull(), isNull(), - eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); } @Test @@ -2100,10 +2238,80 @@ public class AlarmManagerServiceTest { } } + @Test + public void denyListPackagesAdded() { + mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); + assertAndHandleMessageSync(EXACT_ALARM_DENY_LIST_CHANGED); + + ArraySet added = new ArraySet<>(new String[]{"p4", "p5"}); + verify(mService).handlePackagesAddedToExactAlarmsDenyListLocked(eq(added)); + } + + @Test + public void denyListPackagesRemoved() { + clearInvocations(mService.mHandler); + mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2"); + verifyNoMoreInteractions(mService.mHandler); + } + + @Test + public void removeExactAlarmsOnPackageAddedToDenyList() { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + // basic exact alarm + setTestAlarm(ELAPSED_REALTIME, 1, 0, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, + null); + // exact and allow-while-idle alarm + setTestAlarm(ELAPSED_REALTIME, 2, 0, getNewMockPendingIntent(), 0, FLAG_ALLOW_WHILE_IDLE, + TEST_CALLING_UID, null); + // alarm clock + setWakeFromIdle(RTC_WAKEUP, 3, getNewMockPendingIntent()); + + final PendingIntent inexact = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, 4, 10, inexact, 0, 0, TEST_CALLING_UID, null); + + final PendingIntent inexactAwi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, 5, 10, inexactAwi, 0, FLAG_ALLOW_WHILE_IDLE, + TEST_CALLING_UID, null); + + final String differentPackage = "different.package"; + final PendingIntent exactButDifferentPackage = getNewMockPendingIntent( + TEST_CALLING_UID, differentPackage); + setTestAlarm(ELAPSED_REALTIME, 6, 0, exactButDifferentPackage, 0, 0, + TEST_CALLING_UID, differentPackage, null); + assertEquals(6, mService.mAlarmStore.size()); + + when(mAppOpsManager.checkOpNoThrow(eq(OP_SCHEDULE_EXACT_ALARM), anyInt(), + anyString())).thenReturn(MODE_DEFAULT); + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); + assertAndHandleMessageSync(EXACT_ALARM_DENY_LIST_CHANGED); + verify(mService).handlePackagesAddedToExactAlarmsDenyListLocked( + argThat(set -> (set.size() == 1 && set.contains(TEST_CALLING_PACKAGE)))); + + final ArrayList remaining = mService.mAlarmStore.asList(); + assertEquals(3, remaining.size()); + assertTrue("Basic inexact alarm removed", + remaining.removeIf(a -> a.matches(inexact, null))); + assertTrue("Inexact allow-while-idle alarm removed", + remaining.removeIf(a -> a.matches(inexactAwi, null))); + assertTrue("Alarm from different package removed", + remaining.removeIf(a -> a.matches(exactButDifferentPackage, null))); + + // Mock should return false by default. + verify(mDeviceIdleInternal, atLeastOnce()).isAppOnWhitelist( + UserHandle.getAppId(TEST_CALLING_UID)); + } + @Test public void opScheduleExactAlarmRevoked() throws Exception { - when(mIAppOpsService.checkOperation(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, - TEST_CALLING_PACKAGE)).thenReturn(AppOpsManager.MODE_ERRORED); + doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, @@ -2149,7 +2357,8 @@ public class AlarmManagerServiceTest { remaining.removeIf(a -> a.matches(exactButDifferentUid, null))); // Mock should return false by default. - verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); + verify(mDeviceIdleInternal, atLeastOnce()).isAppOnWhitelist( + UserHandle.getAppId(TEST_CALLING_UID)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 8991e9fedadfd..f9c8a23bdfae0 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -81,6 +81,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.util.ArraySet; +import android.util.Pair; import android.view.Display; import androidx.test.InstrumentationRegistry; @@ -114,8 +115,10 @@ import java.util.concurrent.TimeUnit; @SmallTest public class AppStandbyControllerTests { - private static final String PACKAGE_1 = "com.example.foo"; + private static final String PACKAGE_1 = "com.example.foo.1"; private static final int UID_1 = 10000; + private static final String PACKAGE_2 = "com.example.foo.2"; + private static final int UID_2 = 20000; private static final String PACKAGE_EXEMPTED_1 = "com.android.exempted"; private static final int UID_EXEMPTED_1 = 10001; private static final String PACKAGE_SYSTEM_HEADFULL = "com.example.system.headfull"; @@ -196,6 +199,7 @@ public class AppStandbyControllerTests { int[] mRunningUsers = new int[] {USER_ID}; List mCrossProfileTargets = Collections.emptyList(); boolean mDeviceIdleMode = false; + Set> mClockApps = new ArraySet<>(); DeviceConfig.Properties.Builder mSettingsBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_APP_STANDBY) .setLong("screen_threshold_active", 0) @@ -253,6 +257,11 @@ public class AppStandbyControllerTests { return PACKAGE_WELLBEING.equals(packageName); } + @Override + boolean hasScheduleExactAlarm(String packageName, int uid) { + return mClockApps.contains(Pair.create(packageName, uid)); + } + @Override void updatePowerWhitelistCache() { } @@ -349,6 +358,12 @@ public class AppStandbyControllerTests { pi.packageName = PACKAGE_1; packages.add(pi); + PackageInfo pInfo = new PackageInfo(); + pInfo.applicationInfo = new ApplicationInfo(); + pInfo.applicationInfo.uid = UID_2; + pInfo.packageName = PACKAGE_2; + packages.add(pInfo); + PackageInfo pie = new PackageInfo(); pie.applicationInfo = new ApplicationInfo(); pie.applicationInfo.uid = UID_EXEMPTED_1; @@ -1661,6 +1676,29 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } + @Test + public void testClockAppElevated() throws Exception { + mInjector.mClockApps.add(Pair.create(PACKAGE_1, UID_1)); + + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); + + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_2); + assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2); + + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; + + // Make sure a clock app does not get lowered below WORKING_SET. + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_TIMEOUT); + assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); + + // A non clock app should be able to fall lower than WORKING_SET. + mController.setAppStandbyBucket(PACKAGE_2, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_TIMEOUT); + assertBucket(STANDBY_BUCKET_RARE, PACKAGE_2); + } + @Test public void testChangingSettings_ElapsedThreshold_Invalid() { mInjector.mSettingsBuilder