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
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 72899f6..cb61259 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1138,8 +1138,8 @@
? 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 @@
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 @@
// 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,
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6517303..77e2517 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -43,6 +43,8 @@
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 @@
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 @@
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 @@
}
mTestTimer.mExpired = false;
}
- return AlarmManagerService.IS_WAKEUP_MASK; // Doesn't matter, just evaluate.
+ return mTestTimer.getFlags();
}
@Override
@@ -215,6 +228,11 @@
}
@Override
+ long getCurrentTimeMillis() {
+ return mNowRtcTest;
+ }
+
+ @Override
AlarmManagerService.ClockReceiver getClockReceiver(AlarmManagerService service) {
return mClockReceiver;
}
@@ -340,7 +358,7 @@
}
@Test
- public void testSingleAlarmSet() {
+ public void singleElapsedAlarmSet() {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
@@ -348,6 +366,33 @@
}
@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;
final PendingIntent alarmPi = getNewMockPendingIntent();