From 04bf84e220ade9d7ad8ef0b2f7e6ce6ec72841c8 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Thu, 9 Feb 2023 01:31:14 -0800 Subject: [PATCH] Remove exact listener alarms when uid gets cached Listener alarms are only meant to be used within the duration of a valid lifecycle. Once the app goes out of lifecycle, it can be killed arbitrarily and the alarm will be removed. Therefore, no apps should be relying on receiving an alarm after they go out of lifecycle. Besides, apps can be frozen when they end up in the cached state. This breaks the alarm delivery guarantees for listener based exact alarms. So even though dropping the alarm while the app is frozen can cause inconsistencies in the app's view of the world, this is now done explicitly as per the contract of the listener exact alarm APIs: Exact alarms expecting a callback on a listener will now be dropped as soon as the app goes into cached state. This is done to ensure consistency and reliability of behavior. Test: atest FrameworksMockingServicesTests:AlarmManagerServiceTest Test: atest FrameworksMockingServicesTests:AppStateTrackerTest Bug: 265195908 Change-Id: If95a0aa7416c4233b244144b353e471322c303a2 --- .../java/android/app/AlarmManager.java | 30 +++- .../android/server/AppStateTrackerImpl.java | 26 ++- .../server/alarm/AlarmManagerService.java | 31 +++- .../android/server/AppStateTrackerTest.java | 98 ++++++++++- .../server/alarm/AlarmManagerServiceTest.java | 165 ++++++++++++------ 5 files changed, 290 insertions(+), 60 deletions(-) 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);