From ee501485b86708a181ecf28a6030999160b493ff Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Sat, 11 May 2024 16:31:33 +0800 Subject: [PATCH] Schedule periodic job in next full-hour timestamp under local timezone. Bug:315228870 Test: atest SettingsRoboTests:com.android.settings.fuelgauge.batteryusage.PeriodicJobManagerTest Change-Id: I1d2b298ea53c1018b5f94b5ba00692055374eef2 --- .../batteryusage/PeriodicJobManager.java | 33 +++-- .../batteryusage/PeriodicJobManagerTest.java | 114 +++++++++++++++--- 2 files changed, 112 insertions(+), 35 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java index b2c72bf1406..c2dcb99a994 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java @@ -28,7 +28,6 @@ import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; import com.android.settings.overlay.FeatureFactory; -import java.time.Clock; import java.time.Duration; /** Manages the periodic job to schedule or cancel the next job. */ @@ -41,8 +40,6 @@ public final class PeriodicJobManager { private final Context mContext; private final AlarmManager mAlarmManager; - @VisibleForTesting static final int DATA_FETCH_INTERVAL_MINUTE = 60; - @VisibleForTesting static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis(); @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -76,20 +73,21 @@ public final class PeriodicJobManager { // Cancels the previous alert job and schedules the next one. final PendingIntent pendingIntent = getPendingIntent(); cancelJob(pendingIntent); - // Uses UTC time to avoid scheduler is impacted by different timezone. - final long triggerAtMillis = getTriggerAtMillis(mContext, Clock.systemUTC(), fromBoot); + // Uses the timestamp of next full hour in local timezone. + long currentTimeMillis = System.currentTimeMillis(); + final long triggerAtMillis = getTriggerAtMillis(currentTimeMillis, fromBoot); mAlarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); - final String utcToLocalTime = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis); + final String timeForLogging = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis); BatteryUsageLogUtils.writeLog( mContext, Action.SCHEDULE_JOB, - String.format("triggerTime=%s, fromBoot=%b", utcToLocalTime, fromBoot)); - Log.d(TAG, "schedule next alarm job at " + utcToLocalTime); + String.format("triggerTime=%s, fromBoot=%b", timeForLogging, fromBoot)); + Log.d(TAG, "schedule next alarm job at " + timeForLogging); } - void cancelJob(PendingIntent pendingIntent) { + private void cancelJob(PendingIntent pendingIntent) { if (mAlarmManager != null) { mAlarmManager.cancel(pendingIntent); } else { @@ -97,22 +95,21 @@ public final class PeriodicJobManager { } } - /** Gets the next alarm trigger UTC time in milliseconds. */ - static long getTriggerAtMillis(Context context, Clock clock, final boolean fromBoot) { - long currentTimeMillis = clock.millis(); + /** Gets the next alarm trigger time in milliseconds. */ + @VisibleForTesting + static long getTriggerAtMillis(final long currentTimeMillis, final boolean fromBoot) { final boolean delayHourlyJobWhenBooting = FeatureFactory.getFeatureFactory() .getPowerUsageFeatureProvider() .delayHourlyJobWhenBooting(); - // Rounds to the previous nearest time slot and shifts to the next one. - long timeSlotUnit = Duration.ofMinutes(DATA_FETCH_INTERVAL_MINUTE).toMillis(); - long targetTime = (currentTimeMillis / timeSlotUnit) * timeSlotUnit + timeSlotUnit; + long targetTimeMillis = TimestampUtils.getNextHourTimestamp(currentTimeMillis); if (delayHourlyJobWhenBooting && fromBoot - && (targetTime - currentTimeMillis) <= sBroadcastDelayFromBoot) { - targetTime += timeSlotUnit; + && (targetTimeMillis - currentTimeMillis) <= sBroadcastDelayFromBoot) { + // Skips this time broadcast, schedule in the next alarm trigger. + targetTimeMillis = TimestampUtils.getNextHourTimestamp(targetTimeMillis); } - return targetTime; + return targetTimeMillis; } private PendingIntent getPendingIntent() { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java index 69905354a52..7c2abd86d57 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; import static org.robolectric.Shadows.shadowOf; import android.app.AlarmManager; @@ -25,7 +26,7 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; -import com.android.settings.testutils.FakeClock; +import com.android.settings.testutils.FakeFeatureFactory; import org.junit.After; import org.junit.Before; @@ -35,6 +36,8 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowAlarmManager; import java.time.Duration; +import java.util.Calendar; +import java.util.TimeZone; /** Tests of {@link PeriodicJobManager}. */ @RunWith(RobolectricTestRunner.class) @@ -42,11 +45,14 @@ public final class PeriodicJobManagerTest { private Context mContext; private ShadowAlarmManager mShadowAlarmManager; private PeriodicJobManager mPeriodicJobManager; + private FakeFeatureFactory mFeatureFactory; @Before public void setUp() { mContext = ApplicationProvider.getApplicationContext(); mPeriodicJobManager = PeriodicJobManager.getInstance(mContext); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).delayHourlyJobWhenBooting(); mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class)); } @@ -68,28 +74,102 @@ public final class PeriodicJobManagerTest { } @Test - public void getTriggerAtMillis_withoutOffset_returnsExpectedResult() { - long timeSlotUnit = PeriodicJobManager.DATA_FETCH_INTERVAL_MINUTE; - // Sets the current time. - Duration currentTimeDuration = Duration.ofMinutes(timeSlotUnit * 2); - FakeClock fakeClock = new FakeClock(); - fakeClock.setCurrentTime(currentTimeDuration); + public void getTriggerAtMillis_halfFullHourTimeZoneWithoutOffset_returnsExpectedResult() { + final int minutesOffset = 0; + final long currentTimestamp = + setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ false, minutesOffset); + final long expectedTimestamp = + currentTimestamp + Duration.ofMinutes(60 - minutesOffset).toMillis(); assertThat( PeriodicJobManager.getTriggerAtMillis( - mContext, fakeClock, /* fromBoot= */ false)) - .isEqualTo(currentTimeDuration.plusMinutes(timeSlotUnit).toMillis()); + /* currentTimeMillis= */ currentTimestamp, /* fromBoot= */ false)) + .isEqualTo(expectedTimestamp); } @Test - public void getTriggerAtMillis_withOffset_returnsExpectedResult() { - long timeSlotUnit = PeriodicJobManager.DATA_FETCH_INTERVAL_MINUTE; - // Sets the current time. - Duration currentTimeDuration = Duration.ofMinutes(timeSlotUnit * 2); - FakeClock fakeClock = new FakeClock(); - fakeClock.setCurrentTime(currentTimeDuration.plusMinutes(1L).plusMillis(51L)); + public void getTriggerAtMillis_halfFullHourTimeZoneWithOffset_returnsExpectedResult() { + final int minutesOffset = 21; + final long currentTimestamp = + setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ false, minutesOffset); + final long expectedTimestamp = + currentTimestamp + Duration.ofMinutes(60 - minutesOffset).toMillis(); - assertThat(PeriodicJobManager.getTriggerAtMillis(mContext, fakeClock, /* fromBoot= */ true)) - .isEqualTo(currentTimeDuration.plusMinutes(timeSlotUnit).toMillis()); + assertThat( + PeriodicJobManager.getTriggerAtMillis( + /* currentTimeMillis= */ currentTimestamp, /* fromBoot= */ false)) + .isEqualTo(expectedTimestamp); + } + + @Test + public void getTriggerAtMillis_halfFullHourTimeZoneWithBroadcastDelay_returnsExpectedResult() { + doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).delayHourlyJobWhenBooting(); + + final int minutesOffset = 21; + final long currentTimestamp = + setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ false, minutesOffset); + final long expectedTimestamp = + currentTimestamp + Duration.ofMinutes(60 * 2 - minutesOffset).toMillis(); + + assertThat( + PeriodicJobManager.getTriggerAtMillis( + /* currentTimeMillis= */ currentTimestamp, /* fromBoot= */ true)) + .isEqualTo(expectedTimestamp); + } + + @Test + public void getTriggerAtMillis_fullHourTimeZoneWithoutOffset_returnsExpectedResult() { + final int minutesOffset = 0; + final long currentTimestamp = + setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ true, minutesOffset); + final long expectedTimestamp = + currentTimestamp + Duration.ofMinutes(60 - minutesOffset).toMillis(); + + assertThat( + PeriodicJobManager.getTriggerAtMillis( + /* currentTimeMillis= */ currentTimestamp, /* fromBoot= */ false)) + .isEqualTo(expectedTimestamp); + } + + @Test + public void getTriggerAtMillis_fullHourTimeZoneWithOffset_returnsExpectedResult() { + final int minutesOffset = 21; + final long currentTimestamp = + setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ true, minutesOffset); + final long expectedTimestamp = + currentTimestamp + Duration.ofMinutes(60 - minutesOffset).toMillis(); + + assertThat( + PeriodicJobManager.getTriggerAtMillis( + /* currentTimeMillis= */ currentTimestamp, /* fromBoot= */ false)) + .isEqualTo(expectedTimestamp); + } + + @Test + public void getTriggerAtMillis_fullHourTimeZoneWithBroadcastDelay_returnsExpectedResult() { + doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).delayHourlyJobWhenBooting(); + + final int minutesOffset = 21; + final long currentTimestamp = + setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ true, minutesOffset); + final long expectedTimestamp = + currentTimestamp + Duration.ofMinutes(60 * 2 - minutesOffset).toMillis(); + + assertThat( + PeriodicJobManager.getTriggerAtMillis( + /* currentTimeMillis= */ currentTimestamp, /* fromBoot= */ true)) + .isEqualTo(expectedTimestamp); + } + + private static long setTimeZoneAndGenerateTestTimestamp( + final boolean isFullHourTimeZone, final int minutesOffset) { + final TimeZone timeZone = + TimeZone.getTimeZone(isFullHourTimeZone ? "UTC" : /* GMT+05:30 */ "Asia/Kalkata"); + TimeZone.setDefault(timeZone); + Calendar calendar = (Calendar) Calendar.getInstance().clone(); + calendar.set(Calendar.MINUTE, minutesOffset); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTimeInMillis(); } }