Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee501485 authored by mxyyiyi's avatar mxyyiyi
Browse files

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
parent 67541a94
Loading
Loading
Loading
Loading
+15 −18
Original line number Diff line number Diff line
@@ -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() {
+98 −18
Original line number Diff line number Diff line
@@ -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));

        assertThat(PeriodicJobManager.getTriggerAtMillis(mContext, fakeClock, /* fromBoot= */ true))
                .isEqualTo(currentTimeDuration.plusMinutes(timeSlotUnit).toMillis());
    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(
                                /* 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();
    }
}