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