diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 3103fcf6119c1..ec6a8b8af8992 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -311,6 +311,15 @@ public class AlarmManager { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final long SCHEDULE_EXACT_ALARM_DOES_NOT_ELEVATE_BUCKET = 262645982L; + /** + * Exact alarms expecting a {@link OnAlarmListener} callback will be dropped when the calling + * app goes into cached state. + * + * @hide + */ + @ChangeId + public static final long EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED = 265195908L; + @UnsupportedAppUsage private final IAlarmManager mService; private final Context mContext; @@ -808,13 +817,24 @@ public class AlarmManager { * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be * invoked via the specified target Handler, or on the application's main looper * if {@code null} is passed as the {@code targetHandler} parameter. + *

+ * This API should only be used to set alarms that are relevant in the context of the app's + * current lifecycle, as the {@link OnAlarmListener} instance supplied is only valid as long as + * the process is alive, and the system can clean up the app process as soon as it is out of + * lifecycle. To schedule alarms that fire reliably even after the current lifecycle completes, + * and wakes up the app if required, use any of the other scheduling APIs that accept a + * {@link PendingIntent} instance. * - *

Note: + *

* On previous android versions {@link Build.VERSION_CODES#S} and * {@link Build.VERSION_CODES#TIRAMISU}, apps targeting SDK level 31 or higher needed to hold * the {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use * this API, unless the app was exempt from battery restrictions. * + *

Note: + * Starting with android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system will + * explicitly drop any alarms set via this API when the calling app goes out of lifecycle. + * */ public void setExact(@AlarmType int type, long triggerAtMillis, @Nullable String tag, @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) { @@ -984,6 +1004,10 @@ public class AlarmManager { * allowlist. This can be set, for example, by marking the app as {@code } * within the system config. * + *

Note: + * Starting with android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system will + * explicitly drop any alarms set via this API when the calling app goes out of lifecycle. + * * @param type type of alarm * @param triggerAtMillis The exact time in milliseconds, that the alarm should be delivered, * expressed in the appropriate clock's units (depending on the alarm @@ -1295,6 +1319,10 @@ public class AlarmManager { * *

See {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} for more details. * + *

Note: + * Starting with android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system will + * explicitly drop any alarms set via this API when the calling app goes out of lifecycle. + * * @param type type of alarm * @param triggerAtMillis The exact time in milliseconds, that the alarm should be delivered, * expressed in the appropriate clock's units (depending on the alarm diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java index ad406a1a155d8..b7cf2978bb2c9 100644 --- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java +++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java @@ -425,6 +425,12 @@ public class AppStateTrackerImpl implements AppStateTracker { */ public void removeAlarmsForUid(int uid) { } + + /** + * Called when a uid goes into cached, so its alarms using a listener should be removed. + */ + public void removeListenerAlarmsForCachedUid(int uid) { + } } public AppStateTrackerImpl(Context context, Looper looper) { @@ -496,7 +502,8 @@ public class AppStateTrackerImpl implements AppStateTracker { mIActivityManager.registerUidObserver(new UidObserver(), ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE - | ActivityManager.UID_OBSERVER_ACTIVE, + | ActivityManager.UID_OBSERVER_ACTIVE + | ActivityManager.UID_OBSERVER_CACHED, ActivityManager.PROCESS_STATE_UNKNOWN, null); mAppOpsService.startWatchingMode(TARGET_OP, null, new AppOpsWatcher()); @@ -731,6 +738,7 @@ public class AppStateTrackerImpl implements AppStateTracker { @Override public void onUidCachedChanged(int uid, boolean cached) { + mHandler.onUidCachedChanged(uid, cached); } @Override @@ -800,6 +808,7 @@ public class AppStateTrackerImpl implements AppStateTracker { private static final int MSG_ON_UID_ACTIVE = 12; private static final int MSG_ON_UID_GONE = 13; private static final int MSG_ON_UID_IDLE = 14; + private static final int MSG_ON_UID_CACHED = 15; MyHandler(Looper looper) { super(looper); @@ -860,6 +869,12 @@ public class AppStateTrackerImpl implements AppStateTracker { obtainMessage(MSG_ON_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget(); } + public void onUidCachedChanged(int uid, boolean cached) { + if (cached) { + obtainMessage(MSG_ON_UID_CACHED, uid, 0).sendToTarget(); + } + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -953,6 +968,15 @@ public class AppStateTrackerImpl implements AppStateTracker { handleUidDisabled(msg.arg1); } return; + case MSG_ON_UID_CACHED: + handleUidCached(msg.arg1); + return; + } + } + + private void handleUidCached(int uid) { + for (Listener l : cloneListeners()) { + l.removeListenerAlarmsForCachedUid(uid); } } 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 5599e542762e6..f3f4fa1f2f57e 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -19,6 +19,7 @@ package com.android.server.alarm; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; +import static android.app.AlarmManager.EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; @@ -57,6 +58,8 @@ import static com.android.server.alarm.Alarm.TARE_POLICY_INDEX; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_ALARM_CANCELLED; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_DATA_CLEARED; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_EXACT_PERMISSION_REVOKED; +import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_LISTENER_BINDER_DIED; +import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_LISTENER_CACHED; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_PI_CANCELLED; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_UNDEFINED; @@ -610,6 +613,8 @@ public class AlarmManagerService extends SystemService { static final int REMOVE_REASON_EXACT_PERMISSION_REVOKED = 2; static final int REMOVE_REASON_DATA_CLEARED = 3; static final int REMOVE_REASON_PI_CANCELLED = 4; + static final int REMOVE_REASON_LISTENER_BINDER_DIED = 5; + static final int REMOVE_REASON_LISTENER_CACHED = 6; final String mTag; final long mWhenRemovedElapsed; @@ -639,6 +644,10 @@ public class AlarmManagerService extends SystemService { return "data_cleared"; case REMOVE_REASON_PI_CANCELLED: return "pi_cancelled"; + case REMOVE_REASON_LISTENER_BINDER_DIED: + return "listener_binder_died"; + case REMOVE_REASON_LISTENER_CACHED: + return "listener_cached"; default: return "unknown:" + reason; } @@ -1892,7 +1901,9 @@ public class AlarmManagerService extends SystemService { @Override public void binderDied(IBinder who) { final IAlarmListener listener = IAlarmListener.Stub.asInterface(who); - removeImpl(null, listener); + synchronized (mLock) { + removeLocked(null, listener, REMOVE_REASON_LISTENER_BINDER_DIED); + } } }; @@ -5444,6 +5455,24 @@ public class AlarmManagerService extends SystemService { removeForStoppedLocked(uid); } } + + @Override + public void removeListenerAlarmsForCachedUid(int uid) { + if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) { + return; + } + synchronized (mLock) { + removeAlarmsInternalLocked(a -> { + if (a.uid != uid || a.listener == null || a.windowLength != 0) { + return false; + } + // TODO (b/265195908): Change to a .w once we have some data on breakages. + Slog.wtf(TAG, "Alarm " + a.listenerTag + " being removed for " + a.packageName + + " because the app went into cached state"); + return true; + }, REMOVE_REASON_LISTENER_CACHED); + } + } }; private final BroadcastStats getStatsLocked(PendingIntent pi) { diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java index 9b04ae4c513d6..31cfa780a1330 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java @@ -271,7 +271,8 @@ public class AppStateTrackerTest { verify(mMockIActivityManager).registerUidObserver( uidObserverArgumentCaptor.capture(), eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE - | ActivityManager.UID_OBSERVER_ACTIVE), + | ActivityManager.UID_OBSERVER_ACTIVE + | ActivityManager.UID_OBSERVER_CACHED), eq(ActivityManager.PROCESS_STATE_UNKNOWN), isNull()); verify(mMockIAppOpsService).startWatchingMode( @@ -1364,6 +1365,8 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1)); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); mIUidObserver.onUidGone(UID_10_1, true); @@ -1381,6 +1384,8 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(0)).unblockAlarmsForUid(anyInt()); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(1)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); mIUidObserver.onUidActive(UID_10_1); @@ -1398,6 +1403,8 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1)); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); mIUidObserver.onUidIdle(UID_10_1, true); @@ -1415,8 +1422,49 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(0)).unblockAlarmsForUid(anyInt()); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(1)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); + mIUidObserver.onUidCachedChanged(UID_10_1, true); + + waitUntilMainHandlerDrain(); + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(eq(UID_10_1), anyBoolean()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean()); + verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(), + anyBoolean()); + + verify(l, times(0)).updateAllAlarms(); + verify(l, times(0)).updateAlarmsForUid(eq(UID_10_1)); + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(1)).removeListenerAlarmsForCachedUid(UID_10_1); + reset(l); + + mIUidObserver.onUidCachedChanged(UID_10_1, false); + + waitUntilMainHandlerDrain(); + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(eq(UID_10_1), anyBoolean()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean()); + verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(), + anyBoolean()); + + verify(l, times(0)).updateAllAlarms(); + verify(l, times(0)).updateAlarmsForUid(eq(UID_10_1)); + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); + reset(l); + + // Without battery saver. mPowerSaveMode = false; mPowerSaveObserver.accept(getPowerSaveState()); @@ -1433,6 +1481,8 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(0)).unblockAlarmsForUid(anyInt()); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); mIUidObserver.onUidActive(UID_10_1); @@ -1450,6 +1500,8 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1)); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); mIUidObserver.onUidGone(UID_10_1, true); @@ -1467,6 +1519,8 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(0)).unblockAlarmsForUid(anyInt()); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(1)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); mIUidObserver.onUidActive(UID_10_1); @@ -1484,6 +1538,8 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1)); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); mIUidObserver.onUidIdle(UID_10_1, true); @@ -1501,6 +1557,46 @@ public class AppStateTrackerTest { verify(l, times(0)).unblockAllUnrestrictedAlarms(); verify(l, times(0)).unblockAlarmsForUid(anyInt()); verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(1)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); + reset(l); + + mIUidObserver.onUidCachedChanged(UID_10_1, true); + + waitUntilMainHandlerDrain(); + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(eq(UID_10_1), anyBoolean()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean()); + verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(), + anyBoolean()); + + verify(l, times(0)).updateAllAlarms(); + verify(l, times(0)).updateAlarmsForUid(eq(UID_10_1)); + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(1)).removeListenerAlarmsForCachedUid(UID_10_1); + reset(l); + + mIUidObserver.onUidCachedChanged(UID_10_1, false); + + waitUntilMainHandlerDrain(); + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(eq(UID_10_1), anyBoolean()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean()); + verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(), + anyBoolean()); + + verify(l, times(0)).updateAllAlarms(); + verify(l, times(0)).updateAlarmsForUid(eq(UID_10_1)); + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + verify(l, times(0)).removeAlarmsForUid(UID_10_1); + verify(l, times(0)).removeListenerAlarmsForCachedUid(UID_10_1); reset(l); } 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 a1f4fddd23349..000283e1126e0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.alarm; import static android.Manifest.permission.SCHEDULE_EXACT_ALARM; import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; +import static android.app.AlarmManager.EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; @@ -201,17 +202,21 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.LongConsumer; @Presubmit +@SuppressWarnings("GuardedBy") // This test enforces synchronous behavior. @RunWith(AndroidJUnit4.class) public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { private static final String TAG = AlarmManagerServiceTest.class.getSimpleName(); private static final int SYSTEM_UI_UID = 12345; private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID); private static final int TEST_CALLING_UID_2 = TEST_CALLING_UID + 1; + private static final int[] ALARM_TYPES = + {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; private long mAppStandbyWindow; private long mAllowWhileIdleWindow; private AlarmManagerService mService; private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener; + private AppStateTrackerImpl.Listener mListener; private AlarmManagerService.UninstallReceiver mPackageChangesReceiver; private AlarmManagerService.ChargingReceiver mChargingReceiver; private IAppOpsCallback mIAppOpsCallback; @@ -515,10 +520,16 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { setTareEnabled(EconomyManager.ENABLED_MODE_OFF); mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW; - ArgumentCaptor captor = + + ArgumentCaptor idleListenerCaptor = ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class); - verify(mAppStandbyInternal).addListener(captor.capture()); - mAppStandbyListener = captor.getValue(); + verify(mAppStandbyInternal).addListener(idleListenerCaptor.capture()); + mAppStandbyListener = idleListenerCaptor.getValue(); + + final ArgumentCaptor trackerListenerCaptor = + ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class); + verify(mAppStateTracker).addListener(trackerListenerCaptor.capture()); + mListener = trackerListenerCaptor.getValue(); final ArgumentCaptor chargingReceiverCaptor = ArgumentCaptor.forClass(AlarmManagerService.ChargingReceiver.class); @@ -613,8 +624,13 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { } private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { - mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", - FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null, 0); + setTestAlarmWithListener(type, triggerTime, listener, WINDOW_EXACT, TEST_CALLING_UID); + } + + private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener, + long windowLength, int callingUid) { + mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test", + FLAG_STANDALONE, null, null, callingUid, TEST_CALLING_PACKAGE, null, 0); } private PendingIntent getNewMockPendingIntent() { @@ -638,6 +654,15 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { return mockPi; } + private IAlarmListener getNewListener(Runnable onAlarm) { + return new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + onAlarm.run(); + } + }; + } + private void setDeviceConfigInt(String key, int val) { mDeviceConfigKeys.add(key); doReturn(val).when(mDeviceConfigProperties).getInt(eq(key), anyInt()); @@ -1213,10 +1238,6 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { @Test public void testAlarmRestrictedByFAS() throws Exception { - final ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class); - verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture()); - final PendingIntent alarmPi = getNewMockPendingIntent(); when(mAppStateTracker.areAlarmsRestricted(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); @@ -1231,7 +1252,7 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { mTestTimer.expire(); assertNotNull(restrictedAlarms.get(TEST_CALLING_UID)); - listenerArgumentCaptor.getValue().unblockAlarmsForUid(TEST_CALLING_UID); + mListener.unblockAlarmsForUid(TEST_CALLING_UID); verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); assertNull(restrictedAlarms.get(TEST_CALLING_UID)); @@ -1239,11 +1260,6 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { @Test public void alarmsRemovedOnAppStartModeDisabled() { - final ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class); - verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture()); - final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue(); - final PendingIntent alarmPi1 = getNewMockPendingIntent(); final PendingIntent alarmPi2 = getNewMockPendingIntent(); @@ -1254,7 +1270,7 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { when(mActivityManagerInternal.isAppStartModeDisabled(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); - listener.removeAlarmsForUid(TEST_CALLING_UID); + mListener.removeAlarmsForUid(TEST_CALLING_UID); assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } @@ -1305,9 +1321,8 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { @Test public void alarmCountOnSetPi() { final int numAlarms = 103; - final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; for (int i = 1; i <= numAlarms; i++) { - setTestAlarm(types[i % 4], mNowElapsedTest + i, getNewMockPendingIntent()); + setTestAlarm(ALARM_TYPES[i % 4], mNowElapsedTest + i, getNewMockPendingIntent()); assertEquals(i, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } } @@ -1315,13 +1330,9 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { @Test public void alarmCountOnSetListener() { final int numAlarms = 103; - final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; for (int i = 1; i <= numAlarms; i++) { - setTestAlarmWithListener(types[i % 4], mNowElapsedTest + i, new IAlarmListener.Stub() { - @Override - public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { - } - }); + setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i, + getNewListener(() -> {})); assertEquals(i, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } } @@ -1346,12 +1357,7 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { final int numAlarms = 8; // This test is slow for (int i = 0; i < numAlarms; i++) { setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10, - new IAlarmListener.Stub() { - @Override - public void doAlarm(IAlarmCompleteListener callback) - throws RemoteException { - } - }); + getNewListener(() -> {})); } int expired = 0; while (expired < numAlarms) { @@ -1384,12 +1390,9 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { public void alarmCountOnExceptionWhileCallingListener() throws Exception { final int numAlarms = 5; // This test is slow for (int i = 0; i < numAlarms; i++) { - final IAlarmListener listener = new IAlarmListener.Stub() { - @Override - public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { - throw new RemoteException("For testing behavior on exception"); - } - }; + final IAlarmListener listener = getNewListener(() -> { + throw new RuntimeException("For testing behavior on exception"); + }); setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10, listener); } int expired = 0; @@ -1476,7 +1479,7 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { @Test public void alarmTypes() throws Exception { - final int[] typesToSet = {ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME, RTC_WAKEUP, RTC}; + final int[] typesToSet = ALARM_TYPES; final int[] typesExpected = {ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; assertAlarmTypeConversion(typesToSet, typesExpected); @@ -1515,11 +1518,7 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { final int numAlarms = 10; final IAlarmListener[] listeners = new IAlarmListener[numAlarms]; for (int i = 0; i < numAlarms; i++) { - listeners[i] = new IAlarmListener.Stub() { - @Override - public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { - } - }; + listeners[i] = getNewListener(() -> {}); setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]); } assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); @@ -1562,13 +1561,10 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { final int numAlarms = 5; final AtomicInteger alarmsFired = new AtomicInteger(0); for (int i = 0; i < numAlarms; i++) { - final IAlarmListener listener = new IAlarmListener.Stub() { - @Override - public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { - alarmsFired.incrementAndGet(); - mService.mPendingNonWakeupAlarms.clear(); - } - }; + final IAlarmListener listener = getNewListener(() -> { + alarmsFired.incrementAndGet(); + mService.mPendingNonWakeupAlarms.clear(); + }); setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 5, listener); } doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong()); @@ -1956,11 +1952,6 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { @Test public void batterySaverThrottling() { - final ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class); - verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture()); - final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue(); - when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); @@ -1970,12 +1961,12 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(false); - listener.updateAllAlarms(); + mListener.updateAllAlarms(); assertEquals(mNowElapsedTest + 7, mTestTimer.getElapsed()); when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); - listener.updateAlarmsForUid(TEST_CALLING_UID); + mListener.updateAlarmsForUid(TEST_CALLING_UID); assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); } @@ -2229,6 +2220,7 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { private void mockChangeEnabled(long changeId, boolean enabled) { doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(), any(UserHandle.class))); + doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyInt())); } @Test @@ -3753,6 +3745,67 @@ public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase { testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_RARE); } + @Test + public void exactListenerAlarmsRemovedOnCached() { + mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true); + + setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT, + TEST_CALLING_UID); + setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID); + setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0, + TEST_CALLING_UID, null); + setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null); + + setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT, + TEST_CALLING_UID_2); + setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2); + setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0, + TEST_CALLING_UID_2, null); + setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null); + + assertEquals(8, mService.mAlarmStore.size()); + + mListener.removeListenerAlarmsForCachedUid(TEST_CALLING_UID); + assertEquals(7, mService.mAlarmStore.size()); + + mListener.removeListenerAlarmsForCachedUid(TEST_CALLING_UID_2); + assertEquals(6, mService.mAlarmStore.size()); + } + + @Test + public void alarmCountOnListenerCached() { + mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true); + + // Set some alarms for TEST_CALLING_UID. + final int numExactListenerUid1 = 14; + for (int i = 0; i < numExactListenerUid1; i++) { + setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i, + getNewListener(() -> {})); + } + setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID); + setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent()); + setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null); + + // Set some alarms for TEST_CALLING_UID_2. + final int numExactListenerUid2 = 9; + for (int i = 0; i < numExactListenerUid2; i++) { + setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i, + getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2); + } + setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2); + setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0, + TEST_CALLING_UID_2, null); + + assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2)); + + mListener.removeListenerAlarmsForCachedUid(TEST_CALLING_UID); + assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + + mListener.removeListenerAlarmsForCachedUid(TEST_CALLING_UID_2); + assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2)); + } + @Override public void afterSessionFinished() { LocalServices.removeServiceForTest(AlarmManagerInternal.class);