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

Commit 4e05326a authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Separating alarm history for lower quota

Using a separate container for lower allow-while-idle quota to avoid out
of quota issues. Using the same container means that the lower quota is
out whenever higher quota is used for alarms from the same app.
Lower quota - currently once per 9 minutes - is used in the
following cases:
 - Inexact allow-while-idle alarms. This means the same app can use
 alarms using high quota and low quota simultaneously.
 - When the caller is targeting < S.
 - When the caller is in the power save allowlist, but does not have the
 permission - for exact alarms.

Test: atest CtsAlarmManagerTestCases
Test: atest FrameworkMockingServicesTests:com.android.server.alarm

Bug: 190788800
Change-Id: Iba46fdd36dc04843e9256827b0754d21cfff89c9
parent 09cb5879
Loading
Loading
Loading
Loading
+26 −7
Original line number Diff line number Diff line
@@ -256,6 +256,7 @@ public class AlarmManagerService extends SystemService {
    AlarmHandler mHandler;
    AppWakeupHistory mAppWakeupHistory;
    AppWakeupHistory mAllowWhileIdleHistory;
    AppWakeupHistory mAllowWhileIdleCompatHistory;
    private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray();
    private final SparseArray<RingBuffer<RemovedAlarm>> mRemovalHistory = new SparseArray<>();
    ClockReceiver mClockReceiver;
@@ -1633,6 +1634,7 @@ public class AlarmManagerService extends SystemService {

            mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
            mAllowWhileIdleHistory = new AppWakeupHistory(INTERVAL_HOUR);
            mAllowWhileIdleCompatHistory = new AppWakeupHistory(INTERVAL_HOUR);

            mNextWakeup = mNextNonWakeup = 0;

@@ -2142,20 +2144,23 @@ public class AlarmManagerService extends SystemService {
            final int userId = UserHandle.getUserId(alarm.creatorUid);
            final int quota;
            final long window;
            final AppWakeupHistory history;
            if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) {
                quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
                window = mConstants.ALLOW_WHILE_IDLE_WINDOW;
                history = mAllowWhileIdleHistory;
            } else {
                quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
                window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
                history = mAllowWhileIdleCompatHistory;
            }
            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
            final int dispatchesInHistory = history.getTotalWakeupsInWindow(
                    alarm.sourcePackage, userId);
            if (dispatchesInWindow < quota) {
            if (dispatchesInHistory < quota) {
                // fine to go out immediately.
                batterySaverPolicyElapsed = nowElapsed;
            } else {
                batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
                batterySaverPolicyElapsed = history.getNthLastWakeupForPackage(
                        alarm.sourcePackage, userId, quota) + window;
            }
        } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
@@ -2201,20 +2206,23 @@ public class AlarmManagerService extends SystemService {
            final int userId = UserHandle.getUserId(alarm.creatorUid);
            final int quota;
            final long window;
            final AppWakeupHistory history;
            if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) {
                quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
                window = mConstants.ALLOW_WHILE_IDLE_WINDOW;
                history = mAllowWhileIdleHistory;
            } else {
                quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
                window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
                history = mAllowWhileIdleCompatHistory;
            }
            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
            final int dispatchesInHistory = history.getTotalWakeupsInWindow(
                    alarm.sourcePackage, userId);
            if (dispatchesInWindow < quota) {
            if (dispatchesInHistory < quota) {
                // fine to go out immediately.
                deviceIdlePolicyTime = nowElapsed;
            } else {
                final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
                final long whenInQuota = history.getNthLastWakeupForPackage(
                        alarm.sourcePackage, userId, quota) + window;
                deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
            }
@@ -2502,6 +2510,7 @@ public class AlarmManagerService extends SystemService {
                        Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
                // The API doesn't allow using both together.
                flags &= ~FLAG_ALLOW_WHILE_IDLE;
                // Prioritized alarms don't need any extra permission to be exact.
            } else if (exact || allowWhileIdle) {
                final boolean needsPermission;
                boolean lowerQuota;
@@ -2992,6 +3001,10 @@ public class AlarmManagerService extends SystemService {
            mAllowWhileIdleHistory.dump(pw, nowELAPSED);
            pw.println();

            pw.println("Allow while idle compat history:");
            mAllowWhileIdleCompatHistory.dump(pw, nowELAPSED);
            pw.println();

            if (mLastPriorityAlarmDispatch.size() > 0) {
                pw.println("Last priority alarm dispatches:");
                pw.increaseIndent();
@@ -4553,6 +4566,7 @@ public class AlarmManagerService extends SystemService {
                            removeUserLocked(userHandle);
                            mAppWakeupHistory.removeForUser(userHandle);
                            mAllowWhileIdleHistory.removeForUser(userHandle);
                            mAllowWhileIdleCompatHistory.removeForUser(userHandle);
                        }
                        return;
                    case Intent.ACTION_UID_REMOVED:
@@ -4588,6 +4602,8 @@ public class AlarmManagerService extends SystemService {
                            // package-removed and package-restarted case
                            mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                            mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                            mAllowWhileIdleCompatHistory.removeForPackage(pkg,
                                    UserHandle.getUserId(uid));
                            removeLocked(uid, REMOVE_REASON_UNDEFINED);
                        } else {
                            // external-applications-unavailable case
@@ -4965,7 +4981,10 @@ public class AlarmManagerService extends SystemService {
                if (isAllowedWhileIdleRestricted(alarm)) {
                    // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the
                    // device was in doze or battery saver.
                    mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
                    final AppWakeupHistory history = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
                            ? mAllowWhileIdleHistory
                            : mAllowWhileIdleCompatHistory;
                    history.recordAlarmForPackage(alarm.sourcePackage,
                            UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
                    mAlarmStore.updateAlarmDeliveries(a -> {
                        if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {
+148 −9
Original line number Diff line number Diff line
@@ -514,8 +514,16 @@ public class AlarmManagerServiceTest {
    }

    private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi,
            boolean unrestricted) {
        final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE;
            boolean unrestricted, boolean compat) {
        assertFalse("Alarm cannot be compat and unrestricted", unrestricted && compat);
        final int flags;
        if (unrestricted) {
            flags = FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
        } else if (compat) {
            flags = FLAG_ALLOW_WHILE_IDLE_COMPAT;
        } else {
            flags = FLAG_ALLOW_WHILE_IDLE;
        }
        setTestAlarm(type, triggerTime, pi, 0, flags, TEST_CALLING_UID);
    }

@@ -1600,13 +1608,13 @@ public class AlarmManagerServiceTest {
        final long firstTrigger = mNowElapsedTest + 10;
        for (int i = 0; i < quota; i++) {
            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
                    getNewMockPendingIntent(), false);
                    getNewMockPendingIntent(), false, false);
            mNowElapsedTest = mTestTimer.getElapsed();
            mTestTimer.expire();
        }
        // This one should get deferred on set.
        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                getNewMockPendingIntent(), false);
                getNewMockPendingIntent(), false, false);
        final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow;
        assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
                mTestTimer.getElapsed());
@@ -1618,6 +1626,108 @@ public class AlarmManagerServiceTest {
        assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed());
    }

    @Test
    public void allowWhileIdleCompatAlarmsWhileDeviceIdle() throws Exception {
        setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0);

        final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + window + 1000,
                getNewMockPendingIntent());
        assertNotNull(mService.mPendingIdleUntil);

        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
        final long firstTrigger = mNowElapsedTest + 10;
        for (int i = 0; i < quota; i++) {
            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
                    getNewMockPendingIntent(), false, true);
            mNowElapsedTest = mTestTimer.getElapsed();
            mTestTimer.expire();
        }
        // This one should get deferred on set.
        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                getNewMockPendingIntent(), false, true);
        final long expectedNextTrigger = firstTrigger + window;
        assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
                mTestTimer.getElapsed());

        // Bring the idle until alarm back.
        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger - 50,
                getNewMockPendingIntent());
        assertEquals(expectedNextTrigger - 50, mService.mPendingIdleUntil.getWhenElapsed());
        assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed());
    }

    @Test
    public void allowWhileIdleCompatHistorySeparate() throws Exception {
        when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                TEST_CALLING_PACKAGE)).thenReturn(true);
        when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);

        final int fullQuota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
        final int compatQuota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;

        final long fullWindow = mAllowWhileIdleWindow;
        final long compatWindow = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;

        final long firstFullTrigger = mNowElapsedTest + 10;
        for (int i = 0; i < fullQuota; i++) {
            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + i,
                    getNewMockPendingIntent(), false, false);
            mNowElapsedTest = mTestTimer.getElapsed();
            mTestTimer.expire();
        }
        // This one should get deferred on set, as full quota is not available.
        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + fullQuota,
                getNewMockPendingIntent(), false, false);
        final long expectedNextFullTrigger = firstFullTrigger + fullWindow;
        assertEquals("Incorrect trigger when no quota left", expectedNextFullTrigger,
                mTestTimer.getElapsed());
        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);

        // The following should be allowed, as compat quota should be free.
        for (int i = 0; i < compatQuota; i++) {
            final long trigger = mNowElapsedTest + 1;
            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(),
                    false, true);
            assertEquals(trigger, mTestTimer.getElapsed());
            mNowElapsedTest = mTestTimer.getElapsed();
            mTestTimer.expire();
        }

        // Refresh the state
        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
        mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
                TEST_CALLING_USER);

        // Now test with flipped order

        final long firstCompatTrigger = mNowElapsedTest + 10;
        for (int i = 0; i < compatQuota; i++) {
            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + i,
                    getNewMockPendingIntent(), false, true);
            mNowElapsedTest = mTestTimer.getElapsed();
            mTestTimer.expire();
        }
        // This one should get deferred on set, as full quota is not available.
        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + compatQuota,
                getNewMockPendingIntent(), false, true);
        final long expectedNextCompatTrigger = firstCompatTrigger + compatWindow;
        assertEquals("Incorrect trigger when no quota left", expectedNextCompatTrigger,
                mTestTimer.getElapsed());
        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);

        // The following should be allowed, as full quota should be free.
        for (int i = 0; i < fullQuota; i++) {
            final long trigger = mNowElapsedTest + 1;
            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(),
                    false, false);
            assertEquals(trigger, mTestTimer.getElapsed());
            mNowElapsedTest = mTestTimer.getElapsed();
            mTestTimer.expire();
        }
    }

    @Test
    public void allowWhileIdleUnrestricted() throws Exception {
        setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0);
