Loading services/core/java/com/android/server/job/JobSchedulerService.java +27 −4 Original line number Diff line number Diff line Loading @@ -1635,6 +1635,9 @@ public class JobSchedulerService extends com.android.server.SystemService */ private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS; /** The maximum period a periodic job can have. Anything higher will be clamped down to this. */ public static final long MAX_ALLOWED_PERIOD_MS = 365 * 24 * 60 * 60 * 1000L; /** * Called after a periodic has executed so we can reschedule it. We take the last execution * time of the job to be the time of completion (i.e. the time at which this function is Loading @@ -1652,11 +1655,21 @@ public class JobSchedulerService extends com.android.server.SystemService JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) { final long elapsedNow = sElapsedRealtimeClock.millis(); final long newLatestRuntimeElapsed; final long period = periodicToReschedule.getJob().getIntervalMillis(); final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed(); final long flex = periodicToReschedule.getJob().getFlexMillis(); // Make sure period is in the interval [min_possible_period, max_possible_period]. final long period = Math.max(JobInfo.getMinPeriodMillis(), Math.min(MAX_ALLOWED_PERIOD_MS, periodicToReschedule.getJob().getIntervalMillis())); // Make sure flex is in the interval [min_possible_flex, period]. final long flex = Math.max(JobInfo.getMinFlexMillis(), Math.min(period, periodicToReschedule.getJob().getFlexMillis())); long rescheduleBuffer = 0; long olrte = periodicToReschedule.getOriginalLatestRunTimeElapsed(); if (olrte < 0 || olrte == JobStatus.NO_LATEST_RUNTIME) { Slog.wtf(TAG, "Invalid periodic job original latest run time: " + olrte); olrte = elapsedNow; } final long latestRunTimeElapsed = olrte; final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed); if (elapsedNow > latestRunTimeElapsed) { // The job ran past its expected run window. Have it count towards the current window Loading @@ -1664,7 +1677,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.i(TAG, "Periodic job ran after its intended window."); } int numSkippedWindows = (int) (diffMs / period) + 1; // +1 to include original window long numSkippedWindows = (diffMs / period) + 1; // +1 to include original window if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER, (period - flex) / 2)) { if (DEBUG) { Loading @@ -1684,6 +1697,16 @@ public class JobSchedulerService extends com.android.server.SystemService } } if (newLatestRuntimeElapsed < elapsedNow) { Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: " + newLatestRuntimeElapsed); return new JobStatus(periodicToReschedule, getCurrentHeartbeat(), elapsedNow + period - flex, elapsedNow + period, 0 /* backoffAttempt */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); } final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - Math.min(flex, period - rescheduleBuffer); Loading services/core/java/com/android/server/job/controllers/JobStatus.java +10 −2 Original line number Diff line number Diff line Loading @@ -511,8 +511,13 @@ public final class JobStatus { final long elapsedNow = sElapsedRealtimeClock.millis(); final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis; if (job.isPeriodic()) { latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); // Make sure period is in the interval [min_possible_period, max_possible_period]. final long period = Math.max(JobInfo.getMinPeriodMillis(), Math.min(JobSchedulerService.MAX_ALLOWED_PERIOD_MS, job.getIntervalMillis())); latestRunTimeElapsedMillis = elapsedNow + period; earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis // Make sure flex is in the interval [min_possible_flex, period]. - Math.max(JobInfo.getMinFlexMillis(), Math.min(period, job.getFlexMillis())); } else { earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME; Loading Loading @@ -1631,6 +1636,9 @@ public final class JobStatus { formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis); pw.print(", latest="); formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis); pw.print(", original latest="); formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis); pw.println(); if (numFailures != 0) { pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures); Loading services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.job; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; Loading Loading @@ -160,6 +161,56 @@ public class JobSchedulerServiceTest { 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 scheduled with the * minimum possible period. */ @Test public void testGetRescheduleJobForPeriodic_minPeriod() { final long now = sElapsedRealtimeClock.millis(); JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow", createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS)); final long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS; final long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS; for (int i = 0; i < 25; i++) { JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job); assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime()); assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); advanceElapsedClock(30_000); // 30 seconds } for (int i = 0; i < 5; i++) { // Window buffering in last 1/6 of window. JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job); assertEquals(nextWindowStartTime + i * 30_000, rescheduledJob.getEarliestRunTime()); assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); advanceElapsedClock(30_000); // 30 seconds } } /** * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job * with the correct delay and deadline constraints if the periodic job is scheduled with a * period that's too large. */ @Test public void testGetRescheduleJobForPeriodic_largePeriod() { final long now = sElapsedRealtimeClock.millis(); JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow", createJobInfo().setPeriodic(2 * 365 * DAY_IN_MILLIS)); assertEquals(now, job.getEarliestRunTime()); // Periods are capped at 365 days (1 year). assertEquals(now + 365 * DAY_IN_MILLIS, job.getLatestRunTimeElapsed()); final long nextWindowStartTime = now + 365 * DAY_IN_MILLIS; final long nextWindowEndTime = nextWindowStartTime + 365 * DAY_IN_MILLIS; JobStatus 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 is completed and Loading Loading
services/core/java/com/android/server/job/JobSchedulerService.java +27 −4 Original line number Diff line number Diff line Loading @@ -1635,6 +1635,9 @@ public class JobSchedulerService extends com.android.server.SystemService */ private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS; /** The maximum period a periodic job can have. Anything higher will be clamped down to this. */ public static final long MAX_ALLOWED_PERIOD_MS = 365 * 24 * 60 * 60 * 1000L; /** * Called after a periodic has executed so we can reschedule it. We take the last execution * time of the job to be the time of completion (i.e. the time at which this function is Loading @@ -1652,11 +1655,21 @@ public class JobSchedulerService extends com.android.server.SystemService JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) { final long elapsedNow = sElapsedRealtimeClock.millis(); final long newLatestRuntimeElapsed; final long period = periodicToReschedule.getJob().getIntervalMillis(); final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed(); final long flex = periodicToReschedule.getJob().getFlexMillis(); // Make sure period is in the interval [min_possible_period, max_possible_period]. final long period = Math.max(JobInfo.getMinPeriodMillis(), Math.min(MAX_ALLOWED_PERIOD_MS, periodicToReschedule.getJob().getIntervalMillis())); // Make sure flex is in the interval [min_possible_flex, period]. final long flex = Math.max(JobInfo.getMinFlexMillis(), Math.min(period, periodicToReschedule.getJob().getFlexMillis())); long rescheduleBuffer = 0; long olrte = periodicToReschedule.getOriginalLatestRunTimeElapsed(); if (olrte < 0 || olrte == JobStatus.NO_LATEST_RUNTIME) { Slog.wtf(TAG, "Invalid periodic job original latest run time: " + olrte); olrte = elapsedNow; } final long latestRunTimeElapsed = olrte; final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed); if (elapsedNow > latestRunTimeElapsed) { // The job ran past its expected run window. Have it count towards the current window Loading @@ -1664,7 +1677,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.i(TAG, "Periodic job ran after its intended window."); } int numSkippedWindows = (int) (diffMs / period) + 1; // +1 to include original window long numSkippedWindows = (diffMs / period) + 1; // +1 to include original window if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER, (period - flex) / 2)) { if (DEBUG) { Loading @@ -1684,6 +1697,16 @@ public class JobSchedulerService extends com.android.server.SystemService } } if (newLatestRuntimeElapsed < elapsedNow) { Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: " + newLatestRuntimeElapsed); return new JobStatus(periodicToReschedule, getCurrentHeartbeat(), elapsedNow + period - flex, elapsedNow + period, 0 /* backoffAttempt */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); } final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - Math.min(flex, period - rescheduleBuffer); Loading
services/core/java/com/android/server/job/controllers/JobStatus.java +10 −2 Original line number Diff line number Diff line Loading @@ -511,8 +511,13 @@ public final class JobStatus { final long elapsedNow = sElapsedRealtimeClock.millis(); final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis; if (job.isPeriodic()) { latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); // Make sure period is in the interval [min_possible_period, max_possible_period]. final long period = Math.max(JobInfo.getMinPeriodMillis(), Math.min(JobSchedulerService.MAX_ALLOWED_PERIOD_MS, job.getIntervalMillis())); latestRunTimeElapsedMillis = elapsedNow + period; earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis // Make sure flex is in the interval [min_possible_flex, period]. - Math.max(JobInfo.getMinFlexMillis(), Math.min(period, job.getFlexMillis())); } else { earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME; Loading Loading @@ -1631,6 +1636,9 @@ public final class JobStatus { formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis); pw.print(", latest="); formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis); pw.print(", original latest="); formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis); pw.println(); if (numFailures != 0) { pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures); Loading
services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.job; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; Loading Loading @@ -160,6 +161,56 @@ public class JobSchedulerServiceTest { 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 scheduled with the * minimum possible period. */ @Test public void testGetRescheduleJobForPeriodic_minPeriod() { final long now = sElapsedRealtimeClock.millis(); JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow", createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS)); final long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS; final long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS; for (int i = 0; i < 25; i++) { JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job); assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime()); assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); advanceElapsedClock(30_000); // 30 seconds } for (int i = 0; i < 5; i++) { // Window buffering in last 1/6 of window. JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job); assertEquals(nextWindowStartTime + i * 30_000, rescheduledJob.getEarliestRunTime()); assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); advanceElapsedClock(30_000); // 30 seconds } } /** * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job * with the correct delay and deadline constraints if the periodic job is scheduled with a * period that's too large. */ @Test public void testGetRescheduleJobForPeriodic_largePeriod() { final long now = sElapsedRealtimeClock.millis(); JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow", createJobInfo().setPeriodic(2 * 365 * DAY_IN_MILLIS)); assertEquals(now, job.getEarliestRunTime()); // Periods are capped at 365 days (1 year). assertEquals(now + 365 * DAY_IN_MILLIS, job.getLatestRunTimeElapsed()); final long nextWindowStartTime = now + 365 * DAY_IN_MILLIS; final long nextWindowEndTime = nextWindowStartTime + 365 * DAY_IN_MILLIS; JobStatus 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 is completed and Loading