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
This commit is contained in:
Suprabh Shukla
2022-07-22 01:18:12 -07:00
parent 7d107143ed
commit ca41ed611a
5 changed files with 92 additions and 7 deletions

View File

@@ -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.

View File

@@ -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);

View File

@@ -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--) {

View File

@@ -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);

View File

@@ -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);