diff options
4 files changed, 514 insertions, 164 deletions
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 1169391d2cd2..fe0c7f718bb0 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -136,6 +136,8 @@ import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; @@ -215,9 +217,18 @@ public class AlarmManagerService extends SystemService { final Object mLock = new Object(); - /** Immutable set of app ids that have requested SCHEDULE_EXACT_ALARM permission.*/ + /** Immutable set of app ids requesting {@link Manifest.permission#SCHEDULE_EXACT_ALARM} */ @VisibleForTesting volatile Set<Integer> mExactAlarmCandidates = Collections.emptySet(); + + /** + * A map from uid to the last op-mode we have seen for + * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM} + */ + @VisibleForTesting + @GuardedBy("mLock") + SparseIntArray mLastOpScheduleExactAlarm = new SparseIntArray(); + // List of alarms per uid deferred due to user applied background restrictions on the source app SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>(); private long mNextWakeup; @@ -522,6 +533,9 @@ public class AlarmManagerService extends SystemService { static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz"; @VisibleForTesting static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz"; + @VisibleForTesting + static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = + "kill_on_schedule_exact_alarm_revoked"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; @@ -564,6 +578,8 @@ public class AlarmManagerService extends SystemService { private static final long DEFAULT_MIN_DEVICE_IDLE_FUZZ = 2 * 60_000; private static final long DEFAULT_MAX_DEVICE_IDLE_FUZZ = 15 * 60_000; + private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true; + // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -644,6 +660,13 @@ public class AlarmManagerService extends SystemService { */ public long MAX_DEVICE_IDLE_FUZZ = DEFAULT_MAX_DEVICE_IDLE_FUZZ; + /** + * Whether or not to kill app when the permission + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} is revoked. + */ + public boolean KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = + DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED; + private long mLastAllowWhileIdleWhitelistDuration = -1; private int mVersion = 0; @@ -816,6 +839,11 @@ public class AlarmManagerService extends SystemService { deviceIdleFuzzBoundariesUpdated = true; } break; + case KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED: + KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = properties.getBoolean( + KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED, + DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED); + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -830,17 +858,24 @@ public class AlarmManagerService extends SystemService { } private void updateExactAlarmDenyList(String[] newDenyList) { + final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList)); + final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST); + final Set<String> added = new ArraySet<>(newDenyList); + + added.removeAll(EXACT_ALARM_DENY_LIST); + removed.removeAll(newSet); + if (added.size() > 0) { + mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added) + .sendToTarget(); + } + if (removed.size() > 0) { + mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed) + .sendToTarget(); + } if (newDenyList.length == 0) { EXACT_ALARM_DENY_LIST = Collections.emptySet(); } else { - final Set<String> oldSet = EXACT_ALARM_DENY_LIST; - final Set<String> 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(); - } + EXACT_ALARM_DENY_LIST = newSet; } } @@ -1007,6 +1042,20 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST); pw.println(); + pw.print(KEY_MIN_DEVICE_IDLE_FUZZ); + pw.print("="); + TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw); + pw.println(); + + pw.print(KEY_MAX_DEVICE_IDLE_FUZZ); + pw.print("="); + TimeUtils.formatDuration(MAX_DEVICE_IDLE_FUZZ, pw); + pw.println(); + + pw.print(KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED, + KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED); + pw.println(); + pw.decreaseIndent(); } @@ -1667,16 +1716,57 @@ public class AlarmManagerService extends SystemService { void refreshExactAlarmCandidates() { final String[] candidates = mLocalPermissionManager.getAppOpPermissionPackages( Manifest.permission.SCHEDULE_EXACT_ALARM); - final Set<Integer> appIds = new ArraySet<>(candidates.length); + final Set<Integer> newAppIds = new ArraySet<>(candidates.length); for (final String candidate : candidates) { final int uid = mPackageManagerInternal.getPackageUid(candidate, PackageManager.MATCH_ANY_USER, USER_SYSTEM); if (uid > 0) { - appIds.add(UserHandle.getAppId(uid)); + newAppIds.add(UserHandle.getAppId(uid)); + } + } + final ArraySet<Integer> removed = new ArraySet<>(mExactAlarmCandidates); + removed.removeAll(newAppIds); + // This code is only called on package_added and boot. The set {removed} is only expected to + // be non-empty when a package was updated and it removed the permission from its manifest. + for (int i = 0; i < removed.size(); i++) { + final int removedAppId = removed.valueAt(i); + synchronized (mLock) { + Slog.i(TAG, "App id " + removedAppId + " lost SCHEDULE_EXACT_ALARM on update"); + + final Predicate<Alarm> whichAlarms = a -> { + if (UserHandle.getAppId(a.uid) != removedAppId || a.windowLength != 0) { + return false; + } + if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) { + return false; + } + return a.alarmClock != null || !isExemptFromExactAlarmPermission(a.uid); + }; + removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); } } // No need to lock. Assignment is always atomic. - mExactAlarmCandidates = Collections.unmodifiableSet(appIds); + mExactAlarmCandidates = Collections.unmodifiableSet(newAppIds); + } + + @Override + public void onUserStarting(TargetUser user) { + super.onUserStarting(user); + final int userId = user.getUserIdentifier(); + mHandler.post(() -> { + for (final int appId : mExactAlarmCandidates) { + final int uid = UserHandle.getUid(userId, appId); + final AndroidPackage androidPackage = mPackageManagerInternal.getPackage(uid); + // It will be null if it is not installed on the starting user. + if (androidPackage != null) { + final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, + uid, androidPackage.getPackageName()); + synchronized (mLock) { + mLastOpScheduleExactAlarm.put(uid, mode); + } + } + } + }); } @Override @@ -1706,17 +1796,44 @@ public class AlarmManagerService extends SystemService { @Override public void opChanged(int op, int uid, String packageName) throws RemoteException { - if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) { + final int userId = UserHandle.getUserId(uid); + if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM + || !isExactAlarmChangeEnabled(packageName, userId)) { return; } - if (!hasScheduleExactAlarmInternal(packageName, uid)) { + + final boolean requested = mExactAlarmCandidates.contains( + UserHandle.getAppId(uid)); + final boolean denyListed = + mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); + + final int newMode = mAppOps.checkOpNoThrow( + AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, packageName); + + final int oldMode; + synchronized (mLock) { + final int index = mLastOpScheduleExactAlarm.indexOfKey(uid); + if (index < 0) { + oldMode = AppOpsManager.opToDefaultMode( + AppOpsManager.OP_SCHEDULE_EXACT_ALARM); + mLastOpScheduleExactAlarm.put(uid, newMode); + } else { + oldMode = mLastOpScheduleExactAlarm.valueAt(index); + mLastOpScheduleExactAlarm.setValueAt(index, newMode); + } + } + + final boolean hadPermission = getScheduleExactAlarmState(requested, + denyListed, oldMode); + final boolean hasPermission = getScheduleExactAlarmState(requested, + denyListed, newMode); + + if (hadPermission && !hasPermission) { mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS, uid, 0, packageName).sendToTarget(); - } else { - // TODO(b/187206399) Make sure this won't be sent, if the app - // already had the appop previously. + } else if (!hadPermission && hasPermission) { sendScheduleExactAlarmPermissionStateChangedBroadcast( - packageName, UserHandle.getUserId(uid)); + packageName, userId); } } }); @@ -2256,12 +2373,28 @@ public class AlarmManagerService extends SystemService { } } + private static boolean getScheduleExactAlarmState(boolean requested, boolean denyListed, + int appOpMode) { + if (!requested) { + return false; + } + if (appOpMode == AppOpsManager.MODE_DEFAULT) { + return !denyListed; + } + return appOpMode == AppOpsManager.MODE_ALLOWED; + } + boolean hasScheduleExactAlarmInternal(String packageName, int uid) { + // Not using getScheduleExactAlarmState as this can avoid some calls to AppOpsService. + // Not using #mLastOpScheduleExactAlarm as it may contain stale values. + // No locking needed as all internal containers being queried are immutable. + final long start = mStatLogger.getTime(); final boolean hasPermission; - // No locking needed as all internal containers being queried are immutable. if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) { hasPermission = false; + } else if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) { + hasPermission = false; } else { final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, packageName); @@ -2368,8 +2501,7 @@ public class AlarmManagerService extends SystemService { } else if (exact || allowWhileIdle) { final boolean needsPermission; boolean lowerQuota; - if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, - callingPackage, UserHandle.of(callingUserId))) { + if (isExactAlarmChangeEnabled(callingPackage, callingUserId)) { needsPermission = exact; lowerQuota = !exact; idleOptions = exact ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle(); @@ -2524,6 +2656,11 @@ public class AlarmManagerService extends SystemService { } }; + private static boolean isExactAlarmChangeEnabled(String packageName, int userId) { + return CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, + packageName, UserHandle.of(userId)); + } + void dumpImpl(IndentingPrintWriter pw) { synchronized (mLock) { pw.println("Current Alarm Manager state:"); @@ -2673,6 +2810,17 @@ public class AlarmManagerService extends SystemService { pw.println("App ids requesting SCHEDULE_EXACT_ALARM: " + mExactAlarmCandidates); pw.println(); + pw.print("Last OP_SCHEDULE_EXACT_ALARM: ["); + for (int i = 0; i < mLastOpScheduleExactAlarm.size(); i++) { + if (i > 0) { + pw.print(", "); + } + UserHandle.formatUid(pw, mLastOpScheduleExactAlarm.keyAt(i)); + pw.print(":" + AppOpsManager.modeToName(mLastOpScheduleExactAlarm.valueAt(i))); + } + pw.println("]"); + + pw.println(); pw.println("Next alarm clock information: "); pw.increaseIndent(); final TreeSet<Integer> users = new TreeSet<>(); @@ -3362,30 +3510,58 @@ 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. + * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that + * either got added or deleted. + * These packages may lose or gain the SCHEDULE_EXACT_ALARM 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. + * Note that these packages don't need to be installed on the device, but if they are and they + * do undergo a permission change, we will handle them appropriately. * + * This should not be called with the lock held as it calls out to other services. * This is not expected to get called frequently. */ - void handlePackagesAddedToExactAlarmsDenyListLocked(ArraySet<String> packageNames) { - Slog.w(TAG, "Packages " + packageNames + " added to the exact alarm deny list."); - final Predicate<Alarm> 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; + void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) { + Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from") + + " the exact alarm deny list."); + + final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds(); + + for (int i = 0; i < changedPackages.size(); i++) { + final String changedPackage = changedPackages.valueAt(i); + for (final int userId : startedUserIds) { + final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId); + if (uid <= 0) { + continue; + } + if (!isExactAlarmChangeEnabled(changedPackage, userId)) { + continue; + } + final int appOpMode; + synchronized (mLock) { + appOpMode = mLastOpScheduleExactAlarm.get(uid, + AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)); + } + final boolean requested = mExactAlarmCandidates.contains(UserHandle.getAppId(uid)); + + // added: true => package was added to the deny list + // added: false => package was removed from the deny list + final boolean hadPermission = getScheduleExactAlarmState(requested, !added, + appOpMode); + final boolean hasPermission = getScheduleExactAlarmState(requested, added, + appOpMode); + + if (hadPermission == hasPermission) { + continue; + } + if (added) { + synchronized (mLock) { + removeExactAlarmsOnPermissionRevokedLocked(uid, changedPackage); + } + } else { + sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); + } } - return !hasScheduleExactAlarmInternal(a.packageName, a.uid); - }; - removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); + } } /** @@ -3396,9 +3572,7 @@ public class AlarmManagerService extends SystemService { */ void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) { Slog.w(TAG, "Package " + packageName + ", uid " + uid + " lost SCHEDULE_EXACT_ALARM!"); - if (!CompatChanges.isChangeEnabled( - AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, - packageName, UserHandle.getUserHandleForUid(uid))) { + if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) { return; } @@ -3409,6 +3583,11 @@ public class AlarmManagerService extends SystemService { return false; }; removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); + + if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) { + PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), + "schedule_exact_alarm revoked"); + } } private void removeAlarmsInternalLocked(Predicate<Alarm> whichAlarms, int reason) { @@ -3535,6 +3714,11 @@ public class AlarmManagerService extends SystemService { mRemovalHistory.removeAt(i); } } + for (int i = mLastOpScheduleExactAlarm.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mLastOpScheduleExactAlarm.keyAt(i)) == userHandle) { + mLastOpScheduleExactAlarm.removeAt(i); + } + } } void interactiveStateChangedLocked(boolean interactive) { @@ -4091,8 +4275,9 @@ 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; - public static final int REFRESH_EXACT_ALARM_CANDIDATES = 10; + public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9; + public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10; + public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; AlarmHandler() { super(Looper.myLooper()); @@ -4179,10 +4364,11 @@ public class AlarmManagerService extends SystemService { removeExactAlarmsOnPermissionRevokedLocked(uid, packageName); } break; - case EXACT_ALARM_DENY_LIST_CHANGED: - synchronized (mLock) { - handlePackagesAddedToExactAlarmsDenyListLocked((ArraySet<String>) msg.obj); - } + case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: + handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true); + break; + case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED: + handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false); break; case REFRESH_EXACT_ALARM_CANDIDATES: refreshExactAlarmCandidates(); @@ -4349,6 +4535,7 @@ public class AlarmManagerService extends SystemService { case Intent.ACTION_UID_REMOVED: mLastPriorityAlarmDispatch.delete(uid); mRemovalHistory.delete(uid); + mLastOpScheduleExactAlarm.delete(uid); return; case Intent.ACTION_PACKAGE_REMOVED: if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 7cb8bc0d80fd..317e51c27d90 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -254,6 +254,9 @@ public abstract class ActivityManagerInternal { /** Returns the current user id. */ public abstract int getCurrentUserId(); + /** Returns the currently started user ids. */ + public abstract int[] getStartedUserIds(); + /** Returns true if the user is running. */ public abstract boolean isUserRunning(@UserIdInt int userId, int flags); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c841fa3b057c..bed5495946a3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15349,6 +15349,11 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public int[] getStartedUserIds() { + return mUserController.getStartedUserArray(); + } + + @Override public void setPendingIntentAllowBgActivityStarts(IIntentSender target, IBinder allowlistToken, int flags) { if (!(target instanceof PendingIntentRecord)) { 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 d5e1cd6e0415..21de7916e23d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -58,7 +58,8 @@ import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION; 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.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED; +import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED; @@ -103,10 +104,8 @@ 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.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -155,6 +154,8 @@ import com.android.server.AppStateTrackerImpl; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.usage.AppStandbyInternal; @@ -175,6 +176,7 @@ import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -370,8 +372,9 @@ public class AlarmManagerServiceTest { .mockStatic(LocalServices.class) .spyStatic(Looper.class) .mockStatic(MetricsHelper.class) - .mockStatic(Settings.Global.class) + .mockStatic(PermissionManagerService.class) .mockStatic(ServiceManager.class) + .mockStatic(Settings.Global.class) .mockStatic(SystemProperties.class) .spyStatic(UserHandle.class) .strictness(Strictness.WARN) @@ -394,7 +397,7 @@ public class AlarmManagerServiceTest { doCallRealMethod().when((MockedVoidMethod) () -> LocalServices.addService(eq(AlarmManagerInternal.class), any())); doCallRealMethod().when(() -> LocalServices.getService(AlarmManagerInternal.class)); - doReturn(false).when(() -> UserHandle.isCore(TEST_CALLING_UID)); + doReturn(false).when(() -> UserHandle.isCore(anyInt())); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), eq(TEST_CALLING_USER), anyLong())).thenReturn(STANDBY_BUCKET_ACTIVE); doReturn(Looper.getMainLooper()).when(Looper::myLooper); @@ -983,8 +986,7 @@ public class AlarmManagerServiceTest { verify(mService.mHandler, atLeastOnce()).sendMessageAtTime(messageCaptor.capture(), anyLong()); final Message lastMessage = messageCaptor.getValue(); - assertEquals("Unexpected message send to handler", lastMessage.what, - what); + assertEquals("Unexpected message send to handler", what, lastMessage.what); mService.mHandler.handleMessage(lastMessage); } @@ -1876,6 +1878,8 @@ public class AlarmManagerServiceTest { @Test public void hasScheduleExactAlarmBinderCallNotDenyListed() throws RemoteException { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); + mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); @@ -1891,6 +1895,8 @@ public class AlarmManagerServiceTest { @Test public void hasScheduleExactAlarmBinderCallDenyListed() throws RemoteException { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); + mockExactAlarmPermissionGrant(true, true, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); @@ -1905,7 +1911,25 @@ public class AlarmManagerServiceTest { } @Test + public void hasScheduleExactAlarmBinderCallChangeDisabled() throws RemoteException { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); + + mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT); + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + + mockExactAlarmPermissionGrant(true, true, MODE_ALLOWED); + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + } + + private void mockChangeEnabled(long changeId, boolean enabled) { + doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(), + any(UserHandle.class))); + } + + @Test public void hasScheduleExactAlarmBinderCallNotDeclared() throws RemoteException { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); + mockExactAlarmPermissionGrant(false, false, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); @@ -1918,9 +1942,7 @@ public class AlarmManagerServiceTest { @Test public void noPermissionCheckWhenChangeDisabled() throws RemoteException { - doReturn(false).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); // alarm clock mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, @@ -1946,9 +1968,7 @@ public class AlarmManagerServiceTest { @Test public void exactBinderCallWhenChangeDisabled() throws Exception { - doReturn(false).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, @@ -1963,9 +1983,7 @@ public class AlarmManagerServiceTest { @Test public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception { - doReturn(false).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, @@ -1985,9 +2003,7 @@ public class AlarmManagerServiceTest { @Test public void inexactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception { - doReturn(false).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0, @@ -2006,9 +2022,7 @@ public class AlarmManagerServiceTest { @Test public void alarmClockBinderCallWhenChangeDisabled() throws Exception { - doReturn(false).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); final PendingIntent alarmPi = getNewMockPendingIntent(); final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class); @@ -2023,9 +2037,7 @@ public class AlarmManagerServiceTest { @Test public void alarmClockBinderCall() throws RemoteException { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); @@ -2060,7 +2072,6 @@ public class AlarmManagerServiceTest { } else { setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); } - when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(mode); } @@ -2068,9 +2079,7 @@ public class AlarmManagerServiceTest { @Test public void alarmClockBinderCallWithoutPermission() throws RemoteException { setDeviceConfigBoolean(KEY_CRASH_NON_CLOCK_APPS, true); - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); @@ -2089,9 +2098,7 @@ public class AlarmManagerServiceTest { @Test public void exactBinderCallWithPermission() throws RemoteException { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2115,9 +2122,7 @@ public class AlarmManagerServiceTest { @Test public void exactBinderCallWithAllowlist() throws RemoteException { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); @@ -2137,9 +2142,7 @@ public class AlarmManagerServiceTest { @Test public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2162,9 +2165,7 @@ public class AlarmManagerServiceTest { @Test public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); @@ -2191,9 +2192,7 @@ public class AlarmManagerServiceTest { @Test public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException { setDeviceConfigBoolean(KEY_CRASH_NON_CLOCK_APPS, true); - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); @@ -2220,9 +2219,7 @@ public class AlarmManagerServiceTest { public void inexactAllowWhileIdleBinderCall() throws RemoteException { // Both permission and power exemption status don't matter for these alarms. // We only want to test that the flags and idleOptions are correct. - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0, @@ -2244,9 +2241,7 @@ public class AlarmManagerServiceTest { @Test public void binderCallWithUserAllowlist() throws RemoteException { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); @@ -2266,10 +2261,7 @@ public class AlarmManagerServiceTest { @Test public void minWindowChangeEnabled() { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled( - eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); final int minWindow = 73; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); @@ -2315,10 +2307,7 @@ public class AlarmManagerServiceTest { @Test public void minWindowChangeDisabled() { - doReturn(false).when( - () -> CompatChanges.isChangeEnabled( - eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, false); final long minWindow = 73; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); @@ -2335,10 +2324,7 @@ public class AlarmManagerServiceTest { @Test public void minWindowPriorityAlarm() { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled( - eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); final long minWindow = 73; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); @@ -2356,76 +2342,135 @@ public class AlarmManagerServiceTest { } @Test - public void denyListPackagesAdded() { + public void denyListChanged() { mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); + when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT); + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); - assertAndHandleMessageSync(EXACT_ALARM_DENY_LIST_CHANGED); + + final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(), + anyLong()); + + final List<Message> messages = messageCaptor.getAllValues(); + for (final Message msg : messages) { + assertTrue("Unwanted message sent to handler: " + msg.what, + msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED + || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED); + mService.mHandler.handleMessage(msg); + } ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"}); - verify(mService).handlePackagesAddedToExactAlarmsDenyListLocked(eq(added)); + verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true)); + + ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"}); + verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false)); } @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); + public void permissionGrantedDueToDenyList() { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); + + final String[] packages = {"example.package.1", "example.package.2"}; + + final int appId1 = 232; + final int appId2 = 431; + + final int userId1 = 42; + final int userId2 = 53; + + registerAppIds(packages, new Integer[]{appId1, appId2}); + + when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); + + when(mPermissionManagerInternal.getAppOpPermissionPackages( + SCHEDULE_EXACT_ALARM)).thenReturn(packages); + mService.refreshExactAlarmCandidates(); + + final long allowListDuration = 53442; + when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn( + allowListDuration); + + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); + + mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); + + // No permission revoked. + verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString()); + + // Permission got granted only for (appId1, userId2). + final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class); + + verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(), + isNull(), bundleCaptor.capture()); + + assertEquals(userId2, userCaptor.getValue().getIdentifier()); + + // Validate the intent. + assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, + intentCaptor.getValue().getAction()); + assertEquals(packages[0], intentCaptor.getValue().getPackage()); + + // Validate the options. + final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue()); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + bOptions.getTemporaryAppAllowlistType()); + assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration()); + assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, + bOptions.getTemporaryAppAllowlistReasonCode()); } @Test - public void removeExactAlarmsOnPackageAddedToDenyList() { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + public void permissionRevokedDueToDenyList() { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - // 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 String[] packages = {"example.package.1", "example.package.2"}; - final PendingIntent inexact = getNewMockPendingIntent(); - setTestAlarm(ELAPSED_REALTIME, 4, 10, inexact, 0, 0, TEST_CALLING_UID, null); + final int appId1 = 232; + final int appId2 = 431; - final PendingIntent inexactAwi = getNewMockPendingIntent(); - setTestAlarm(ELAPSED_REALTIME, 5, 10, inexactAwi, 0, FLAG_ALLOW_WHILE_IDLE, - TEST_CALLING_UID, null); + final int userId1 = 42; + final int userId2 = 53; - 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()); + registerAppIds(packages, new Integer[]{appId1, appId2}); - 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)))); + when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); - final ArrayList<Alarm> 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))); + when(mPermissionManagerInternal.getAppOpPermissionPackages( + SCHEDULE_EXACT_ALARM)).thenReturn(packages); + mService.refreshExactAlarmCandidates(); - // Mock should return false by default. - verify(mDeviceIdleInternal, atLeastOnce()).isAppOnWhitelist( - UserHandle.getAppId(TEST_CALLING_UID)); + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); + mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); + + mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true); + + // Permission got revoked only for (appId1, userId2) + verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + eq(UserHandle.getUid(userId1, appId1)), eq(packages[0])); + verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + eq(UserHandle.getUid(userId1, appId2)), eq(packages[1])); + verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + eq(UserHandle.getUid(userId2, appId2)), eq(packages[1])); + + verify(mService).removeExactAlarmsOnPermissionRevokedLocked( + eq(UserHandle.getUid(userId2, appId1)), eq(packages[0])); } @Test - public void opScheduleExactAlarmRevoked() throws Exception { + public void opChangedPermissionRevoked() throws Exception { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); + + mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); + mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, @@ -2433,10 +2478,39 @@ public class AlarmManagerServiceTest { } @Test - public void opScheduleExactAlarmGranted() throws Exception { + public void opChangedChangeDisabled() throws Exception { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); + + mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); + + mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); + + verify(mService.mHandler, never()).sendMessageAtTime( + argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong()); + } + + @Test + public void opChangedNoPermissionChangeDueToDenyList() throws Exception { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); + + mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); + mockExactAlarmPermissionGrant(true, true, MODE_DEFAULT); + + mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); + + verify(mService.mHandler, never()).sendMessageAtTime( + argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong()); + } + + @Test + public void opChangedPermissionGranted() throws Exception { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); + final long durationMs = 20000L; when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs); + mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); @@ -2462,9 +2536,7 @@ public class AlarmManagerServiceTest { @Test public void removeExactAlarmsOnPermissionRevoked() { - doReturn(true).when( - () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), - anyString(), any(UserHandle.class))); + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // basic exact alarm setTestAlarm(ELAPSED_REALTIME, 0, 0, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, @@ -2501,6 +2573,9 @@ public class AlarmManagerServiceTest { // Mock should return false by default. verify(mDeviceIdleInternal, atLeastOnce()).isAppOnWhitelist( UserHandle.getAppId(TEST_CALLING_UID)); + + verify(() -> PermissionManagerService.killUid(eq(TEST_CALLING_UID), eq(TEST_CALLING_USER), + anyString())); } @Test @@ -2567,6 +2642,86 @@ public class AlarmManagerServiceTest { } @Test + public void onLastOpScheduleExactAlarmOnUserStart() { + final int userId = 54; + SystemService.TargetUser mockTargetUser = mock(SystemService.TargetUser.class); + when(mockTargetUser.getUserIdentifier()).thenReturn(userId); + + final Integer[] appIds = new Integer[]{43, 254, 7731}; + final int unknownAppId = 2347; + final String[] packageNames = new String[]{"p43", "p254", "p7731"}; + final int[] appOpModes = new int[]{MODE_ALLOWED, MODE_IGNORED, MODE_ERRORED}; + mService.mExactAlarmCandidates = new ArraySet<>(appIds); + mService.mExactAlarmCandidates.add(unknownAppId); + + for (int i = 0; i < appIds.length; i++) { + final int uid = UserHandle.getUid(userId, appIds[i]); + final AndroidPackage pkg = mock(AndroidPackage.class); + when(pkg.getPackageName()).thenReturn(packageNames[i]); + + when(mPackageManagerInternal.getPackage(uid)).thenReturn(pkg); + when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, uid, + packageNames[i])).thenReturn(appOpModes[i]); + } + + final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + doReturn(true).when(mService.mHandler).post(runnableCaptor.capture()); + + mService.onUserStarting(mockTargetUser); + runnableCaptor.getValue().run(); + + assertEquals(appIds.length, mService.mLastOpScheduleExactAlarm.size()); + for (int i = 0; i < appIds.length; i++) { + final int uid = UserHandle.getUid(userId, appIds[i]); + assertEquals(appOpModes[i], mService.mLastOpScheduleExactAlarm.get(uid, -1)); + } + assertTrue(mService.mLastOpScheduleExactAlarm.indexOfKey( + UserHandle.getUid(userId, unknownAppId)) < 0); + } + + @Test + public void refreshExactAlarmCandidatesRemovesExactAlarmsIfNeeded() { + mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); + + mService.mExactAlarmCandidates = new ArraySet<>(new Integer[]{1, 2, 5}); + final String[] updatedRequesters = new String[]{"p11", "p2", "p9"}; + final Integer[] appIds = new Integer[]{11, 2, 9}; + registerAppIds(updatedRequesters, appIds); + + when(mPermissionManagerInternal.getAppOpPermissionPackages( + SCHEDULE_EXACT_ALARM)).thenReturn(updatedRequesters); + + final PendingIntent exactAppId1 = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, 0, 0, exactAppId1, 0, 0, + UserHandle.getUid(TEST_CALLING_USER, 1), null); + + final PendingIntent exactAppId2 = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, 0, 0, exactAppId2, 0, 0, + UserHandle.getUid(TEST_CALLING_USER, 2), null); + + final PendingIntent exactAppId5 = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, 0, 0, exactAppId5, 0, 0, + UserHandle.getUid(TEST_CALLING_USER, 5), null); + + final PendingIntent inexactAppId5 = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, 0, 23, inexactAppId5, 0, 0, + UserHandle.getUid(TEST_CALLING_USER, 5), null); + + assertEquals(4, mService.mAlarmStore.size()); + + mService.refreshExactAlarmCandidates(); + // App ids 1 and 5 lost the permission, so there alarms should be removed. + + final ArrayList<Alarm> remaining = mService.mAlarmStore.asList(); + assertEquals(2, remaining.size()); + + assertTrue("Inexact alarm removed", + remaining.removeIf(a -> a.matches(inexactAppId5, null))); + assertTrue("Alarm from app id 2 removed", + remaining.removeIf(a -> a.matches(exactAppId2, null))); + } + + @Test public void refreshExactAlarmCandidatesOnPackageAdded() { final String[] exactAlarmRequesters = new String[]{"p11", "p2", "p9"}; final Integer[] appIds = new Integer[]{11, 2, 9}; |