Flex time for periodic jobs in JobScheduler
A periodic job will start only within time
satisfying period - flex < time % period < period.
Bug: 26254850
Change-Id: I97a840446e5592e5151d784800855d909f1790b8
diff --git a/api/current.txt b/api/current.txt
index 42e72e3..693d9f4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6166,6 +6166,7 @@
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.PersistableBundle getExtras();
+ method public long getFlexMillis();
method public int getId();
method public long getInitialBackoffMillis();
method public long getIntervalMillis();
@@ -6183,6 +6184,8 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIOD_MILLIS = 3600000L; // 0x36ee80L
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6196,6 +6199,7 @@
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
+ method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
diff --git a/api/system-current.txt b/api/system-current.txt
index b34a7f4..d4a97b6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6381,6 +6381,7 @@
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.PersistableBundle getExtras();
+ method public long getFlexMillis();
method public int getId();
method public long getInitialBackoffMillis();
method public long getIntervalMillis();
@@ -6398,6 +6399,8 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIOD_MILLIS = 3600000L; // 0x36ee80L
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6411,6 +6414,7 @@
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
+ method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index bcab75b..2f24953 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6168,6 +6168,7 @@
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.PersistableBundle getExtras();
+ method public long getFlexMillis();
method public int getId();
method public long getInitialBackoffMillis();
method public long getIntervalMillis();
@@ -6185,6 +6186,8 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIOD_MILLIS = 3600000L; // 0x36ee80L
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6198,6 +6201,7 @@
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
+ method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 0d9e778..b899710 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -64,6 +64,11 @@
*/
public static final int BACKOFF_POLICY_EXPONENTIAL = 1;
+ /* Minimum interval for a periodic job, in milliseconds. */
+ public static final long MIN_PERIOD_MILLIS = 60 * 60 * 1000L; // 60 minutes
+ /* Minimum flex for a periodic job, in milliseconds. */
+ public static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
+
/**
* Default type of backoff.
* @hide
@@ -83,6 +88,7 @@
private final boolean isPeriodic;
private final boolean isPersisted;
private final long intervalMillis;
+ private final long flexMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
@@ -165,7 +171,17 @@
* job does not recur periodically.
*/
public long getIntervalMillis() {
- return intervalMillis;
+ return intervalMillis >= MIN_PERIOD_MILLIS ? intervalMillis : MIN_PERIOD_MILLIS;
+ }
+
+ /**
+ * Flex time for this job. Only valid if this is a periodic job.
+ */
+ public long getFlexMillis() {
+ long interval = getIntervalMillis();
+ long percentClamp = 5 * interval / 100;
+ long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, MIN_FLEX_MILLIS));
+ return clampedFlex <= interval ? clampedFlex : interval;
}
/**
@@ -216,6 +232,7 @@
isPeriodic = in.readInt() == 1;
isPersisted = in.readInt() == 1;
intervalMillis = in.readLong();
+ flexMillis = in.readLong();
initialBackoffMillis = in.readLong();
backoffPolicy = in.readInt();
hasEarlyConstraint = in.readInt() == 1;
@@ -234,6 +251,7 @@
isPeriodic = b.mIsPeriodic;
isPersisted = b.mIsPersisted;
intervalMillis = b.mIntervalMillis;
+ flexMillis = b.mFlexMillis;
initialBackoffMillis = b.mInitialBackoffMillis;
backoffPolicy = b.mBackoffPolicy;
hasEarlyConstraint = b.mHasEarlyConstraint;
@@ -258,6 +276,7 @@
out.writeInt(isPeriodic ? 1 : 0);
out.writeInt(isPersisted ? 1 : 0);
out.writeLong(intervalMillis);
+ out.writeLong(flexMillis);
out.writeLong(initialBackoffMillis);
out.writeInt(backoffPolicy);
out.writeInt(hasEarlyConstraint ? 1 : 0);
@@ -299,6 +318,7 @@
private boolean mHasEarlyConstraint;
private boolean mHasLateConstraint;
private long mIntervalMillis;
+ private long mFlexMillis;
// Back-off parameters.
private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS;
private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
@@ -373,8 +393,21 @@
* @param intervalMillis Millisecond interval for which this job will repeat.
*/
public Builder setPeriodic(long intervalMillis) {
+ return setPeriodic(intervalMillis, intervalMillis);
+ }
+
+ /**
+ * Specify that this job should recur with the provided interval and flex. The job can
+ * execute at any time in a window of flex length at the end of the period.
+ * @param intervalMillis Millisecond interval for which this job will repeat.
+ * @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
+ * {@link #MIN_FLEX_MILLIS} or 5 percent of the period, whichever is
+ * higher.
+ */
+ public Builder setPeriodic(long intervalMillis, long flexMillis) {
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
+ mFlexMillis = flexMillis;
mHasEarlyConstraint = mHasLateConstraint = true;
return this;
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 4eabe36..3530d80 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -613,9 +613,10 @@
if (periodicToReschedule.hasDeadlineConstraint()) {
runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
}
- long newEarliestRunTimeElapsed = elapsedNow + runEarly;
+ long flex = periodicToReschedule.getJob().getFlexMillis();
long period = periodicToReschedule.getJob().getIntervalMillis();
- long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
+ long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
+ long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
if (DEBUG) {
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 472e8f6..b8aa9dd 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -397,6 +397,7 @@
if (jobStatus.getJob().isPeriodic()) {
out.startTag(null, XML_TAG_PERIODIC);
out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
+ out.attribute(null, "flex", Long.toString(job.getFlexMillis()));
} else {
out.startTag(null, XML_TAG_ONEOFF);
}
@@ -594,13 +595,17 @@
String val = parser.getAttributeValue(null, "period");
final long periodMillis = Long.valueOf(val);
jobBuilder.setPeriodic(periodMillis);
- // As a sanity check, cap the recreated run time to be no later than 2 periods
+ val = parser.getAttributeValue(null, "flex");
+ final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
+ // As a sanity check, cap the recreated run time to be no later than flex+period
// from now. This is the latest the periodic could be pushed out. This could
- // happen if the periodic ran early (at the start of its period), and then the
+ // happen if the periodic ran early (at flex time before period), and then the
// device rebooted.
- if (elapsedRuntimes.second > elapsedNow + 2 * periodMillis) {
- final long clampedEarlyRuntimeElapsed = elapsedNow + periodMillis;
- final long clampedLateRuntimeElapsed = elapsedNow + 2 * periodMillis;
+ if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
+ final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
+ + periodMillis;
+ final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
+ - flexMillis;
Slog.w(TAG,
String.format("Periodic job for uid='%d' persisted run-time is" +
" too big [%s, %s]. Clamping to [%s,%s]",
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index c02611f..060a93e 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -96,8 +96,8 @@
final long elapsedNow = SystemClock.elapsedRealtime();
if (job.isPeriodic()) {
- earliestRunTimeElapsedMillis = elapsedNow;
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
+ earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis();
} else {
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;