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

Commit abdd736f authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Fix movement of RTC alarms with time changes

Whenever the system time changes, we re-add all the alarms but the
re-adding logic was not resetting the expectedWhenElapsed fields,
resulting in wrong assignment of the final whenElapsed upon app-standby
adjustment evaluation which assumes expectedWhenElapsed >= whenElapsed.

Also, whenever app-standby adjusted delivery of a repeating alarm, it
was incorrectly scheduling its subsequent occurrence.

Test: atest com.android.server.AlarmManagerServiceTest
atest CtsAlarmManagerTestCases:TimeChangeTests

Bug: 120187902
Bug: 110910377
Change-Id: I4bdb72ae55bad45ef666f7caa5d125e8278d85fd
parent 065e1521
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -1138,8 +1138,8 @@ class AlarmManagerService extends SystemService {
                    ? clampPositive(whenElapsed + a.windowLength)
                    : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
        }
        a.whenElapsed = whenElapsed;
        a.maxWhenElapsed = maxElapsed;
        a.expectedWhenElapsed = a.whenElapsed = whenElapsed;
        a.expectedMaxWhenElapsed = a.maxWhenElapsed = maxElapsed;
        setImplLocked(a, true, doValidate);
    }

@@ -1243,7 +1243,7 @@ class AlarmManagerService extends SystemService {
                alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
                // Also schedule its next recurrence
                final long delta = alarm.count * alarm.repeatInterval;
                final long nextElapsed = alarm.whenElapsed + delta;
                final long nextElapsed = alarm.expectedWhenElapsed + delta;
                setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
                        maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
                        alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
@@ -3596,10 +3596,9 @@ class AlarmManagerService extends SystemService {
                    // this adjustment will be zero if we're late by
                    // less than one full repeat interval
                    alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;

                    // Also schedule its next recurrence
                    final long delta = alarm.count * alarm.repeatInterval;
                    final long nextElapsed = alarm.whenElapsed + delta;
                    final long nextElapsed = alarm.expectedWhenElapsed + delta;
                    setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
                            maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
                            alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
+48 −3
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ import static com.android.server.AlarmManagerService.Constants.KEY_LISTENER_TIME
import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
import static com.android.server.AlarmManagerService.IS_WAKEUP_MASK;
import static com.android.server.AlarmManagerService.TIME_CHANGED_MASK;
import static com.android.server.AlarmManagerService.WORKING_INDEX;

import static org.junit.Assert.assertEquals;
@@ -126,13 +128,15 @@ public class AlarmManagerServiceTest {
    private MockitoSession mMockingSession;
    private Injector mInjector;
    private volatile long mNowElapsedTest;
    private volatile long mNowRtcTest;
    @GuardedBy("mTestTimer")
    private TestTimer mTestTimer = new TestTimer();

    static class TestTimer {
        private long mElapsed;
        boolean mExpired;
        int mType;
        private int mType;
        private int mFlags; // Flags used to decide what needs to be evaluated.

        synchronized long getElapsed() {
            return mElapsed;
@@ -147,7 +151,16 @@ public class AlarmManagerServiceTest {
            return mType;
        }

        synchronized int getFlags() {
            return mFlags;
        }

        synchronized void expire() throws InterruptedException {
            expire(IS_WAKEUP_MASK); // Default: evaluate eligibility of all alarms
        }

        synchronized void expire(int flags) throws InterruptedException {
            mFlags = flags;
            mExpired = true;
            notifyAll();
            // Now wait for the alarm thread to finish execution.
@@ -181,7 +194,7 @@ public class AlarmManagerServiceTest {
                }
                mTestTimer.mExpired = false;
            }
            return AlarmManagerService.IS_WAKEUP_MASK; // Doesn't matter, just evaluate.
            return mTestTimer.getFlags();
        }

        @Override
@@ -214,6 +227,11 @@ public class AlarmManagerServiceTest {
            return mNowElapsedTest;
        }

        @Override
        long getCurrentTimeMillis() {
            return mNowRtcTest;
        }

        @Override
        AlarmManagerService.ClockReceiver getClockReceiver(AlarmManagerService service) {
            return mClockReceiver;
@@ -340,13 +358,40 @@ public class AlarmManagerServiceTest {
    }

    @Test
    public void testSingleAlarmSet() {
    public void singleElapsedAlarmSet() {
        final long triggerTime = mNowElapsedTest + 5000;
        final PendingIntent alarmPi = getNewMockPendingIntent();
        setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
        assertEquals(triggerTime, mTestTimer.getElapsed());
    }

    @Test
    public void singleRtcAlarmSet() {
        mNowElapsedTest = 54;
        mNowRtcTest = 1243;     // arbitrary values of time
        final long triggerRtc = mNowRtcTest + 5000;
        final PendingIntent alarmPi = getNewMockPendingIntent();
        setTestAlarm(RTC_WAKEUP, triggerRtc, alarmPi);
        final long triggerElapsed = triggerRtc - (mNowRtcTest - mNowElapsedTest);
        assertEquals(triggerElapsed, mTestTimer.getElapsed());
    }

    @Test
    public void timeChangeMovesRtcAlarm() throws Exception {
        mNowElapsedTest = 42;
        mNowRtcTest = 4123;     // arbitrary values of time
        final long triggerRtc = mNowRtcTest + 5000;
        final PendingIntent alarmPi = getNewMockPendingIntent();
        setTestAlarm(RTC_WAKEUP, triggerRtc, alarmPi);
        final long triggerElapsed1 = mTestTimer.getElapsed();
        final long timeDelta = -123;
        mNowRtcTest += timeDelta;
        mTestTimer.expire(TIME_CHANGED_MASK);
        final long triggerElapsed2 = mTestTimer.getElapsed();
        assertEquals("Invalid movement of triggerElapsed following time change", triggerElapsed2,
                triggerElapsed1 - timeDelta);
    }

    @Test
    public void testSingleAlarmExpiration() throws Exception {
        final long triggerTime = mNowElapsedTest + 5000;