Add job persistance as a setter in the API
Bug: 15936795
Change-Id: I11e5a722bab5838dc151670256ed09dfaa7fdaa7
diff --git a/api/current.txt b/api/current.txt
index 79c4abd..12fb061 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5492,6 +5492,7 @@
method public int getNetworkCapabilities();
method public android.content.ComponentName getService();
method public boolean isPeriodic();
+ method public boolean isPersisted();
method public boolean isRequireCharging();
method public boolean isRequireDeviceIdle();
method public void writeToParcel(android.os.Parcel, int);
@@ -5508,6 +5509,7 @@
method public android.app.job.JobInfo build();
method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setIsPersisted(boolean);
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);
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index a22e4cd..70f6966 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -64,7 +64,6 @@
}
private final int jobId;
- // TODO: Change this to use PersistableBundle when that lands in master.
private final PersistableBundle extras;
private final ComponentName service;
private final boolean requireCharging;
@@ -75,6 +74,7 @@
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
+ private final boolean isPersisted;
private final long intervalMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
@@ -145,6 +145,13 @@
}
/**
+ * @return Whether or not this job should be persisted across device reboots.
+ */
+ public boolean isPersisted() {
+ return isPersisted;
+ }
+
+ /**
* Set to the interval between occurrences of this job. This value is <b>not</b> set if the
* job does not recur periodically.
*/
@@ -197,6 +204,7 @@
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
isPeriodic = in.readInt() == 1;
+ isPersisted = in.readInt() == 1;
intervalMillis = in.readLong();
initialBackoffMillis = in.readLong();
backoffPolicy = in.readInt();
@@ -214,6 +222,7 @@
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
isPeriodic = b.mIsPeriodic;
+ isPersisted = b.mIsPersisted;
intervalMillis = b.mIntervalMillis;
initialBackoffMillis = b.mInitialBackoffMillis;
backoffPolicy = b.mBackoffPolicy;
@@ -237,6 +246,7 @@
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
out.writeInt(isPeriodic ? 1 : 0);
+ out.writeInt(isPersisted ? 1 : 0);
out.writeLong(intervalMillis);
out.writeLong(initialBackoffMillis);
out.writeInt(backoffPolicy);
@@ -265,6 +275,7 @@
private boolean mRequiresCharging;
private boolean mRequiresDeviceIdle;
private int mNetworkCapabilities;
+ private boolean mIsPersisted;
// One-off parameters.
private long mMinLatencyMillis;
private long mMaxExecutionDelayMillis;
@@ -342,11 +353,6 @@
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
* only the guarantee that it will be executed at most once within this interval.
- * A periodic job will be repeated until the phone is turned off, however it will only be
- * persisted beyond boot if the client app has declared the
- * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule
- * periodic jobs without this permission, they simply will cease to exist after the phone
- * restarts.
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this job will repeat.
@@ -407,6 +413,19 @@
}
/**
+ * Set whether or not to persist this job across device reboots. This will only have an
+ * effect if your application holds the permission
+ * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will
+ * be thrown.
+ * @param isPersisted True to indicate that the job will be written to disk and loaded at
+ * boot.
+ */
+ public Builder setIsPersisted(boolean isPersisted) {
+ mIsPersisted = isPersisted;
+ return this;
+ }
+
+ /**
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index cab2728..7f8b232 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -140,12 +140,10 @@
* This cancels the job if it's already been scheduled, and replaces it with the one provided.
* @param job JobInfo object containing execution parameters
* @param uId The package identifier of the application this job is for.
- * @param canPersistJob Whether or not the client has the appropriate permissions for
- * persisting this job.
* @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
*/
- public int schedule(JobInfo job, int uId, boolean canPersistJob) {
- JobStatus jobStatus = new JobStatus(job, uId, canPersistJob);
+ public int schedule(JobInfo job, int uId) {
+ JobStatus jobStatus = new JobStatus(job, uId);
cancelJob(uId, job.getId());
startTrackingJob(jobStatus);
return JobScheduler.RESULT_SUCCESS;
@@ -668,11 +666,16 @@
final int uid = Binder.getCallingUid();
enforceValidJobRequest(uid, job);
- final boolean canPersist = canPersistJobs(pid, uid);
+ if (job.isPersisted()) {
+ if (!canPersistJobs(pid, uid)) {
+ throw new IllegalArgumentException("Error: requested job be persisted without"
+ + " holding RECEIVE_BOOT_COMPLETED permission.");
+ }
+ }
long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.schedule(job, uid, canPersist);
+ return JobSchedulerService.this.schedule(job, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 4ac26c1..8736980 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -160,7 +160,9 @@
}
return false;
}
- maybeWriteStatusToDiskAsync();
+ if (jobStatus.isPersisted()) {
+ maybeWriteStatusToDiskAsync();
+ }
return removed;
}
@@ -429,7 +431,8 @@
}
}
- private List<JobStatus> readJobMapImpl(FileInputStream fis) throws XmlPullParserException, IOException {
+ private List<JobStatus> readJobMapImpl(FileInputStream fis)
+ throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
@@ -498,6 +501,7 @@
// Read out job identifier attributes.
try {
jobBuilder = buildBuilderFromXml(parser);
+ jobBuilder.setIsPersisted(true);
uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
} catch (NumberFormatException e) {
Slog.e(TAG, "Error parsing job's required fields, skipping");
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 53337c4..9ee2869 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -43,9 +43,6 @@
final JobInfo job;
final int uId;
- /** At reschedule time we need to know whether to update job on disk. */
- final boolean persisted;
-
// Constraints.
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
@@ -72,16 +69,15 @@
return uId;
}
- private JobStatus(JobInfo job, int uId, boolean persisted, int numFailures) {
+ private JobStatus(JobInfo job, int uId, int numFailures) {
this.job = job;
this.uId = uId;
this.numFailures = numFailures;
- this.persisted = persisted;
}
/** Create a newly scheduled job. */
- public JobStatus(JobInfo job, int uId, boolean persisted) {
- this(job, uId, persisted, 0);
+ public JobStatus(JobInfo job, int uId) {
+ this(job, uId, 0);
final long elapsedNow = SystemClock.elapsedRealtime();
@@ -105,7 +101,7 @@
*/
public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
long latestRunTimeElapsedMillis) {
- this(job, uId, true, 0);
+ this(job, uId, 0);
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
@@ -114,7 +110,7 @@
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
long newLatestRuntimeElapsedMillis, int backoffAttempt) {
- this(rescheduling.job, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt);
+ this(rescheduling.job, rescheduling.getUid(), backoffAttempt);
earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
@@ -172,6 +168,10 @@
return job.isRequireDeviceIdle();
}
+ public boolean isPersisted() {
+ return job.isPersisted();
+ }
+
public long getEarliestRunTime() {
return earliestRunTimeElapsedMillis;
}
@@ -180,9 +180,6 @@
return latestRunTimeElapsedMillis;
}
- public boolean isPersisted() {
- return persisted;
- }
/**
* @return Whether or not this job is ready to run, based on its requirements.
*/
diff --git a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
index 7a7fa07..cb8da70 100644
--- a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
@@ -63,8 +63,9 @@
.setBackoffCriteria(initialBackoff, JobInfo.BackoffPolicy.EXPONENTIAL)
.setOverrideDeadline(runByMillis)
.setMinimumLatency(runFromMillis)
+ .setIsPersisted(true)
.build();
- final JobStatus ts = new JobStatus(task, SOME_UID, true /* persisted */);
+ final JobStatus ts = new JobStatus(task, SOME_UID);
mTaskStoreUnderTest.add(ts);
Thread.sleep(IO_WAIT);
// Manually load tasks from xml file.
@@ -89,15 +90,17 @@
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
.setRequiresCharging(true)
+ .setIsPersisted(true)
.build();
final JobInfo task2 = new Builder(12, mComponent)
.setMinimumLatency(5000L)
.setBackoffCriteria(15000L, JobInfo.BackoffPolicy.LINEAR)
.setOverrideDeadline(30000L)
.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED)
+ .setIsPersisted(true)
.build();
- final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID, true /* persisted */);
- final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID, true /* persisted */);
+ final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID);
+ final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID);
mTaskStoreUnderTest.add(taskStatus1);
mTaskStoreUnderTest.add(taskStatus2);
Thread.sleep(IO_WAIT);
@@ -128,7 +131,8 @@
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
- .setRequiresCharging(true);
+ .setRequiresCharging(true)
+ .setIsPersisted(true);
PersistableBundle extras = new PersistableBundle();
extras.putDouble("hello", 3.2);
@@ -136,7 +140,7 @@
extras.putInt("into", 3);
b.setExtras(extras);
final JobInfo task = b.build();
- JobStatus taskStatus = new JobStatus(task, SOME_UID, true /* persisted */);
+ JobStatus taskStatus = new JobStatus(task, SOME_UID);
mTaskStoreUnderTest.add(taskStatus);
Thread.sleep(IO_WAIT);