diff options
2 files changed, 78 insertions, 3 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index fed3c42ab87f..a900d162ab96 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -369,8 +369,23 @@ public final class FlexibilityController extends StateController { @VisibleForTesting @GuardedBy("mLock") long getLifeCycleBeginningElapsedLocked(JobStatus js) { + long earliestRuntime = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME + ? js.enqueueTime : js.getEarliestRunTime(); + if (js.getJob().isPeriodic() && js.getNumPreviousAttempts() == 0) { + // Rescheduling periodic jobs (after a successful execution) may result in the job's + // start time being a little after the "true" periodic start time (to avoid jobs + // running back to back). See JobSchedulerService#getRescheduleJobForPeriodic for more + // details. Since rescheduled periodic jobs may already be delayed slightly by this + // policy, don't penalize them further by then enforcing the full set of applied + // flex constraints at the beginning of the newly determined start time. Let the flex + // constraint requirement start closer to the true periodic start time. + final long truePeriodicStartTimeElapsed = + js.getLatestRunTimeElapsed() - js.getJob().getFlexMillis(); + // For now, treat the lifecycle beginning as the midpoint between the true periodic + // start time and the adjusted start time. + earliestRuntime = (earliestRuntime + truePeriodicStartTimeElapsed) / 2; + } if (js.getJob().isPrefetch()) { - final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime()); final long estimatedLaunchTime = mPrefetchController.getNextEstimatedLaunchTimeLocked(js); long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault( @@ -381,8 +396,7 @@ public final class FlexibilityController extends StateController { } return Math.max(prefetchWindowStart, earliestRuntime); } - return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME - ? js.enqueueTime : js.getEarliestRunTime(); + return earliestRuntime; } @VisibleForTesting diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 0659f7e9a064..debc69604690 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -23,6 +23,7 @@ import static android.app.job.JobInfo.NETWORK_TYPE_CELLULAR; import static android.app.job.JobInfo.NETWORK_TYPE_NONE; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 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.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -422,6 +423,66 @@ public class FlexibilityControllerTest { } @Test + public void testGetLifeCycleBeginningElapsedLocked_Periodic() { + // Periodic with lifecycle + JobInfo.Builder jbBasic = createJob(0).setPeriodic(HOUR_IN_MILLIS); + JobInfo.Builder jbFlex = createJob(0) + .setPeriodic(HOUR_IN_MILLIS, 20 * MINUTE_IN_MILLIS); + JobStatus jsBasic = + createJobStatus("testGetLifeCycleBeginningElapsedLocked_Periodic", jbBasic); + JobStatus jsFlex = + createJobStatus("testGetLifeCycleBeginningElapsedLocked_Periodic", jbFlex); + + final long nowElapsed = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Base case, no start adjustment + assertEquals(nowElapsed, + mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic)); + assertEquals(nowElapsed + 40 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex)); + + // Rescheduled with start adjustment + final long adjustmentMs = 4 * MINUTE_IN_MILLIS; + jsBasic = new JobStatus(jsBasic, + // "True" start is nowElapsed + HOUR_IN_MILLIS + nowElapsed + HOUR_IN_MILLIS + adjustmentMs, + nowElapsed + 2 * HOUR_IN_MILLIS, + 0 /* numFailures */, 0 /* numSystemStops */, + JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */, + 0, 0); + jsFlex = new JobStatus(jsFlex, + // "True" start is nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs, + nowElapsed + 2 * HOUR_IN_MILLIS, + 0 /* numFailures */, 0 /* numSystemStops */, + JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */, + 0, 0); + + assertEquals(nowElapsed + HOUR_IN_MILLIS + adjustmentMs / 2, + mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic)); + assertEquals(nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs / 2, + mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex)); + + // Rescheduled for failure + jsBasic = new JobStatus(jsBasic, + nowElapsed + 30 * MINUTE_IN_MILLIS, + NO_LATEST_RUNTIME, + 1 /* numFailures */, 1 /* numSystemStops */, + JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */, + 0, 0); + jsFlex = new JobStatus(jsFlex, + nowElapsed + 30 * MINUTE_IN_MILLIS, + NO_LATEST_RUNTIME, + 1 /* numFailures */, 1 /* numSystemStops */, + JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */, + 0, 0); + + assertEquals(nowElapsed + 30 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic)); + assertEquals(nowElapsed + 30 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex)); + } + + @Test public void testGetLifeCycleBeginningElapsedLocked_Prefetch() { // prefetch with lifecycle when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L); |