diff options
21 files changed, 720 insertions, 127 deletions
diff --git a/api/current.txt b/api/current.txt index 7c532d4b8ffa..76fa651ee99c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6826,6 +6826,8 @@ package android.app.job { } public class JobParameters implements android.os.Parcelable { + method public void completeWork(android.app.job.JobWorkItem); + method public android.app.job.JobWorkItem dequeueWork(); method public int describeContents(); method public android.content.ClipData getClipData(); method public int getClipGrantFlags(); @@ -6843,6 +6845,7 @@ package android.app.job { ctor public JobScheduler(); method public abstract void cancel(int); method public abstract void cancelAll(); + method public abstract int enqueue(android.app.job.JobInfo, android.app.job.JobWorkItem); method public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs(); method public abstract android.app.job.JobInfo getPendingJob(int); method public abstract int schedule(android.app.job.JobInfo); @@ -6859,6 +6862,15 @@ package android.app.job { field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_JOB_SERVICE"; } + 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 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; + } + } package android.app.usage { diff --git a/api/system-current.txt b/api/system-current.txt index 99a579321c79..968d7151fbf6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -7255,6 +7255,8 @@ package android.app.job { } public class JobParameters implements android.os.Parcelable { + method public void completeWork(android.app.job.JobWorkItem); + method public android.app.job.JobWorkItem dequeueWork(); method public int describeContents(); method public android.content.ClipData getClipData(); method public int getClipGrantFlags(); @@ -7272,6 +7274,7 @@ package android.app.job { ctor public JobScheduler(); method public abstract void cancel(int); method public abstract void cancelAll(); + method public abstract int enqueue(android.app.job.JobInfo, android.app.job.JobWorkItem); method public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs(); method public abstract android.app.job.JobInfo getPendingJob(int); method public abstract int schedule(android.app.job.JobInfo); @@ -7289,6 +7292,15 @@ package android.app.job { field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_JOB_SERVICE"; } + 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 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; + } + } package android.app.usage { diff --git a/api/test-current.txt b/api/test-current.txt index 570855fb0043..2603dfd39464 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6855,6 +6855,8 @@ package android.app.job { } public class JobParameters implements android.os.Parcelable { + method public void completeWork(android.app.job.JobWorkItem); + method public android.app.job.JobWorkItem dequeueWork(); method public int describeContents(); method public android.content.ClipData getClipData(); method public int getClipGrantFlags(); @@ -6872,6 +6874,7 @@ package android.app.job { ctor public JobScheduler(); method public abstract void cancel(int); method public abstract void cancelAll(); + method public abstract int enqueue(android.app.job.JobInfo, android.app.job.JobWorkItem); method public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs(); method public abstract android.app.job.JobInfo getPendingJob(int); method public abstract int schedule(android.app.job.JobInfo); @@ -6888,6 +6891,15 @@ package android.app.job { field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_JOB_SERVICE"; } + 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 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; + } + } package android.app.usage { diff --git a/core/java/android/app/JobSchedulerImpl.java b/core/java/android/app/JobSchedulerImpl.java index e30b96fd692a..4ac44f79a4ec 100644 --- a/core/java/android/app/JobSchedulerImpl.java +++ b/core/java/android/app/JobSchedulerImpl.java @@ -20,6 +20,8 @@ package android.app; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.app.job.IJobScheduler; +import android.app.job.JobWorkItem; +import android.content.Intent; import android.os.RemoteException; import java.util.List; @@ -46,6 +48,15 @@ public class JobSchedulerImpl extends JobScheduler { } @Override + public int enqueue(JobInfo job, JobWorkItem work) { + try { + return mBinder.enqueue(job, work); + } catch (RemoteException e) { + return JobScheduler.RESULT_FAILURE; + } + } + + @Override public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag) { try { return mBinder.scheduleAsPackage(job, packageName, userId, tag); diff --git a/core/java/android/app/job/IJobCallback.aidl b/core/java/android/app/job/IJobCallback.aidl index 2d3948f6e85d..e7695e2e747f 100644 --- a/core/java/android/app/job/IJobCallback.aidl +++ b/core/java/android/app/job/IJobCallback.aidl @@ -16,6 +16,8 @@ package android.app.job; +import android.app.job.JobWorkItem; + /** * The server side of the JobScheduler IPC protocols. The app-side implementation * invokes on this interface to indicate completion of the (asynchronous) instructions @@ -43,6 +45,14 @@ interface IJobCallback { */ void acknowledgeStopMessage(int jobId, boolean reschedule); /* + * Called to deqeue next work item for the job. + */ + JobWorkItem dequeueWork(int jobId); + /* + * Called to report that job has completed processing a work item. + */ + boolean completeWork(int jobId, int workId); + /* * Tell the job manager that the client is done with its execution, so that it can go on to * the next one and stop attributing wakelock time to us etc. * diff --git a/core/java/android/app/job/IJobScheduler.aidl b/core/java/android/app/job/IJobScheduler.aidl index b6eec27c5412..e94da0c69f9c 100644 --- a/core/java/android/app/job/IJobScheduler.aidl +++ b/core/java/android/app/job/IJobScheduler.aidl @@ -17,6 +17,7 @@ package android.app.job; import android.app.job.JobInfo; +import android.app.job.JobWorkItem; /** * IPC interface that supports the app-facing {@link #JobScheduler} api. @@ -24,6 +25,7 @@ import android.app.job.JobInfo; */ interface IJobScheduler { int schedule(in JobInfo job); + int enqueue(in JobInfo job, in JobWorkItem work); int scheduleAsPackage(in JobInfo job, String packageName, int userId, String tag); void cancel(int jobId); void cancelAll(); diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 8220ff94361f..412e4459ecdd 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.ClipData; import android.content.ComponentName; import android.net.Uri; +import android.os.BaseBundle; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -442,6 +443,130 @@ public class JobInfo implements Parcelable { return hasLateConstraint; } + private static boolean kindofEqualsBundle(BaseBundle a, BaseBundle b) { + return (a == b) || (a != null && a.kindofEquals(b)); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof JobInfo)) { + return false; + } + JobInfo j = (JobInfo) o; + if (jobId != j.jobId) { + return false; + } + // XXX won't be correct if one is parcelled and the other not. + if (!kindofEqualsBundle(extras, j.extras)) { + return false; + } + // XXX won't be correct if one is parcelled and the other not. + if (!kindofEqualsBundle(transientExtras, j.transientExtras)) { + return false; + } + // XXX for now we consider two different clip data objects to be different, + // regardless of whether their contents are the same. + if (clipData != j.clipData) { + return false; + } + if (clipGrantFlags != j.clipGrantFlags) { + return false; + } + if (!Objects.equals(service, j.service)) { + return false; + } + if (constraintFlags != j.constraintFlags) { + return false; + } + if (!Objects.deepEquals(triggerContentUris, j.triggerContentUris)) { + return false; + } + if (triggerContentUpdateDelay != j.triggerContentUpdateDelay) { + return false; + } + if (triggerContentMaxDelay != j.triggerContentMaxDelay) { + return false; + } + if (hasEarlyConstraint != j.hasEarlyConstraint) { + return false; + } + if (hasLateConstraint != j.hasLateConstraint) { + return false; + } + if (networkType != j.networkType) { + return false; + } + if (minLatencyMillis != j.minLatencyMillis) { + return false; + } + if (maxExecutionDelayMillis != j.maxExecutionDelayMillis) { + return false; + } + if (isPeriodic != j.isPeriodic) { + return false; + } + if (isPersisted != j.isPersisted) { + return false; + } + if (intervalMillis != j.intervalMillis) { + return false; + } + if (flexMillis != j.flexMillis) { + return false; + } + if (initialBackoffMillis != j.initialBackoffMillis) { + return false; + } + if (backoffPolicy != j.backoffPolicy) { + return false; + } + if (priority != j.priority) { + return false; + } + if (flags != j.flags) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hashCode = jobId; + if (extras != null) { + hashCode = 31*hashCode + extras.hashCode(); + } + if (transientExtras != null) { + hashCode = 31*hashCode + transientExtras.hashCode(); + } + if (clipData != null) { + hashCode = 31*hashCode + clipData.hashCode(); + } + hashCode = 31*hashCode + clipGrantFlags; + if (service != null) { + hashCode = 31*hashCode + service.hashCode(); + } + hashCode = 31*hashCode + constraintFlags; + if (triggerContentUris != null) { + hashCode = 31*hashCode + triggerContentUris.hashCode(); + } + hashCode = 31*hashCode + Long.hashCode(triggerContentUpdateDelay); + hashCode = 31*hashCode + Long.hashCode(triggerContentMaxDelay); + hashCode = 31*hashCode + Boolean.hashCode(hasEarlyConstraint); + hashCode = 31*hashCode + Boolean.hashCode(hasLateConstraint); + hashCode = 31*hashCode + networkType; + hashCode = 31*hashCode + Long.hashCode(minLatencyMillis); + hashCode = 31*hashCode + Long.hashCode(maxExecutionDelayMillis); + hashCode = 31*hashCode + Boolean.hashCode(isPeriodic); + hashCode = 31*hashCode + Boolean.hashCode(isPersisted); + hashCode = 31*hashCode + Long.hashCode(intervalMillis); + hashCode = 31*hashCode + Long.hashCode(flexMillis); + hashCode = 31*hashCode + Long.hashCode(initialBackoffMillis); + hashCode = 31*hashCode + backoffPolicy; + hashCode = 31*hashCode + priority; + hashCode = 31*hashCode + flags; + return hashCode; + } + private JobInfo(Parcel in) { jobId = in.readInt(); extras = in.readPersistableBundle(); diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java index 8d52d3b96941..016a0fa8b92b 100644 --- a/core/java/android/app/job/JobParameters.java +++ b/core/java/android/app/job/JobParameters.java @@ -24,6 +24,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import android.os.RemoteException; /** * Contains the parameters used to configure/identify your job. You do not create this object @@ -155,6 +156,53 @@ public class JobParameters implements Parcelable { return mTriggeredContentAuthorities; } + /** + * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their + * currently running job. Calling this method when there is no more work available and all + * previously dequeued work has been completed will result in the system taking care of + * stopping the job for you -- + * you should not call {@link JobService#jobFinished(JobParameters, boolean)} yourself + * (otherwise you risk losing an upcoming JobWorkItem that is being enqueued at the same time). + * + * @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 + * completed other work, to check either for more work or allow the system to stop the job.) + */ + public JobWorkItem dequeueWork() { + try { + return getCallback().dequeueWork(getJobId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report the completion of executing a {@link JobWorkItem} previously returned by + * {@link #dequeueWork()}. This tells the system you are done with the + * work associated with that item, so it will not be returned again. Note that if this + * is the last work in the queue, completing it here will <em>not</em> finish the overall + * job -- for that to happen, you still need to call {@link #dequeueWork()} + * again. + * + * <p>If you are enqueueing work into a job, you must call this method for each piece + * of work you process. Do <em>not</em> call + * {@link JobService#jobFinished(JobParameters, boolean)} + * or else you can lose work in your queue.</p> + * + * @param work The work you have completed processing, as previously returned by + * {@link #dequeueWork()} + */ + public void completeWork(JobWorkItem work) { + try { + if (!getCallback().completeWork(getJobId(), work.getWorkId())) { + throw new IllegalArgumentException("Given work is not active: " + work); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ public IJobCallback getCallback() { return IJobCallback.Stub.asInterface(callback); diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java index 1b640d08dde2..e0afe039478e 100644 --- a/core/java/android/app/job/JobScheduler.java +++ b/core/java/android/app/job/JobScheduler.java @@ -19,6 +19,10 @@ package android.app.job; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.ClipData; +import android.content.Intent; +import android.os.Bundle; +import android.os.PersistableBundle; import java.util.List; @@ -59,6 +63,10 @@ public abstract class JobScheduler { public static final int RESULT_SUCCESS = 1; /** + * Schedule a job to be executed. Will replace any currently scheduled job with the same + * ID with the new information in the {@link JobInfo}. If a job with the given ID is currently + * running, it will be stopped. + * * @param job The job you wish scheduled. See * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs * you can schedule. @@ -67,6 +75,42 @@ public abstract class JobScheduler { public abstract int schedule(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. + * + * <p>The work you enqueue is later retrieved through + * {@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 + * 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 + * previous work to be stopped and restarted with the new JobInfo.</p> + * + * <p>It is recommended that you avoid using + * {@link JobInfo.Builder#setExtras(PersistableBundle)} or + * {@link JobInfo.Builder#setTransientExtras(Bundle)} with a JobInfo you are using to + * enqueue work. The system will try to compare these extras with the previous JobInfo, + * but there are situations where it may get this wrong and count the JobInfo as changing. + * (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> + * + * @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 + * you can schedule. + * @param work New work to enqueue. This will be available later when the job starts running. + * @return An int representing ({@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}). + */ + public abstract int enqueue(JobInfo job, JobWorkItem work); + + /** * * @param job The job to be scheduled. * @param packageName The package on behalf of which the job is to be scheduled. This will be diff --git a/core/java/android/app/job/JobWorkItem.aidl b/core/java/android/app/job/JobWorkItem.aidl new file mode 100644 index 000000000000..e8fe47d07865 --- /dev/null +++ b/core/java/android/app/job/JobWorkItem.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.job; + +/** @hide */ +parcelable JobWorkItem; diff --git a/core/java/android/app/job/JobWorkItem.java b/core/java/android/app/job/JobWorkItem.java new file mode 100644 index 000000000000..4bb057e689b2 --- /dev/null +++ b/core/java/android/app/job/JobWorkItem.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.job; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A unit of work that can be enqueued for a job using + * {@link JobScheduler#enqueue JobScheduler.enqueue}. + */ +final public class JobWorkItem implements Parcelable { + final Intent mIntent; + int mWorkId; + + /** + * Create a new piece of work. + * @param intent The general Intent describing this work. + */ + public JobWorkItem(Intent intent) { + mIntent = intent; + } + + /** + * Return the Intent associated with this work. + */ + public Intent getIntent() { + return mIntent; + } + + /** + * @hide + */ + public void setWorkId(int id) { + mWorkId = id; + } + + /** + * @hide + */ + public int getWorkId() { + return mWorkId; + } + + public String toString() { + return "JobWorkItem{id=" + mWorkId + " intent=" + mIntent + "}"; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + if (mIntent != null) { + out.writeInt(1); + mIntent.writeToParcel(out, 0); + } else { + out.writeInt(0); + } + out.writeInt(mWorkId); + } + + public static final Parcelable.Creator<JobWorkItem> CREATOR + = new Parcelable.Creator<JobWorkItem>() { + public JobWorkItem createFromParcel(Parcel in) { + return new JobWorkItem(in); + } + + public JobWorkItem[] newArray(int size) { + return new JobWorkItem[size]; + } + }; + + public JobWorkItem(Parcel in) { + if (in.readInt() != 0) { + mIntent = Intent.CREATOR.createFromParcel(in); + } else { + mIntent = null; + } + mWorkId = in.readInt(); + } +} diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 6f388e27fe6e..65025fb9d337 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -325,6 +325,23 @@ public class BaseBundle { } /** + * @hide This kind-of does an equality comparison. Kind-of. + */ + public boolean kindofEquals(BaseBundle other) { + if (other == null) { + return false; + } + if (isParcelled() != other.isParcelled()) { + // Big kind-of here! + return false; + } else if (isParcelled()) { + return mParcelledData.compareData(other.mParcelledData) == 0; + } else { + return mMap.equals(other.mMap); + } + } + + /** * Removes all elements from the mapping of this Bundle. */ public void clear() { diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 76128e640e33..c3836a38c822 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -315,6 +315,7 @@ public final class Parcel { private static native byte[] nativeMarshall(long nativePtr); private static native long nativeUnmarshall( long nativePtr, byte[] data, int offset, int length); + private static native int nativeCompareData(long thisNativePtr, long otherNativePtr); private static native long nativeAppendFrom( long thisNativePtr, long otherNativePtr, int offset, int length); @FastNative @@ -487,6 +488,11 @@ public final class Parcel { updateNativeSize(nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length)); } + /** @hide */ + public final int compareData(Parcel other) { + return nativeCompareData(mNativePtr, other.mNativePtr); + } + /** * Report whether the parcel contains any marshalled file descriptors. */ diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index 8f7908a0e048..d740a76f8c2e 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -617,6 +617,21 @@ static jlong android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativ return parcel->getOpenAshmemSize(); } +static jint android_os_Parcel_compareData(JNIEnv* env, jclass clazz, jlong thisNativePtr, + jlong otherNativePtr) +{ + Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr); + if (thisParcel == NULL) { + return 0; + } + Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr); + if (otherParcel == NULL) { + return thisParcel->getOpenAshmemSize(); + } + + return thisParcel->compareData(*otherParcel); +} + static jlong android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr, jlong otherNativePtr, jint offset, jint length) { @@ -781,6 +796,7 @@ static const JNINativeMethod gParcelMethods[] = { {"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall}, {"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall}, + {"nativeCompareData", "(JJ)I", (void*)android_os_Parcel_compareData}, {"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom}, // @FastNative {"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors}, diff --git a/services/core/java/com/android/server/job/JobCompletedListener.java b/services/core/java/com/android/server/job/JobCompletedListener.java index 655abd73e6ad..34ba753b3daa 100644 --- a/services/core/java/com/android/server/job/JobCompletedListener.java +++ b/services/core/java/com/android/server/job/JobCompletedListener.java @@ -27,5 +27,5 @@ public interface JobCompletedListener { * Callback for when a job is completed. * @param needsReschedule Whether the implementing class should reschedule this job. */ - void onJobCompleted(JobStatus jobStatus, boolean needsReschedule); + void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule); } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index cd3ba4c8b1f3..a0d0d777637f 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -37,6 +37,7 @@ import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.app.job.IJobScheduler; +import android.app.job.JobWorkItem; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -582,20 +583,8 @@ public final class JobSchedulerService extends com.android.server.SystemService mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle); } - /** - * Entry point from client to schedule the provided job. - * 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. - * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes. - */ - public int schedule(JobInfo job, int uId) { - return scheduleAsPackage(job, uId, null, -1, null); - } - - public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId, - String tag) { - JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag); + public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, + int userId, String tag) { try { if (ActivityManager.getService().isAppStartModeDisabled(uId, job.getService().getPackageName())) { @@ -605,9 +594,20 @@ public final class JobSchedulerService extends com.android.server.SystemService } } catch (RemoteException e) { } - if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); - JobStatus toCancel; synchronized (mLock) { + final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); + + if (work != null && toCancel != null) { + // Fast path: we are adding work to an existing job, and the JobInfo is not + // changing. We can just directly enqueue this work in to the job. + if (toCancel.getJob().equals(job)) { + toCancel.enqueueWorkLocked(work); + return JobScheduler.RESULT_SUCCESS; + } + } + + JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag); + if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); // Jobs on behalf of others don't apply to the per-app job cap if (ENFORCE_MAX_JOBS && packageName == null) { if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) { @@ -618,15 +618,18 @@ public final class JobSchedulerService extends com.android.server.SystemService } // This may throw a SecurityException. - jobStatus.prepare(ActivityManager.getService()); + jobStatus.prepareLocked(ActivityManager.getService()); - toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); if (toCancel != null) { cancelJobImpl(toCancel, jobStatus); } - startTrackingJob(jobStatus, toCancel); + if (work != null) { + // If work has been supplied, enqueue it into the new job. + jobStatus.enqueueWorkLocked(work); + } + startTrackingJobLocked(jobStatus, toCancel); + mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); } - mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); return JobScheduler.RESULT_SUCCESS; } @@ -715,17 +718,17 @@ public final class JobSchedulerService extends com.android.server.SystemService } private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) { - if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); - cancelled.unprepare(ActivityManager.getService()); - stopTrackingJob(cancelled, incomingJob, true /* writeBack */); synchronized (mLock) { + if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); + cancelled.unprepareLocked(ActivityManager.getService()); + stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */); // Remove from pending queue. if (mPendingJobs.remove(cancelled)) { mJobPackageTracker.noteNonpending(cancelled); } // Cancel if running. stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED); - reportActive(); + reportActiveLocked(); } } @@ -773,7 +776,7 @@ public final class JobSchedulerService extends com.android.server.SystemService } } - void reportActive() { + void reportActiveLocked() { // active is true if pending queue contains jobs OR some job is running. boolean active = mPendingJobs.size() > 0; if (mPendingJobs.size() <= 0) { @@ -895,20 +898,18 @@ public final class JobSchedulerService extends com.android.server.SystemService * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know * about. */ - private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) { - synchronized (mLock) { - if (!jobStatus.isPrepared()) { - Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus); - } - final boolean update = mJobs.add(jobStatus); - if (mReadyToRock) { - for (int i = 0; i < mControllers.size(); i++) { - StateController controller = mControllers.get(i); - if (update) { - controller.maybeStopTrackingJobLocked(jobStatus, null, true); - } - controller.maybeStartTrackingJobLocked(jobStatus, lastJob); + private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { + if (!jobStatus.isPreparedLocked()) { + Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus); + } + final boolean update = mJobs.add(jobStatus); + if (mReadyToRock) { + for (int i = 0; i < mControllers.size(); i++) { + StateController controller = mControllers.get(i); + if (update) { + controller.maybeStopTrackingJobLocked(jobStatus, null, true); } + controller.maybeStartTrackingJobLocked(jobStatus, lastJob); } } } @@ -917,19 +918,20 @@ public final class JobSchedulerService extends com.android.server.SystemService * Called when we want to remove a JobStatus object that we've finished executing. Returns the * object removed. */ - private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob, + private boolean stopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean writeBack) { - synchronized (mLock) { - // Remove from store as well as controllers. - final boolean removed = mJobs.remove(jobStatus, writeBack); - if (removed && mReadyToRock) { - for (int i=0; i<mControllers.size(); i++) { - StateController controller = mControllers.get(i); - controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false); - } + // Deal with any remaining work items in the old job. + jobStatus.stopTrackingJobLocked(incomingJob); + + // Remove from store as well as controllers. + final boolean removed = mJobs.remove(jobStatus, writeBack); + if (removed && mReadyToRock) { + for (int i=0; i<mControllers.size(); i++) { + StateController controller = mControllers.get(i); + controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false); } - return removed; } + return removed; } private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) { @@ -990,7 +992,7 @@ public final class JobSchedulerService extends com.android.server.SystemService * * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH */ - private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) { + private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) { final long elapsedNowMillis = SystemClock.elapsedRealtime(); final JobInfo job = failureToReschedule.getJob(); @@ -1017,7 +1019,7 @@ public final class JobSchedulerService extends com.android.server.SystemService JobStatus.NO_LATEST_RUNTIME, backoffAttempts); for (int ic=0; ic<mControllers.size(); ic++) { StateController controller = mControllers.get(ic); - controller.rescheduleForFailure(newJob, failureToReschedule); + controller.rescheduleForFailureLocked(newJob, failureToReschedule); } return newJob; } @@ -1065,13 +1067,13 @@ public final class JobSchedulerService extends com.android.server.SystemService * @param needsReschedule Whether the implementing class should reschedule this job. */ @Override - public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) { + public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) { if (DEBUG) { Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); } // Do not write back immediately if this is a periodic job. The job may get lost if system // shuts down before it is added back. - if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) { + if (!stopTrackingJobLocked(jobStatus, null, !jobStatus.getJob().isPeriodic())) { if (DEBUG) { Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); } @@ -1085,24 +1087,24 @@ public final class JobSchedulerService extends com.android.server.SystemService // the old job after scheduling the new one, but since we have no lock held here // that may cause ordering problems if the app removes jobStatus while in here. if (needsReschedule) { - JobStatus rescheduled = getRescheduleJobForFailure(jobStatus); + JobStatus rescheduled = getRescheduleJobForFailureLocked(jobStatus); try { - rescheduled.prepare(ActivityManager.getService()); + rescheduled.prepareLocked(ActivityManager.getService()); } catch (SecurityException e) { Slog.w(TAG, "Unable to regrant job permissions for " + rescheduled); } - startTrackingJob(rescheduled, jobStatus); + startTrackingJobLocked(rescheduled, jobStatus); } else if (jobStatus.getJob().isPeriodic()) { JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus); try { - rescheduledPeriodic.prepare(ActivityManager.getService()); + rescheduledPeriodic.prepareLocked(ActivityManager.getService()); } catch (SecurityException e) { Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledPeriodic); } - startTrackingJob(rescheduledPeriodic, jobStatus); + startTrackingJobLocked(rescheduledPeriodic, jobStatus); } - jobStatus.unprepare(ActivityManager.getService()); - reportActive(); + jobStatus.unprepareLocked(ActivityManager.getService()); + reportActiveLocked(); mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); } @@ -1410,7 +1412,7 @@ public final class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); } assignJobsToContextsLocked(); - reportActive(); + reportActiveLocked(); } } } @@ -1698,7 +1700,37 @@ public final class JobSchedulerService extends com.android.server.SystemService long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.schedule(job, uid); + return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // IJobScheduler implementation + @Override + public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work); + } + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + + enforceValidJobRequest(uid, job); + if (job.isPersisted()) { + throw new IllegalArgumentException("Can't enqueue work for persisted jobs"); + } + if (work == null) { + throw new NullPointerException("work is null"); + } + + if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG); + } + + long ident = Binder.clearCallingIdentity(); + try { + return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null); } finally { Binder.restoreCallingIdentity(ident); } @@ -1731,7 +1763,7 @@ public final class JobSchedulerService extends com.android.server.SystemService long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.scheduleAsPackage(job, callerUid, + return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid, packageName, userId, tag); } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 728ed72d2151..c7ef0e26971f 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -21,11 +21,11 @@ import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.IJobCallback; import android.app.job.IJobService; +import android.app.job.JobWorkItem; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.PackageManager; import android.net.Uri; import android.os.Binder; import android.os.Handler; @@ -195,7 +195,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mExecutionStartTimeElapsed = SystemClock.elapsedRealtime(); mVerb = VERB_BINDING; - scheduleOpTimeOut(); + scheduleOpTimeOutLocked(); final Intent intent = new Intent().setComponent(job.getServiceComponent()); boolean binding = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND, @@ -208,7 +208,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mParams = null; mExecutionStartTimeElapsed = 0L; mVerb = VERB_FINISHED; - removeOpTimeOut(); + removeOpTimeOutLocked(); return false; } try { @@ -297,6 +297,38 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget(); } + @Override + public JobWorkItem dequeueWork(int jobId) { + if (!verifyCallingUid()) { + throw new SecurityException("Bad calling uid: " + Binder.getCallingUid()); + } + JobWorkItem work = null; + boolean stillWorking = false; + synchronized (mLock) { + if (mRunningJob != null) { + work = mRunningJob.dequeueWorkLocked(); + stillWorking = mRunningJob.hasExecutingWorkLocked(); + } + } + if (work == null && !stillWorking) { + jobFinished(jobId, false); + } + return work; + } + + @Override + public boolean completeWork(int jobId, int workId) { + if (!verifyCallingUid()) { + throw new SecurityException("Bad calling uid: " + Binder.getCallingUid()); + } + synchronized (mLock) { + if (mRunningJob != null) { + return mRunningJob.completeWorkLocked(workId); + } + return false; + } + } + /** * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work * we intend to send to the client - we stop sending work when the service is unbound so until @@ -378,46 +410,18 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne public void handleMessage(Message message) { switch (message.what) { case MSG_SERVICE_BOUND: - removeOpTimeOut(); - handleServiceBoundH(); + doServiceBound(); break; case MSG_CALLBACK: - if (DEBUG) { - Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob - + " v:" + VERB_STRINGS[mVerb]); - } - removeOpTimeOut(); - - if (mVerb == VERB_STARTING) { - final boolean workOngoing = message.arg2 == 1; - handleStartedH(workOngoing); - } else if (mVerb == VERB_EXECUTING || - mVerb == VERB_STOPPING) { - final boolean reschedule = message.arg2 == 1; - handleFinishedH(reschedule); - } else { - if (DEBUG) { - Slog.d(TAG, "Unrecognised callback: " + mRunningJob); - } - } + doCallback(message.arg2); break; case MSG_CANCEL: - if (mVerb == VERB_FINISHED) { - if (DEBUG) { - Slog.d(TAG, - "Trying to process cancel for torn-down context, ignoring."); - } - return; - } - mParams.setStopReason(message.arg1); - if (message.arg1 == JobParameters.REASON_PREEMPT) { - mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : - NO_PREFERRED_UID; - } - handleCancelH(); + doCancel(message.arg1); break; case MSG_TIMEOUT: - handleOpTimeoutH(); + synchronized (mLock) { + handleOpTimeoutH(); + } break; case MSG_SHUTDOWN_EXECUTION: closeAndCleanupJobH(true /* needsReschedule */); @@ -427,6 +431,55 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } } + void doServiceBound() { + synchronized (mLock) { + removeOpTimeOutLocked(); + handleServiceBoundH(); + } + } + + void doCallback(int arg2) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob + + " v:" + VERB_STRINGS[mVerb]); + } + removeOpTimeOutLocked(); + + if (mVerb == VERB_STARTING) { + final boolean workOngoing = arg2 == 1; + handleStartedH(workOngoing); + } else if (mVerb == VERB_EXECUTING || + mVerb == VERB_STOPPING) { + final boolean reschedule = arg2 == 1; + handleFinishedH(reschedule); + } else { + if (DEBUG) { + Slog.d(TAG, "Unrecognised callback: " + mRunningJob); + } + } + } + } + + void doCancel(int arg1) { + synchronized (mLock) { + if (mVerb == VERB_FINISHED) { + if (DEBUG) { + Slog.d(TAG, + "Trying to process cancel for torn-down context, ignoring."); + } + return; + } + mParams.setStopReason(arg1); + if (arg1 == JobParameters.REASON_PREEMPT) { + mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : + NO_PREFERRED_UID; + } + handleCancelH(); + } + + } + /** Start the job on the service. */ private void handleServiceBoundH() { if (DEBUG) { @@ -448,7 +501,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } try { mVerb = VERB_STARTING; - scheduleOpTimeOut(); + scheduleOpTimeOutLocked(); service.startJob(mParams); } catch (Exception e) { // We catch 'Exception' because client-app malice or bugs might induce a wide @@ -483,7 +536,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne handleCancelH(); return; } - scheduleOpTimeOut(); + scheduleOpTimeOutLocked(); break; default: Slog.e(TAG, "Handling started job but job wasn't starting! Was " @@ -587,7 +640,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne * VERB_STOPPING. */ private void sendStopMessageH() { - removeOpTimeOut(); + removeOpTimeOutLocked(); if (mVerb != VERB_EXECUTING) { Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob); closeAndCleanupJobH(false /* reschedule */); @@ -595,7 +648,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } try { mVerb = VERB_STOPPING; - scheduleOpTimeOut(); + scheduleOpTimeOutLocked(); service.stopJob(mParams); } catch (RemoteException e) { Slog.e(TAG, "Error sending onStopJob to client.", e); @@ -635,13 +688,13 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mCancelled.set(false); service = null; mAvailable = true; + removeOpTimeOutLocked(); + removeMessages(MSG_CALLBACK); + removeMessages(MSG_SERVICE_BOUND); + removeMessages(MSG_CANCEL); + removeMessages(MSG_SHUTDOWN_EXECUTION); + mCompletedListener.onJobCompletedLocked(completedJob, reschedule); } - removeOpTimeOut(); - removeMessages(MSG_CALLBACK); - removeMessages(MSG_SERVICE_BOUND); - removeMessages(MSG_CANCEL); - removeMessages(MSG_SHUTDOWN_EXECUTION); - mCompletedListener.onJobCompleted(completedJob, reschedule); } } @@ -650,8 +703,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne * we haven't received a response in a certain amount of time, we want to give up and carry * on with life. */ - private void scheduleOpTimeOut() { - removeOpTimeOut(); + private void scheduleOpTimeOutLocked() { + removeOpTimeOutLocked(); final long timeoutMillis; switch (mVerb) { @@ -678,7 +731,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } - private void removeOpTimeOut() { + private void removeOpTimeOutLocked() { mCallbackHandler.removeMessages(MSG_TIMEOUT); } } diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index c0264df9c66c..fcc082790892 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -454,7 +454,7 @@ public class JobStore { IActivityManager am = ActivityManager.getService(); for (int i=0; i<jobs.size(); i++) { JobStatus js = jobs.get(i); - js.prepare(am); + js.prepareLocked(am); this.jobSet.add(js); } } diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java index 5d209fc14b5c..29f0e2c3b957 100644 --- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java +++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java @@ -164,7 +164,7 @@ public class ContentObserverController extends StateController { && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) { // We are stopping this job, but it is going to be replaced by this given // incoming job. We want to propagate our state over to it, so we don't - // lose any content changes that had happend since the last one started. + // lose any content changes that had happened since the last one started. // If there is a previous job associated with the new job, propagate over // any pending content URI trigger reports. if (incomingJob.contentObserverJobInstance == null) { @@ -195,16 +195,14 @@ public class ContentObserverController extends StateController { } @Override - public void rescheduleForFailure(JobStatus newJob, JobStatus failureToReschedule) { + public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) { if (failureToReschedule.hasContentTriggerConstraint() && newJob.hasContentTriggerConstraint()) { - synchronized (mLock) { - // Our job has failed, and we are scheduling a new job for it. - // Copy the last reported content changes in to the new job, so when - // we schedule the new one we will pick them up and report them again. - newJob.changedAuthorities = failureToReschedule.changedAuthorities; - newJob.changedUris = failureToReschedule.changedUris; - } + // Our job has failed, and we are scheduling a new job for it. + // Copy the last reported content changes in to the new job, so when + // we schedule the new one we will pick them up and report them again. + newJob.changedAuthorities = failureToReschedule.changedAuthorities; + newJob.changedUris = failureToReschedule.changedUris; } } 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 d27d0e582cc5..4cdce5f52f8f 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -19,6 +19,7 @@ package com.android.server.job.controllers; import android.app.AppGlobals; import android.app.IActivityManager; import android.app.job.JobInfo; +import android.app.job.JobWorkItem; import android.content.ClipData; import android.content.ComponentName; import android.content.ContentProvider; @@ -35,6 +36,7 @@ import android.util.Slog; import android.util.TimeUtils; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; /** @@ -126,6 +128,14 @@ public final class JobStatus { public int lastEvaluatedPriority; + // If non-null, this is work that has been enqueued for the job. + public ArrayList<JobWorkItem> pendingWork; + + // If non-null, this is work that is currently being executed. + public ArrayList<JobWorkItem> executingWork; + + public int nextPendingWorkId = 1; + // Used by shell commands public int overrideState = 0; @@ -256,7 +266,59 @@ public final class JobStatus { earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); } - public void prepare(IActivityManager am) { + public void enqueueWorkLocked(JobWorkItem work) { + if (pendingWork == null) { + pendingWork = new ArrayList<>(); + } + work.setWorkId(nextPendingWorkId); + nextPendingWorkId++; + pendingWork.add(work); + } + + public JobWorkItem dequeueWorkLocked() { + if (pendingWork != null && pendingWork.size() > 0) { + JobWorkItem work = pendingWork.remove(0); + if (work != null) { + if (executingWork == null) { + executingWork = new ArrayList<>(); + } + executingWork.add(work); + } + return work; + } + return null; + } + + public boolean hasExecutingWorkLocked() { + return executingWork != null && executingWork.size() > 0; + } + + public boolean completeWorkLocked(int workId) { + if (executingWork != null) { + final int N = executingWork.size(); + for (int i = 0; i < N; i++) { + if (executingWork.get(i).getWorkId() == workId) { + executingWork.remove(i); + return true; + } + } + } + return false; + } + + public void stopTrackingJobLocked(JobStatus incomingJob) { + if (incomingJob != null) { + // We are replacing with a new job -- transfer the work! + incomingJob.pendingWork = pendingWork; + pendingWork = null; + incomingJob.nextPendingWorkId = nextPendingWorkId; + } else { + // We are completely stopping the job... need to clean up work. + // XXX remove perms when that is impl. + } + } + + public void prepareLocked(IActivityManager am) { if (prepared) { Slog.wtf(TAG, "Already prepared: " + this); return; @@ -271,7 +333,7 @@ public final class JobStatus { } } - public void unprepare(IActivityManager am) { + public void unprepareLocked(IActivityManager am) { if (!prepared) { Slog.wtf(TAG, "Hasn't been prepared: " + this); return; @@ -288,7 +350,7 @@ public final class JobStatus { } } - public boolean isPrepared() { + public boolean isPreparedLocked() { return prepared; } @@ -854,6 +916,22 @@ public final class JobStatus { } } } + if (pendingWork != null && pendingWork.size() > 0) { + pw.print(prefix); pw.println("Pending work:"); + for (int i = 0; i < pendingWork.size(); i++) { + JobWorkItem work = pendingWork.get(i); + pw.print(prefix); pw.print(" #"); pw.print(i); pw.print(": #"); + pw.print(work.getWorkId()); pw.print(" "); pw.println(work.getIntent()); + } + } + if (executingWork != null && executingWork.size() > 0) { + pw.print(prefix); pw.println("Executing work:"); + for (int i = 0; i < executingWork.size(); i++) { + JobWorkItem work = executingWork.get(i); + pw.print(prefix); pw.print(" #"); pw.print(i); pw.print(": #"); + pw.print(work.getWorkId()); pw.print(" "); pw.println(work.getIntent()); + } + } pw.print(prefix); pw.print("Earliest run time: "); pw.println(formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)); pw.print(prefix); pw.print("Latest run time: "); diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java index 1721fb9ac3fc..497faab8b3b4 100644 --- a/services/core/java/com/android/server/job/controllers/StateController.java +++ b/services/core/java/com/android/server/job/controllers/StateController.java @@ -61,7 +61,7 @@ public abstract class StateController { /** * Called when a new job is being created to reschedule an old failed job. */ - public void rescheduleForFailure(JobStatus newJob, JobStatus failureToReschedule) { + public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) { } public abstract void dumpControllerStateLocked(PrintWriter pw, int filterUid); |