From 28d1b661347a6a7e05dc1004fd7e8436cace8953 Mon Sep 17 00:00:00 2001
From: Dianne Hackborn 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.
This example shows how to construct a JobService that will serially dequeue and + * process work that is available for it:
+ * + * {@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 4d6e3a22c83c8..23f9eea65abe9 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 or 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. * *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.
* *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.
* *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.
+ * even if the ClipData contents are exactly the same. * * @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 a62861902a557..b7d759b27c017 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 05687ee9aace7..0eb0450e8f2a2 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) { @@ -44,6 +48,23 @@ final public class JobWorkItem implements Parcelable { return mIntent; } + /** + * 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 */ @@ -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 3db2f31f0427b..abb2b555be578 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); } - // 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 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); + } + + // 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 4d5aba3c1daba..7fdb08a56f79e 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 or 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 + " ");