summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Shintaro Kawamura <kawasin@google.com> 2024-08-02 17:05:03 +0900
committer Shintaro Kawamura <kawasin@google.com> 2024-08-08 16:20:13 +0900
commit8ef22c403ed30aba5ec69d6be9bf0a23c240028d (patch)
treeb79e5db8b9deffdb084dfc9e477263f0945c263d
parentaa92cb88a2094cf5c231b83042d47280e786052d (diff)
Initialize timestamp in AlarmManagerService with actual timestamps
AlarmManagerService sends android.intent.action.TIME_SET broadcast if real time clock and boot time clock proceed differently which means the real time is changed (e.g. by timezone change). Kernel notifies the time change by returning ECANCELED on timerfd. AlarmManagerService filteres the notifications with less than 1 second difference out because kernel can notifies small adjustment. With initializing timestamps (mLastTimeChangeClockTime, mLastTimeChangeRealtime) as 0, AlarmManagerService always sends android.intent.action.TIME_SET for the first time change notification from the kernel even if the time adjustment was small. While stale TIME_SET broadcast does not break any logic technically because applications usually just reset their timers, waking up BroadcastReceiver of applications simultaneously has a performance impact on devices on which the processes for the applications had not been launched and the system end up launching application processes. ARCVM on ChromeOS can easily reproduce the stale TIME_SET broadcast by suspending and resuming the device for the first time. Initializing the timestamps as 0 was introduced from BatteryStatsImpl logic by this commit: https://gerrit.aospa.co/plugins/gitiles/AOSPA/android_frameworks_base/+/c352722e8af0a5510144b5f32ea87561db553f42%5E%21/ Before that BatteryStatsImpl had just monitored time change notifications for a stats purpose and a stale notification did not have much cost. However sending a broadcast to applications costs more on AlarmManagerService. The cost of initializing the 2 timestamps on the AlarmManager startup is small enough to avoid the stale broadcast. Bug: 356564926 Test: manually check that no TIME_SET broadcast is sent on the first susupend/resume on ARCVM Test: atest FrameworksMockingServicesTests:com.android.server.alarm Flag: EXEMPT bugfix Change-Id: Ibad10ad6a7c2812dc085f80c1a94a3026f9fb96f
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java58
2 files changed, 65 insertions, 3 deletions
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 18ee6f2c7992..ba66ff72bfdd 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4441,6 +4441,11 @@ public class AlarmManagerService extends SystemService {
public void run() {
ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
+ synchronized (mLock) {
+ mLastTimeChangeClockTime = mInjector.getCurrentTimeMillis();
+ mLastTimeChangeRealtime = mInjector.getElapsedRealtimeMillis();
+ }
+
while (true) {
int result = mInjector.waitForAlarm();
final long nowRTC = mInjector.getCurrentTimeMillis();
@@ -4464,10 +4469,9 @@ public class AlarmManagerService extends SystemService {
expectedClockTime = lastTimeChangeClockTime
+ (nowELAPSED - mLastTimeChangeRealtime);
}
- if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime - 1000)
+ if (nowRTC < (expectedClockTime - 1000)
|| nowRTC > (expectedClockTime + 1000)) {
- // The change is by at least +/- 1000 ms (or this is the first change),
- // let's do it!
+ // The change is by at least +/- 1000 ms, let's do it!
if (DEBUG_BATCH) {
Slog.v(TAG, "Time changed notification from kernel; rebatching");
}
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 b980ca05b609..30de0e8c7981 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -512,6 +512,9 @@ public final class AlarmManagerServiceTest {
when(mPermissionManagerInternal.getAppOpPermissionPackages(
SCHEDULE_EXACT_ALARM)).thenReturn(EmptyArray.STRING);
+ // Initialize timestamps with arbitrary values of time
+ mNowElapsedTest = 12;
+ mNowRtcTest = 345;
mInjector = new Injector(mMockContext);
mService = new AlarmManagerService(mMockContext, mInjector);
spyOn(mService);
@@ -774,6 +777,61 @@ public final class AlarmManagerServiceTest {
}
@Test
+ public void timeChangeBroadcastForward() throws Exception {
+ final long timeDelta = 12345;
+ // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms more than boot
+ // time clock.
+ mNowRtcTest += timeDelta + 1001;
+ mNowElapsedTest += timeDelta;
+ mTestTimer.expire(TIME_CHANGED_MASK);
+
+ verify(mMockContext)
+ .sendBroadcastAsUser(
+ argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED),
+ eq(UserHandle.ALL),
+ isNull(),
+ any());
+ }
+
+ @Test
+ public void timeChangeBroadcastBackward() throws Exception {
+ final long timeDelta = 12345;
+ // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms less than boot
+ // time clock.
+ mNowRtcTest += timeDelta - 1001;
+ mNowElapsedTest += timeDelta;
+ mTestTimer.expire(TIME_CHANGED_MASK);
+
+ verify(mMockContext)
+ .sendBroadcastAsUser(
+ argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED),
+ eq(UserHandle.ALL),
+ isNull(),
+ any());
+ }
+
+ @Test
+ public void timeChangeFilterMinorAdjustment() throws Exception {
+ final long timeDelta = 12345;
+ // AlarmManagerService does not send the broadcast if real time clock proceeds within 1000ms
+ // than boot time clock.
+ mNowRtcTest += timeDelta + 1000;
+ mNowElapsedTest += timeDelta;
+ mTestTimer.expire(TIME_CHANGED_MASK);
+
+ mNowRtcTest += timeDelta - 1000;
+ mNowElapsedTest += timeDelta;
+ mTestTimer.expire(TIME_CHANGED_MASK);
+
+ verify(mMockContext, never())
+ .sendBroadcastAsUser(
+ argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED),
+ any(),
+ any(),
+ any());
+ }
+
+ @Test
public void testSingleAlarmExpiration() throws Exception {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();