diff options
| -rw-r--r-- | api/current.txt | 4 | ||||
| -rw-r--r-- | api/system-current.txt | 4 | ||||
| -rw-r--r-- | api/test-current.txt | 4 | ||||
| -rw-r--r-- | core/java/android/app/job/JobInfo.java | 8 | ||||
| -rw-r--r-- | core/java/android/app/job/JobParameters.java | 6 | ||||
| -rw-r--r-- | core/java/android/app/job/JobScheduler.java | 17 | ||||
| -rw-r--r-- | core/java/android/app/job/JobServiceEngine.java | 8 | ||||
| -rw-r--r-- | core/java/android/app/job/JobWorkItem.java | 41 | ||||
| -rw-r--r-- | services/core/java/com/android/server/job/JobSchedulerService.java | 44 | ||||
| -rw-r--r-- | services/core/java/com/android/server/job/controllers/JobStatus.java | 7 |
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 + " "); |