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
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user