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:
mxyyiyi
2024-05-11 16:31:33 +08:00
parent 67541a9444
commit ee501485b8
2 changed files with 112 additions and 35 deletions

View File

@@ -28,7 +28,6 @@ import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import java.time.Clock;
import java.time.Duration; import java.time.Duration;
/** Manages the periodic job to schedule or cancel the next job. */ /** 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 Context mContext;
private final AlarmManager mAlarmManager; private final AlarmManager mAlarmManager;
@VisibleForTesting static final int DATA_FETCH_INTERVAL_MINUTE = 60;
@VisibleForTesting static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis(); @VisibleForTesting static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis();
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -76,20 +73,21 @@ public final class PeriodicJobManager {
// Cancels the previous alert job and schedules the next one. // Cancels the previous alert job and schedules the next one.
final PendingIntent pendingIntent = getPendingIntent(); final PendingIntent pendingIntent = getPendingIntent();
cancelJob(pendingIntent); cancelJob(pendingIntent);
// Uses UTC time to avoid scheduler is impacted by different timezone. // Uses the timestamp of next full hour in local timezone.
final long triggerAtMillis = getTriggerAtMillis(mContext, Clock.systemUTC(), fromBoot); long currentTimeMillis = System.currentTimeMillis();
final long triggerAtMillis = getTriggerAtMillis(currentTimeMillis, fromBoot);
mAlarmManager.setExactAndAllowWhileIdle( mAlarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
final String utcToLocalTime = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis); final String timeForLogging = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis);
BatteryUsageLogUtils.writeLog( BatteryUsageLogUtils.writeLog(
mContext, mContext,
Action.SCHEDULE_JOB, Action.SCHEDULE_JOB,
String.format("triggerTime=%s, fromBoot=%b", utcToLocalTime, fromBoot)); String.format("triggerTime=%s, fromBoot=%b", timeForLogging, fromBoot));
Log.d(TAG, "schedule next alarm job at " + utcToLocalTime); Log.d(TAG, "schedule next alarm job at " + timeForLogging);
} }
void cancelJob(PendingIntent pendingIntent) { private void cancelJob(PendingIntent pendingIntent) {
if (mAlarmManager != null) { if (mAlarmManager != null) {
mAlarmManager.cancel(pendingIntent); mAlarmManager.cancel(pendingIntent);
} else { } else {
@@ -97,22 +95,21 @@ public final class PeriodicJobManager {
} }
} }
/** Gets the next alarm trigger UTC time in milliseconds. */ /** Gets the next alarm trigger time in milliseconds. */
static long getTriggerAtMillis(Context context, Clock clock, final boolean fromBoot) { @VisibleForTesting
long currentTimeMillis = clock.millis(); static long getTriggerAtMillis(final long currentTimeMillis, final boolean fromBoot) {
final boolean delayHourlyJobWhenBooting = final boolean delayHourlyJobWhenBooting =
FeatureFactory.getFeatureFactory() FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider() .getPowerUsageFeatureProvider()
.delayHourlyJobWhenBooting(); .delayHourlyJobWhenBooting();
// Rounds to the previous nearest time slot and shifts to the next one. long targetTimeMillis = TimestampUtils.getNextHourTimestamp(currentTimeMillis);
long timeSlotUnit = Duration.ofMinutes(DATA_FETCH_INTERVAL_MINUTE).toMillis();
long targetTime = (currentTimeMillis / timeSlotUnit) * timeSlotUnit + timeSlotUnit;
if (delayHourlyJobWhenBooting if (delayHourlyJobWhenBooting
&& fromBoot && fromBoot
&& (targetTime - currentTimeMillis) <= sBroadcastDelayFromBoot) { && (targetTimeMillis - currentTimeMillis) <= sBroadcastDelayFromBoot) {
targetTime += timeSlotUnit; // Skips this time broadcast, schedule in the next alarm trigger.
targetTimeMillis = TimestampUtils.getNextHourTimestamp(targetTimeMillis);
} }
return targetTime; return targetTimeMillis;
} }
private PendingIntent getPendingIntent() { private PendingIntent getPendingIntent() {

View File

@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.robolectric.Shadows.shadowOf; import static org.robolectric.Shadows.shadowOf;
import android.app.AlarmManager; import android.app.AlarmManager;
@@ -25,7 +26,7 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider; 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.After;
import org.junit.Before; import org.junit.Before;
@@ -35,6 +36,8 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowAlarmManager; import org.robolectric.shadows.ShadowAlarmManager;
import java.time.Duration; import java.time.Duration;
import java.util.Calendar;
import java.util.TimeZone;
/** Tests of {@link PeriodicJobManager}. */ /** Tests of {@link PeriodicJobManager}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@@ -42,11 +45,14 @@ public final class PeriodicJobManagerTest {
private Context mContext; private Context mContext;
private ShadowAlarmManager mShadowAlarmManager; private ShadowAlarmManager mShadowAlarmManager;
private PeriodicJobManager mPeriodicJobManager; private PeriodicJobManager mPeriodicJobManager;
private FakeFeatureFactory mFeatureFactory;
@Before @Before
public void setUp() { public void setUp() {
mContext = ApplicationProvider.getApplicationContext(); mContext = ApplicationProvider.getApplicationContext();
mPeriodicJobManager = PeriodicJobManager.getInstance(mContext); mPeriodicJobManager = PeriodicJobManager.getInstance(mContext);
mFeatureFactory = FakeFeatureFactory.setupForTest();
doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).delayHourlyJobWhenBooting();
mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class)); mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class));
} }
@@ -68,28 +74,102 @@ public final class PeriodicJobManagerTest {
} }
@Test @Test
public void getTriggerAtMillis_withoutOffset_returnsExpectedResult() { public void getTriggerAtMillis_halfFullHourTimeZoneWithoutOffset_returnsExpectedResult() {
long timeSlotUnit = PeriodicJobManager.DATA_FETCH_INTERVAL_MINUTE; final int minutesOffset = 0;
// Sets the current time. final long currentTimestamp =
Duration currentTimeDuration = Duration.ofMinutes(timeSlotUnit * 2); setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ false, minutesOffset);
FakeClock fakeClock = new FakeClock(); final long expectedTimestamp =
fakeClock.setCurrentTime(currentTimeDuration); currentTimestamp + Duration.ofMinutes(60 - minutesOffset).toMillis();
assertThat( assertThat(
PeriodicJobManager.getTriggerAtMillis( PeriodicJobManager.getTriggerAtMillis(
mContext, fakeClock, /* fromBoot= */ false)) /* currentTimeMillis= */ currentTimestamp, /* fromBoot= */ false))
.isEqualTo(currentTimeDuration.plusMinutes(timeSlotUnit).toMillis()); .isEqualTo(expectedTimestamp);
} }
@Test @Test
public void getTriggerAtMillis_withOffset_returnsExpectedResult() { public void getTriggerAtMillis_halfFullHourTimeZoneWithOffset_returnsExpectedResult() {
long timeSlotUnit = PeriodicJobManager.DATA_FETCH_INTERVAL_MINUTE; final int minutesOffset = 21;
// Sets the current time. final long currentTimestamp =
Duration currentTimeDuration = Duration.ofMinutes(timeSlotUnit * 2); setTimeZoneAndGenerateTestTimestamp(/* isFullHourTimeZone= */ false, minutesOffset);
FakeClock fakeClock = new FakeClock(); final long expectedTimestamp =
fakeClock.setCurrentTime(currentTimeDuration.plusMinutes(1L).plusMillis(51L)); currentTimestamp + Duration.ofMinutes(60 - minutesOffset).toMillis();
assertThat(PeriodicJobManager.getTriggerAtMillis(mContext, fakeClock, /* fromBoot= */ true)) assertThat(
.isEqualTo(currentTimeDuration.plusMinutes(timeSlotUnit).toMillis()); 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();
} }
} }