Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04bf84e2 authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

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
parent 0085edf0
Loading
Loading
Loading
Loading
+29 −1
Original line number Diff line number Diff line
@@ -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.
     * <p>
     * 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.
     *
     * <p class="note"><strong>Note:</strong>
     * <p>
     * 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.
     *
     * <p class="note"><strong>Note:</strong>
     * 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 <allow-in-power-save>}
     * within the system config.
     *
     * <p class="note"><strong>Note:</strong>
     * 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 {
     *
     * <p> See {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} for more details.
     *
     * <p class="note"><strong>Note:</strong>
     * 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
+25 −1
Original line number Diff line number Diff line
@@ -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);
            }
        }

+30 −1
Original line number Diff line number Diff line
@@ -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) {
+97 −1
Original line number Diff line number Diff line
@@ -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);
    }

+109 −56

File changed.

Preview size limit exceeded, changes collapsed.