From ca41ed611ac9c6584c6d5c38ae8428b8e4f3b135 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Fri, 22 Jul 2022 01:18:12 -0700 Subject: [PATCH] Adding an API to cancel all alarms In some cases, an app can lose access to the pending intents it used to set previous alarms. In the current state, such alarms can never be cancelled. This can be an issue because there is now a maximum limit to the number of concurrent alarms an app can set. An API to cancel all pending alarms set by the caller should help mitigate this. It should be noted that an API to query or retrieve all pending alarms is not straightforward with the current design. Test: atest CtsAlarmManagerTestCases:BasicApiTests Test: atest FrameworksMockingServicesTests:AlarmManagerServiceTest Bug: 182859584 Change-Id: If8f81f93838bf366887c63ae006f9291ee636f5c --- .../java/android/app/AlarmManager.java | 11 ++++ .../java/android/app/IAlarmManager.aidl | 1 + .../server/alarm/AlarmManagerService.java | 24 ++++++- core/api/current.txt | 1 + .../server/alarm/AlarmManagerServiceTest.java | 62 +++++++++++++++++-- 5 files changed, 92 insertions(+), 7 deletions(-) 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);