Merge "Fix periodic job scheduling." into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e2d64ff898
@@ -18,6 +18,7 @@ package com.android.server.job;
|
||||
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -1801,7 +1802,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
*
|
||||
* @see #maybeQueueReadyJobsForExecutionLocked
|
||||
*/
|
||||
private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
|
||||
@VisibleForTesting
|
||||
JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
|
||||
final long elapsedNowMillis = sElapsedRealtimeClock.millis();
|
||||
final JobInfo job = failureToReschedule.getJob();
|
||||
|
||||
@@ -1848,6 +1850,10 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
elapsedNowMillis + delayMillis,
|
||||
JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
|
||||
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
|
||||
if (job.isPeriodic()) {
|
||||
newJob.setOriginalLatestRunTimeElapsed(
|
||||
failureToReschedule.getOriginalLatestRunTimeElapsed());
|
||||
}
|
||||
for (int ic=0; ic<mControllers.size(); ic++) {
|
||||
StateController controller = mControllers.get(ic);
|
||||
controller.rescheduleForFailureLocked(newJob, failureToReschedule);
|
||||
@@ -1868,23 +1874,41 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* @return A new job representing the execution criteria for this instantiation of the
|
||||
* recurring job.
|
||||
*/
|
||||
private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
|
||||
@VisibleForTesting
|
||||
JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
|
||||
final long elapsedNow = sElapsedRealtimeClock.millis();
|
||||
// Compute how much of the period is remaining.
|
||||
long runEarly = 0L;
|
||||
final long newLatestRuntimeElapsed;
|
||||
final long period = periodicToReschedule.getJob().getIntervalMillis();
|
||||
final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
|
||||
final long flex = periodicToReschedule.getJob().getFlexMillis();
|
||||
|
||||
// If this periodic was rescheduled it won't have a deadline.
|
||||
if (periodicToReschedule.hasDeadlineConstraint()) {
|
||||
runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
|
||||
if (elapsedNow > latestRunTimeElapsed) {
|
||||
// The job ran past its expected run window. Have it count towards the current window
|
||||
// and schedule a new job for the next window.
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Periodic job ran after its intended window.");
|
||||
}
|
||||
final long diffMs = (elapsedNow - latestRunTimeElapsed);
|
||||
int numSkippedWindows = (int) (diffMs / period) + 1; // +1 to include original window
|
||||
if (period != flex && diffMs > Math.min(30 * MINUTE_IN_MILLIS, (period - flex) / 2)) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Custom flex job ran too close to next window.");
|
||||
}
|
||||
// For custom flex periods, if the job was run too close to the next window,
|
||||
// skip the next window and schedule for the following one.
|
||||
numSkippedWindows += 1;
|
||||
}
|
||||
newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
|
||||
} else {
|
||||
newLatestRuntimeElapsed = latestRunTimeElapsed + period;
|
||||
}
|
||||
long flex = periodicToReschedule.getJob().getFlexMillis();
|
||||
long period = periodicToReschedule.getJob().getIntervalMillis();
|
||||
long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
|
||||
long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
|
||||
|
||||
final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
|
||||
newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
|
||||
newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000
|
||||
+ "]s");
|
||||
}
|
||||
return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
|
||||
newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
|
||||
|
||||
@@ -161,6 +161,12 @@ public final class JobStatus {
|
||||
*/
|
||||
private final long latestRunTimeElapsedMillis;
|
||||
|
||||
/**
|
||||
* Valid only for periodic jobs. The original latest point in the future at which this
|
||||
* job was expected to run.
|
||||
*/
|
||||
private long mOriginalLatestRunTimeElapsedMillis;
|
||||
|
||||
/** How many times this job has failed, used to compute back-off. */
|
||||
private final int numFailures;
|
||||
|
||||
@@ -394,6 +400,7 @@ public final class JobStatus {
|
||||
|
||||
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
|
||||
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
|
||||
this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
|
||||
this.numFailures = numFailures;
|
||||
|
||||
int requiredConstraints = job.getConstraintFlags();
|
||||
@@ -871,6 +878,14 @@ public final class JobStatus {
|
||||
return latestRunTimeElapsedMillis;
|
||||
}
|
||||
|
||||
public long getOriginalLatestRunTimeElapsed() {
|
||||
return mOriginalLatestRunTimeElapsedMillis;
|
||||
}
|
||||
|
||||
public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
|
||||
mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fractional position of "now" within the "run time" window of
|
||||
* this job.
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.os.BatteryManagerInternal;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.android.server.AppStateTracker;
|
||||
import com.android.server.DeviceIdleController;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.controllers.JobStatus;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoSession;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
public class JobSchedulerServiceTest {
|
||||
private JobSchedulerService mService;
|
||||
|
||||
private MockitoSession mMockingSession;
|
||||
@Mock
|
||||
private ActivityManagerInternal mActivityMangerInternal;
|
||||
@Mock
|
||||
private Context mContext;
|
||||
|
||||
private class TestJobSchedulerService extends JobSchedulerService {
|
||||
TestJobSchedulerService(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChainedAttributionEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mMockingSession = mockitoSession()
|
||||
.initMocks(this)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.mockStatic(LocalServices.class)
|
||||
.startMocking();
|
||||
|
||||
// Called in JobSchedulerService constructor.
|
||||
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
|
||||
doReturn(mActivityMangerInternal)
|
||||
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
|
||||
doReturn(mock(UsageStatsManagerInternal.class))
|
||||
.when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
|
||||
// Called in BackgroundJobsController constructor.
|
||||
doReturn(mock(AppStateTracker.class))
|
||||
.when(() -> LocalServices.getService(AppStateTracker.class));
|
||||
// Called in BatteryController constructor.
|
||||
doReturn(mock(BatteryManagerInternal.class))
|
||||
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
|
||||
// Called in ConnectivityController constructor.
|
||||
when(mContext.getSystemService(ConnectivityManager.class))
|
||||
.thenReturn(mock(ConnectivityManager.class));
|
||||
when(mContext.getSystemService(NetworkPolicyManager.class))
|
||||
.thenReturn(mock(NetworkPolicyManager.class));
|
||||
// Called in DeviceIdleJobsController constructor.
|
||||
doReturn(mock(DeviceIdleController.LocalService.class))
|
||||
.when(() -> LocalServices.getService(DeviceIdleController.LocalService.class));
|
||||
// Used in JobStatus.
|
||||
doReturn(mock(PackageManagerInternal.class))
|
||||
.when(() -> LocalServices.getService(PackageManagerInternal.class));
|
||||
// Called via IdleController constructor.
|
||||
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
|
||||
when(mContext.getResources()).thenReturn(mock(Resources.class));
|
||||
// Called in QuotaController constructor.
|
||||
IActivityManager activityManager = ActivityManager.getService();
|
||||
spyOn(activityManager);
|
||||
try {
|
||||
doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
|
||||
} catch (RemoteException e) {
|
||||
fail("registerUidObserver threw exception: " + e.getMessage());
|
||||
}
|
||||
|
||||
JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
|
||||
JobSchedulerService.sElapsedRealtimeClock =
|
||||
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
|
||||
|
||||
mService = new TestJobSchedulerService(mContext);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (mMockingSession != null) {
|
||||
mMockingSession.finishMocking();
|
||||
}
|
||||
}
|
||||
|
||||
private Clock getAdvancedClock(Clock clock, long incrementMs) {
|
||||
return Clock.offset(clock, Duration.ofMillis(incrementMs));
|
||||
}
|
||||
|
||||
private void advanceElapsedClock(long incrementMs) {
|
||||
JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
|
||||
JobSchedulerService.sElapsedRealtimeClock, incrementMs);
|
||||
}
|
||||
|
||||
private static JobInfo.Builder createJobInfo() {
|
||||
return new JobInfo.Builder(351, new ComponentName("foo", "bar"));
|
||||
}
|
||||
|
||||
private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder) {
|
||||
return JobStatus.createFromJobInfo(
|
||||
jobInfoBuilder.build(), 1234, "com.android.test", 0, testTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
|
||||
* with the correct delay and deadline constraints if the periodic job is completed and
|
||||
* rescheduled while run in its expected running window.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRescheduleJobForPeriodic_insideWindow() {
|
||||
final long now = sElapsedRealtimeClock.millis();
|
||||
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
|
||||
createJobInfo().setPeriodic(HOUR_IN_MILLIS));
|
||||
final long nextWindowStartTime = now + HOUR_IN_MILLIS;
|
||||
final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
|
||||
|
||||
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(45 * MINUTE_IN_MILLIS); // now + 55 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 59 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
|
||||
* with the correct delay and deadline constraints if the periodic job with a custom flex
|
||||
* setting is completed and rescheduled while run in its expected running window.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRescheduleJobForPeriodic_insideWindow_flex() {
|
||||
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_flex",
|
||||
createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
|
||||
// First window starts 30 minutes from now.
|
||||
advanceElapsedClock(30 * MINUTE_IN_MILLIS);
|
||||
final long now = sElapsedRealtimeClock.millis();
|
||||
final long nextWindowStartTime = now + HOUR_IN_MILLIS;
|
||||
final long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
|
||||
|
||||
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(15 * MINUTE_IN_MILLIS); // now + 25 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 29 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
|
||||
* with the correct delay and deadline constraints if the periodic job failed but then ran
|
||||
* successfully and was rescheduled while run in its expected running window.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRescheduleJobForPeriodic_insideWindow_failedJob() {
|
||||
final long now = sElapsedRealtimeClock.millis();
|
||||
final long nextWindowStartTime = now + HOUR_IN_MILLIS;
|
||||
final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
|
||||
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob",
|
||||
createJobInfo().setPeriodic(HOUR_IN_MILLIS));
|
||||
JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
|
||||
|
||||
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes
|
||||
failedJob = mService.getRescheduleJobForFailureLocked(job);
|
||||
advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes
|
||||
failedJob = mService.getRescheduleJobForFailureLocked(job);
|
||||
advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
|
||||
failedJob = mService.getRescheduleJobForFailureLocked(job);
|
||||
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
|
||||
* with the correct delay and deadline constraints if the periodic job is completed and
|
||||
* rescheduled when run after its expected running window.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRescheduleJobForPeriodic_outsideWindow() {
|
||||
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow",
|
||||
createJobInfo().setPeriodic(HOUR_IN_MILLIS));
|
||||
long now = sElapsedRealtimeClock.millis();
|
||||
long nextWindowStartTime = now + HOUR_IN_MILLIS;
|
||||
long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
|
||||
|
||||
advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
|
||||
// Say the job ran at the very end of its previous window. The intended JSS behavior is to
|
||||
// have consistent windows, so the new window should start as soon as the previous window
|
||||
// ended and end PERIOD time after the previous window ended.
|
||||
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(2 * HOUR_IN_MILLIS);
|
||||
// Say that the job ran at this point, possibly due to device idle.
|
||||
// The next window should be consistent (start and end at the time it would have had the job
|
||||
// run normally in previous windows).
|
||||
nextWindowStartTime += 2 * HOUR_IN_MILLIS;
|
||||
nextWindowEndTime += 2 * HOUR_IN_MILLIS;
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
|
||||
* with the correct delay and deadline constraints if the periodic job with a custom flex
|
||||
* setting is completed and rescheduled when run after its expected running window.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRescheduleJobForPeriodic_outsideWindow_flex() {
|
||||
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_flex",
|
||||
createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
|
||||
// First window starts 30 minutes from now.
|
||||
advanceElapsedClock(30 * MINUTE_IN_MILLIS);
|
||||
long now = sElapsedRealtimeClock.millis();
|
||||
long nextWindowStartTime = now + HOUR_IN_MILLIS;
|
||||
long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
|
||||
|
||||
advanceElapsedClock(31 * MINUTE_IN_MILLIS);
|
||||
// Say the job ran at the very end of its previous window. The intended JSS behavior is to
|
||||
// have consistent windows, so the new window should start as soon as the previous window
|
||||
// ended and end PERIOD time after the previous window ended.
|
||||
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
// 5 minutes before the start of the next window. It's too close to the next window, so the
|
||||
// returned job should be for the window after.
|
||||
advanceElapsedClock(24 * MINUTE_IN_MILLIS);
|
||||
nextWindowStartTime += HOUR_IN_MILLIS;
|
||||
nextWindowEndTime += HOUR_IN_MILLIS;
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(2 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS);
|
||||
// Say that the job ran at this point, possibly due to device idle.
|
||||
// The next window should be consistent (start and end at the time it would have had the job
|
||||
// run normally in previous windows).
|
||||
nextWindowStartTime += 2 * HOUR_IN_MILLIS;
|
||||
nextWindowEndTime += 2 * HOUR_IN_MILLIS;
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
|
||||
* with the correct delay and deadline constraints if the periodic job failed but then ran
|
||||
* successfully and was rescheduled when run after its expected running window.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRescheduleJobForPeriodic_outsideWindow_failedJob() {
|
||||
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob",
|
||||
createJobInfo().setPeriodic(HOUR_IN_MILLIS));
|
||||
JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
|
||||
long now = sElapsedRealtimeClock.millis();
|
||||
long nextWindowStartTime = now + HOUR_IN_MILLIS;
|
||||
long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
|
||||
|
||||
advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
|
||||
// Say the job ran at the very end of its previous window. The intended JSS behavior is to
|
||||
// have consistent windows, so the new window should start as soon as the previous window
|
||||
// ended and end PERIOD time after the previous window ended.
|
||||
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(2 * HOUR_IN_MILLIS);
|
||||
// Say that the job ran at this point, possibly due to device idle.
|
||||
// The next window should be consistent (start and end at the time it would have had the job
|
||||
// run normally in previous windows).
|
||||
nextWindowStartTime += 2 * HOUR_IN_MILLIS;
|
||||
nextWindowEndTime += 2 * HOUR_IN_MILLIS;
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
|
||||
* with the correct delay and deadline constraints if the periodic job with a custom flex
|
||||
* setting failed but then ran successfully and was rescheduled when run after its expected
|
||||
* running window.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob() {
|
||||
JobStatus job = createJobStatus(
|
||||
"testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob",
|
||||
createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
|
||||
JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
|
||||
// First window starts 30 minutes from now.
|
||||
advanceElapsedClock(30 * MINUTE_IN_MILLIS);
|
||||
long now = sElapsedRealtimeClock.millis();
|
||||
long nextWindowStartTime = now + HOUR_IN_MILLIS;
|
||||
long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
|
||||
|
||||
advanceElapsedClock(31 * MINUTE_IN_MILLIS);
|
||||
// Say the job ran at the very end of its previous window. The intended JSS behavior is to
|
||||
// have consistent windows, so the new window should start as soon as the previous window
|
||||
// ended and end PERIOD time after the previous window ended.
|
||||
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
// 5 minutes before the start of the next window. It's too close to the next window, so the
|
||||
// returned job should be for the window after.
|
||||
advanceElapsedClock(24 * MINUTE_IN_MILLIS);
|
||||
nextWindowStartTime += HOUR_IN_MILLIS;
|
||||
nextWindowEndTime += HOUR_IN_MILLIS;
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
|
||||
advanceElapsedClock(2 * HOUR_IN_MILLIS);
|
||||
// Say that the job ran at this point, possibly due to device idle.
|
||||
// The next window should be consistent (start and end at the time it would have had the job
|
||||
// run normally in previous windows).
|
||||
nextWindowStartTime += 2 * HOUR_IN_MILLIS;
|
||||
nextWindowEndTime += 2 * HOUR_IN_MILLIS;
|
||||
|
||||
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user