summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt4
-rw-r--r--api/system-current.txt4
-rw-r--r--api/test-current.txt4
-rw-r--r--core/java/android/app/job/JobInfo.java8
-rw-r--r--core/java/android/app/job/JobParameters.java6
-rw-r--r--core/java/android/app/job/JobScheduler.java17
-rw-r--r--core/java/android/app/job/JobServiceEngine.java8
-rw-r--r--core/java/android/app/job/JobWorkItem.java41
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java44
-rw-r--r--services/core/java/com/android/server/job/controllers/JobStatus.java7
10 files changed, 109 insertions, 34 deletions
diff --git a/api/current.txt b/api/current.txt
index 2de1baba1c7a..698c9b6da651 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6893,15 +6893,15 @@ package android.app.job {
public abstract class JobServiceEngine {
ctor public JobServiceEngine(android.app.Service);
method public final android.os.IBinder getBinder();
- method public final void jobFinished(android.app.job.JobParameters, boolean);
+ method public void jobFinished(android.app.job.JobParameters, boolean);
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
}
public final class JobWorkItem implements android.os.Parcelable {
ctor public JobWorkItem(android.content.Intent);
- ctor public JobWorkItem(android.os.Parcel);
method public int describeContents();
+ method public int getDeliveryCount();
method public android.content.Intent getIntent();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
diff --git a/api/system-current.txt b/api/system-current.txt
index 4e2bd920da5f..b89964d49f6a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7329,15 +7329,15 @@ package android.app.job {
public abstract class JobServiceEngine {
ctor public JobServiceEngine(android.app.Service);
method public final android.os.IBinder getBinder();
- method public final void jobFinished(android.app.job.JobParameters, boolean);
+ method public void jobFinished(android.app.job.JobParameters, boolean);
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
}
public final class JobWorkItem implements android.os.Parcelable {
ctor public JobWorkItem(android.content.Intent);
- ctor public JobWorkItem(android.os.Parcel);
method public int describeContents();
+ method public int getDeliveryCount();
method public android.content.Intent getIntent();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
diff --git a/api/test-current.txt b/api/test-current.txt
index eec30f112088..4cafde5e2a6d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6923,15 +6923,15 @@ package android.app.job {
public abstract class JobServiceEngine {
ctor public JobServiceEngine(android.app.Service);
method public final android.os.IBinder getBinder();
- method public final void jobFinished(android.app.job.JobParameters, boolean);
+ method public void jobFinished(android.app.job.JobParameters, boolean);
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
}
public final class JobWorkItem implements android.os.Parcelable {
ctor public JobWorkItem(android.content.Intent);
- ctor public JobWorkItem(android.os.Parcel);
method public int describeContents();
+ method public int getDeliveryCount();
method public android.content.Intent getIntent();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index fa07fbdbcc53..3538256761aa 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -837,8 +837,12 @@ public class JobInfo implements Parcelable {
}
/**
- * Set optional transient extras. This is incompatible with jobs that are also
- * persisted with {@link #setPersisted(boolean)}; mixing the two is not allowed.
+ * Set optional transient extras.
+ *
+ * <p>Because setting this property is not compatible with persisted
+ * jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
+ * {@link android.app.job.JobInfo.Builder#build()} is called.</p>
+ *
* @param extras Bundle containing extras you want the scheduler to hold on to for you.
*/
public Builder setTransientExtras(@NonNull Bundle extras) {
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index 0985f5f91373..98bdde8efeab 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -180,6 +180,12 @@ public class JobParameters implements Parcelable {
* doing so any pending as well as remaining uncompleted work will be re-queued
* for the next time the job runs.</p>
*
+ * <p>This example shows how to construct a JobService that will serially dequeue and
+ * process work that is available for it:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java
+ * service}
+ *
* @return Returns a new {@link JobWorkItem} if there is one pending, otherwise null.
* If null is returned, the system will also stop the job if all work has also been completed.
* (This means that for correct operation, you must always call dequeueWork() after you have
diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java
index 4d6e3a22c83c..23f9eea65abe 100644
--- a/core/java/android/app/job/JobScheduler.java
+++ b/core/java/android/app/job/JobScheduler.java
@@ -75,21 +75,22 @@ public abstract class JobScheduler {
public abstract int schedule(@NonNull JobInfo job);
/**
- * Similar to {@link #schedule}, but allows you to enqueue work for an existing job. If a job
- * with the same ID is already scheduled, it will be replaced with the new {@link JobInfo}, but
- * any previously enqueued work will remain and be dispatched the next time it runs. If a job
- * with the same ID is already running, the new work will be enqueued for it.
+ * Similar to {@link #schedule}, but allows you to enqueue work for a new <em>or existing</em>
+ * job. If a job with the same ID is already scheduled, it will be replaced with the
+ * new {@link JobInfo}, but any previously enqueued work will remain and be dispatched the
+ * next time it runs. If a job with the same ID is already running, the new work will be
+ * enqueued for it.
*
* <p>The work you enqueue is later retrieved through
- * {@link JobParameters#dequeueWork() JobParameters.dequeueWork()}. Be sure to see there
+ * {@link JobParameters#dequeueWork() JobParameters.dequeueWork}. Be sure to see there
* about how to process work; the act of enqueueing work changes how you should handle the
* overall lifecycle of an executing job.</p>
*
* <p>It is strongly encouraged that you use the same {@link JobInfo} for all work you
- * enqueue. This will allow the system to optimal schedule work along with any pending
+ * enqueue. This will allow the system to optimally schedule work along with any pending
* and/or currently running work. If the JobInfo changes from the last time the job was
* enqueued, the system will need to update the associated JobInfo, which can cause a disruption
- * in exection. In particular, this can result in any currently running job that is processing
+ * in execution. In particular, this can result in any currently running job that is processing
* previous work to be stopped and restarted with the new JobInfo.</p>
*
* <p>It is recommended that you avoid using
@@ -100,7 +101,7 @@ public abstract class JobScheduler {
* (That said, you should be relatively safe with a simple set of consistent data in these
* fields.) You should never use {@link JobInfo.Builder#setClipData(ClipData, int)} with
* work you are enqueue, since currently this will always be treated as a different JobInfo,
- * even if the ClipData contents is exactly the same.</p>
+ * even if the ClipData contents are exactly the same.</p>
*
* @param job The job you wish to enqueue work for. See
* {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
diff --git a/core/java/android/app/job/JobServiceEngine.java b/core/java/android/app/job/JobServiceEngine.java
index a62861902a55..b7d759b27c01 100644
--- a/core/java/android/app/job/JobServiceEngine.java
+++ b/core/java/android/app/job/JobServiceEngine.java
@@ -32,7 +32,11 @@ import java.lang.ref.WeakReference;
/**
* Helper for implementing a {@link android.app.Service} that interacts with
- * {@link JobScheduler}.
+ * {@link JobScheduler}. This is not intended for use by regular applications, but
+ * allows frameworks built on top of the platform to create their own
+ * {@link android.app.Service} that interact with {@link JobScheduler} as well as
+ * add in additional functionality. If you just want to execute jobs normally, you
+ * should instead be looking at {@link JobService}.
*/
public abstract class JobServiceEngine {
private static final String TAG = "JobServiceEngine";
@@ -215,7 +219,7 @@ public abstract class JobServiceEngine {
* {@link JobService#jobFinished(JobParameters, boolean)} JobService.jobFinished} for more
* information.
*/
- public final void jobFinished(JobParameters params, boolean needsReschedule) {
+ public void jobFinished(JobParameters params, boolean needsReschedule) {
Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);
m.arg2 = needsReschedule ? 1 : 0;
m.sendToTarget();
diff --git a/core/java/android/app/job/JobWorkItem.java b/core/java/android/app/job/JobWorkItem.java
index 05687ee9aace..0eb0450e8f2a 100644
--- a/core/java/android/app/job/JobWorkItem.java
+++ b/core/java/android/app/job/JobWorkItem.java
@@ -22,15 +22,19 @@ import android.os.Parcelable;
/**
* A unit of work that can be enqueued for a job using
- * {@link JobScheduler#enqueue JobScheduler.enqueue}.
+ * {@link JobScheduler#enqueue JobScheduler.enqueue}. See
+ * {@link JobParameters#dequeueWork() JobParameters.dequeueWork} for more details.
*/
final public class JobWorkItem implements Parcelable {
final Intent mIntent;
+ int mDeliveryCount;
int mWorkId;
Object mGrants;
/**
- * Create a new piece of work.
+ * Create a new piece of work, which can be submitted to
+ * {@link JobScheduler#enqueue JobScheduler.enqueue}.
+ *
* @param intent The general Intent describing this work.
*/
public JobWorkItem(Intent intent) {
@@ -45,6 +49,23 @@ final public class JobWorkItem implements Parcelable {
}
/**
+ * Return the count of the number of times this work item has been delivered
+ * to the job. The value will be > 1 if it has been redelivered because the job
+ * was stopped or crashed while it had previously been delivered but before the
+ * job had called {@link JobParameters#completeWork JobParameters.completeWork} for it.
+ */
+ public int getDeliveryCount() {
+ return mDeliveryCount;
+ }
+
+ /**
+ * @hide
+ */
+ public void bumpDeliveryCount() {
+ mDeliveryCount++;
+ }
+
+ /**
* @hide
*/
public void setWorkId(int id) {
@@ -73,7 +94,17 @@ final public class JobWorkItem implements Parcelable {
}
public String toString() {
- return "JobWorkItem{id=" + mWorkId + " intent=" + mIntent + "}";
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("JobWorkItem{id=");
+ sb.append(mWorkId);
+ sb.append(" intent=");
+ sb.append(mIntent);
+ if (mDeliveryCount != 0) {
+ sb.append(" dcount=");
+ sb.append(mDeliveryCount);
+ }
+ sb.append("}");
+ return sb.toString();
}
public int describeContents() {
@@ -87,6 +118,7 @@ final public class JobWorkItem implements Parcelable {
} else {
out.writeInt(0);
}
+ out.writeInt(mDeliveryCount);
out.writeInt(mWorkId);
}
@@ -101,12 +133,13 @@ final public class JobWorkItem implements Parcelable {
}
};
- public JobWorkItem(Parcel in) {
+ JobWorkItem(Parcel in) {
if (in.readInt() != 0) {
mIntent = Intent.CREATOR.createFromParcel(in);
} else {
mIntent = null;
}
+ mDeliveryCount = in.readInt();
mWorkId = in.readInt();
}
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 3db2f31f0427..abb2b555be57 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -1367,26 +1367,48 @@ public final class JobSchedulerService extends com.android.server.SystemService
* - The component is enabled and runnable.
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
- final boolean jobExists = mJobs.containsJob(job);
final boolean jobReady = job.isReady();
- final boolean jobPending = mPendingJobs.contains(job);
- final boolean jobActive = isCurrentlyActiveLocked(job);
- final boolean jobBackingUp = mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0;
+
+ if (DEBUG) {
+ Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ + " ready=" + jobReady);
+ }
+
+ // This is a condition that is very likely to be false (most jobs that are
+ // scheduled are sitting there, not ready yet) and very cheap to check (just
+ // a few conditions on data in JobStatus).
+ if (!jobReady) {
+ return false;
+ }
+
+ final boolean jobExists = mJobs.containsJob(job);
final int userId = job.getUserId();
final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
- + " exists=" + jobExists
- + " ready=" + jobReady + " pending=" + jobPending
- + " active=" + jobActive + " backingup=" + jobBackingUp
- + " userStarted=" + userStarted);
+ + " exists=" + jobExists + " userStarted=" + userStarted);
+ }
+
+ // These are also fairly cheap to check, though they typically will not
+ // be conditions we fail.
+ if (!jobExists || !userStarted) {
+ return false;
+ }
+
+ final boolean jobPending = mPendingJobs.contains(job);
+ final boolean jobActive = isCurrentlyActiveLocked(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ + " pending=" + jobPending + " active=" + jobActive);
}
- // Short circuit: don't do the expensive PM check unless we really think
- // we might need to run this job now.
- if (!jobExists || !userStarted || !jobReady || jobPending || jobActive || jobBackingUp) {
+ // These can be a little more expensive (especially jobActive, since we need to
+ // go through the array of all potentially active jobs), so we are doing them
+ // later... but still before checking with the package manager!
+ if (jobPending || jobActive) {
return false;
}
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 4d5aba3c1dab..7fdb08a56f79 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -339,6 +339,7 @@ public final class JobStatus {
executingWork = new ArrayList<>();
}
executingWork.add(work);
+ work.bumpDeliveryCount();
}
return work;
}
@@ -661,6 +662,9 @@ public final class JobStatus {
/**
* @return Whether or not this job is ready to run, based on its requirements. This is true if
* the constraints are satisfied <strong>or</strong> the deadline on the job has expired.
+ * TODO: This function is called a *lot*. We should probably just have it check an
+ * already-computed boolean, which we updated whenever we see one of the states it depends
+ * on here change.
*/
public boolean isReady() {
// Deadline constraint trumps other constraints (except for periodic jobs where deadline
@@ -856,7 +860,8 @@ public final class JobStatus {
private void dumpJobWorkItem(PrintWriter pw, String prefix, JobWorkItem work, int index) {
pw.print(prefix); pw.print(" #"); pw.print(index); pw.print(": #");
- pw.print(work.getWorkId()); pw.print(" "); pw.println(work.getIntent());
+ pw.print(work.getWorkId()); pw.print(" "); pw.print(work.getDeliveryCount());
+ pw.print("x "); pw.println(work.getIntent());
if (work.getGrants() != null) {
pw.print(prefix); pw.println(" URI grants:");
((GrantedUriPermissions)work.getGrants()).dump(pw, prefix + " ");