@@ -1634,7 +1744,7 @@ public class AlarmManagerServiceTest {
        final long firstTrigger = mNowElapsedTest + 10;
        for (int i = 0; i < numAlarms; i++) {
            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
                    getNewMockPendingIntent(), true);
                    getNewMockPendingIntent(), true, false);
        }
        // All of them should fire as expected.
        for (int i = 0; i < numAlarms; i++) {
@@ -1736,7 +1846,7 @@ public class AlarmManagerServiceTest {
        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;

        testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
                getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);

        // Refresh the state
        mService.removeLocked(TEST_CALLING_UID,
@@ -1744,7 +1854,7 @@ public class AlarmManagerServiceTest {
        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);

        testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
                trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
                trigger, getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);

        // Refresh the state
        mService.removeLocked(TEST_CALLING_UID,
@@ -1752,7 +1862,36 @@ public class AlarmManagerServiceTest {
        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);

        testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
                getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
    }

    @Test
    public void allowWhileIdleCompatAlarmsInBatterySaver() throws Exception {
        when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                TEST_CALLING_PACKAGE)).thenReturn(true);
        when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);

        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
        final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;

        testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
                getNewMockPendingIntent(), false, true), quota, window);

        // Refresh the state
        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
        mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
                TEST_CALLING_USER);

        testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
                trigger, getNewMockPendingIntent(), false, true), quota, window);

        // Refresh the state
        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
        mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
                TEST_CALLING_USER);

        testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
                getNewMockPendingIntent(), false, true), quota, window);
    }

    @Test