summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt12
-rw-r--r--api/system-current.txt12
-rw-r--r--api/test-current.txt12
-rw-r--r--core/java/android/app/JobSchedulerImpl.java11
-rw-r--r--core/java/android/app/job/IJobCallback.aidl10
-rw-r--r--core/java/android/app/job/IJobScheduler.aidl2
-rw-r--r--core/java/android/app/job/JobInfo.java125
-rw-r--r--core/java/android/app/job/JobParameters.java48
-rw-r--r--core/java/android/app/job/JobScheduler.java44
-rw-r--r--core/java/android/app/job/JobWorkItem.aidl20
-rw-r--r--core/java/android/app/job/JobWorkItem.java97
-rw-r--r--core/java/android/os/BaseBundle.java17
-rw-r--r--core/java/android/os/Parcel.java6
-rw-r--r--core/jni/android_os_Parcel.cpp16
-rw-r--r--services/core/java/com/android/server/job/JobCompletedListener.java2
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java156
-rw-r--r--services/core/java/com/android/server/job/JobServiceContext.java153
-rw-r--r--services/core/java/com/android/server/job/JobStore.java2
-rw-r--r--services/core/java/com/android/server/job/controllers/ContentObserverController.java16
-rw-r--r--services/core/java/com/android/server/job/controllers/JobStatus.java84
-rw-r--r--services/core/java/com/android/server/job/controllers/StateController.java2
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);