diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 7393bcde13b62..5a445d476711a 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -1256,6 +1256,17 @@ public class AlarmManager { wrapper.cancel(); } + /** + * Remove all alarms previously set by the caller, if any. + */ + public void cancelAll() { + try { + mService.removeAll(mContext.getOpPackageName()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + /** * Set the system wall clock time. * Requires the permission android.permission.SET_TIME. diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl index 25caf4b695bb8..a46e69796abd3 100644 --- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl +++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl @@ -37,6 +37,7 @@ interface IAlarmManager { boolean setTime(long millis); void setTimeZone(String zone); void remove(in PendingIntent operation, in IAlarmListener listener); + void removeAll(String packageName); long getNextWakeFromIdleTime(); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) AlarmManager.AlarmClockInfo getNextAlarmClock(int userId); 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 92068e516207e..2e4048dc172ef 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2965,6 +2965,24 @@ public class AlarmManagerService extends SystemService { } } + @Override + public void removeAll(String callingPackage) { + final int callingUid = mInjector.getCallingUid(); + if (callingUid == Process.SYSTEM_UID) { + Slog.wtfStack(TAG, "Attempt to remove all alarms from the system uid package: " + + callingPackage); + return; + } + if (callingUid != mPackageManagerInternal.getPackageUid(callingPackage, 0, + UserHandle.getUserId(callingUid))) { + throw new SecurityException("Package " + callingPackage + + " does not belong to the calling uid " + callingUid); + } + synchronized (mLock) { + removeLocked(callingPackage, REMOVE_REASON_ALARM_CANCELLED); + } + } + @Override public long getNextWakeFromIdleTime() { return getNextWakeFromIdleTimeImpl(); @@ -4077,7 +4095,7 @@ public class AlarmManagerService extends SystemService { } @GuardedBy("mLock") - void removeLocked(final String packageName) { + void removeLocked(final String packageName, int reason) { if (packageName == null) { if (localLOGV) { Slog.w(TAG, "requested remove() of null packageName", @@ -4085,7 +4103,7 @@ public class AlarmManagerService extends SystemService { } return; } - removeAlarmsInternalLocked(a -> a.matches(packageName), REMOVE_REASON_UNDEFINED); + removeAlarmsInternalLocked(a -> a.matches(packageName), reason); } // Only called for ephemeral apps @@ -5128,7 +5146,7 @@ public class AlarmManagerService extends SystemService { removeLocked(uid, REMOVE_REASON_UNDEFINED); } else { // external-applications-unavailable case - removeLocked(pkg); + removeLocked(pkg, REMOVE_REASON_UNDEFINED); } mPriorities.remove(pkg); for (int i = mBroadcastStats.size() - 1; i >= 0; i--) { diff --git a/core/api/current.txt b/core/api/current.txt index 805435a757d15..7dd805dd4cc22 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4596,6 +4596,7 @@ package android.app { method public boolean canScheduleExactAlarms(); method public void cancel(android.app.PendingIntent); method public void cancel(android.app.AlarmManager.OnAlarmListener); + method public void cancelAll(); method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock(); method public void set(int, long, android.app.PendingIntent); method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler); 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 c8c87075eda29..2e1eea29cecc4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -254,6 +254,7 @@ public class AlarmManagerServiceTest { private Injector mInjector; private volatile long mNowElapsedTest; private volatile long mNowRtcTest; + private volatile int mTestCallingUid = TEST_CALLING_UID; @GuardedBy("mTestTimer") private TestTimer mTestTimer = new TestTimer(); @@ -328,7 +329,7 @@ public class AlarmManagerServiceTest { @Override int getCallingUid() { - return TEST_CALLING_UID; + return mTestCallingUid; } @Override @@ -1395,7 +1396,7 @@ public class AlarmManagerServiceTest { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent()); } assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); - mService.removeLocked(TEST_CALLING_PACKAGE); + mService.removeLocked(TEST_CALLING_PACKAGE, REMOVE_REASON_UNDEFINED); assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } @@ -2677,6 +2678,59 @@ public class AlarmManagerServiceTest { eq(EXACT_ALLOW_REASON_ALLOW_LIST)); } + @Test + public void removeAllBinderCall() throws RemoteException { + for (int i = 0; i < 10; i++) { + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, getNewMockPendingIntent()); + } + + final String otherUidPackage1 = "other.uid.package1"; + final String otherUidPackage2 = "other.uid.package2"; + final int otherUid = 1243; + + registerAppIds( + new String[]{TEST_CALLING_PACKAGE, otherUidPackage1, otherUidPackage2}, + new Integer[]{TEST_CALLING_UID, otherUid, otherUid} + ); + + for (int i = 0; i < 9; i++) { + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 11, 0, + getNewMockPendingIntent(otherUid, otherUidPackage1), 0, 0, otherUid, + otherUidPackage1, null); + } + + for (int i = 0; i < 8; i++) { + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 20, 0, + getNewMockPendingIntent(otherUid, otherUidPackage2), 0, 0, otherUid, + otherUidPackage2, null); + } + + assertEquals(27, mService.mAlarmStore.size()); + + try { + mBinder.removeAll(otherUidPackage1); + fail("removeAll() for wrong package did not throw SecurityException"); + } catch (SecurityException se) { + // Expected + } + + try { + mBinder.removeAll(otherUidPackage2); + fail("removeAll() for wrong package did not throw SecurityException"); + } catch (SecurityException se) { + // Expected + } + + mBinder.removeAll(TEST_CALLING_PACKAGE); + assertEquals(17, mService.mAlarmStore.size()); + assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(TEST_CALLING_PACKAGE))); + + mTestCallingUid = otherUid; + mBinder.removeAll(otherUidPackage1); + assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage1))); + assertEquals(8, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage2))); + } + @Test public void minWindowChangeEnabled() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); @@ -3572,7 +3626,7 @@ public class AlarmManagerServiceTest { getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow); // refresh the state. - mService.removeLocked(TEST_CALLING_PACKAGE); + mService.removeLocked(TEST_CALLING_PACKAGE, REMOVE_REASON_UNDEFINED); mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); @@ -3581,7 +3635,7 @@ public class AlarmManagerServiceTest { getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow); // refresh the state. - mService.removeLocked(TEST_CALLING_PACKAGE); + mService.removeLocked(TEST_CALLING_PACKAGE, REMOVE_REASON_UNDEFINED); mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);