diff options
author | Christopher Tate <ctate@google.com> | 2014-06-09 19:50:00 -0700 |
---|---|---|
committer | Christopher Tate <ctate@google.com> | 2014-06-10 12:51:55 -0700 |
commit | 7060b04f6d92351b67222e636ab378a0273bf3e7 (patch) | |
tree | 82fce1e04dd58a5d79895d0869b3b0adeffbb417 /core/java/android/app/job | |
parent | 6d7a25f317be60ae8a4d8806e517052be2398753 (diff) | |
download | frameworks_base-7060b04f6d92351b67222e636ab378a0273bf3e7.zip frameworks_base-7060b04f6d92351b67222e636ab378a0273bf3e7.tar.gz frameworks_base-7060b04f6d92351b67222e636ab378a0273bf3e7.tar.bz2 |
Out with the old; in with the new
Switch to the official "JobScheduler" etc naming.
Bug 14997851
Change-Id: I73a61aaa9af0740c114d08188bd97c52f3ac86b7
Diffstat (limited to 'core/java/android/app/job')
-rw-r--r-- | core/java/android/app/job/IJobCallback.aidl | 53 | ||||
-rw-r--r-- | core/java/android/app/job/IJobScheduler.aidl | 30 | ||||
-rw-r--r-- | core/java/android/app/job/IJobService.aidl | 32 | ||||
-rw-r--r-- | core/java/android/app/job/JobInfo.aidl | 19 | ||||
-rw-r--r-- | core/java/android/app/job/JobInfo.java | 432 | ||||
-rw-r--r-- | core/java/android/app/job/JobParameters.aidl | 19 | ||||
-rw-r--r-- | core/java/android/app/job/JobParameters.java | 93 | ||||
-rw-r--r-- | core/java/android/app/job/JobScheduler.java | 72 | ||||
-rw-r--r-- | core/java/android/app/job/JobService.java | 260 |
9 files changed, 1010 insertions, 0 deletions
diff --git a/core/java/android/app/job/IJobCallback.aidl b/core/java/android/app/job/IJobCallback.aidl new file mode 100644 index 0000000..2d3948f --- /dev/null +++ b/core/java/android/app/job/IJobCallback.aidl @@ -0,0 +1,53 @@ +/** + * Copyright 2014, 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; + +/** + * The server side of the JobScheduler IPC protocols. The app-side implementation + * invokes on this interface to indicate completion of the (asynchronous) instructions + * issued by the server. + * + * In all cases, the 'who' parameter is the caller's service binder, used to track + * which Job Service instance is reporting. + * + * {@hide} + */ +interface IJobCallback { + /** + * Immediate callback to the system after sending a start signal, used to quickly detect ANR. + * + * @param jobId Unique integer used to identify this job. + * @param ongoing True to indicate that the client is processing the job. False if the job is + * complete + */ + void acknowledgeStartMessage(int jobId, boolean ongoing); + /** + * Immediate callback to the system after sending a stop signal, used to quickly detect ANR. + * + * @param jobId Unique integer used to identify this job. + * @param reschedule Whether or not to reschedule this job. + */ + void acknowledgeStopMessage(int jobId, boolean reschedule); + /* + * 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. + * + * @param jobId Unique integer used to identify this job. + * @param reschedule Whether or not to reschedule this job. + */ + void jobFinished(int jobId, boolean reschedule); +} diff --git a/core/java/android/app/job/IJobScheduler.aidl b/core/java/android/app/job/IJobScheduler.aidl new file mode 100644 index 0000000..f1258ae --- /dev/null +++ b/core/java/android/app/job/IJobScheduler.aidl @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2014 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.app.job.JobInfo; + + /** + * IPC interface that supports the app-facing {@link #JobScheduler} api. + * {@hide} + */ +interface IJobScheduler { + int schedule(in JobInfo job); + void cancel(int jobId); + void cancelAll(); + List<JobInfo> getAllPendingJobs(); +} diff --git a/core/java/android/app/job/IJobService.aidl b/core/java/android/app/job/IJobService.aidl new file mode 100644 index 0000000..63f8b81 --- /dev/null +++ b/core/java/android/app/job/IJobService.aidl @@ -0,0 +1,32 @@ +/** + * Copyright 2014, 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.app.job.JobParameters; + +/** + * Interface that the framework uses to communicate with application code that implements a + * JobService. End user code does not implement this interface directly; instead, the app's + * service implementation will extend android.app.job.JobService. + * {@hide} + */ +oneway interface IJobService { + /** Begin execution of application's job. */ + void startJob(in JobParameters jobParams); + /** Stop execution of application's task. */ + void stopJob(in JobParameters jobParams); +} diff --git a/core/java/android/app/job/JobInfo.aidl b/core/java/android/app/job/JobInfo.aidl new file mode 100644 index 0000000..7b198a8 --- /dev/null +++ b/core/java/android/app/job/JobInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (C) 2014 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; + +parcelable JobInfo; diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java new file mode 100644 index 0000000..a22e4cd --- /dev/null +++ b/core/java/android/app/job/JobInfo.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2014 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.ComponentName; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; + +/** + * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the + * parameters required to schedule work against the calling application. These are constructed + * using the {@link JobInfo.Builder}. + */ +public class JobInfo implements Parcelable { + public interface NetworkType { + /** Default. */ + public final int NONE = 0; + /** This job requires network connectivity. */ + public final int ANY = 1; + /** This job requires network connectivity that is unmetered. */ + public final int UNMETERED = 2; + } + + /** + * Amount of backoff a job has initially by default, in milliseconds. + * @hide. + */ + public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 5000L; + + /** + * Default type of backoff. + * @hide + */ + public static final int DEFAULT_BACKOFF_POLICY = BackoffPolicy.EXPONENTIAL; + /** + * Maximum backoff we allow for a job, in milliseconds. + * @hide + */ + public static final long MAX_BACKOFF_DELAY_MILLIS = 24 * 60 * 60 * 1000; // 24 hours. + + /** + * Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1 + * Expon: retry_time(failure_time, t) = failure_time + initial_retry_delay ^ t, t >= 1 + */ + public interface BackoffPolicy { + public final int LINEAR = 0; + public final int EXPONENTIAL = 1; + } + + private final int jobId; + // TODO: Change this to use PersistableBundle when that lands in master. + private final PersistableBundle extras; + private final ComponentName service; + private final boolean requireCharging; + private final boolean requireDeviceIdle; + private final boolean hasEarlyConstraint; + private final boolean hasLateConstraint; + private final int networkCapabilities; + private final long minLatencyMillis; + private final long maxExecutionDelayMillis; + private final boolean isPeriodic; + private final long intervalMillis; + private final long initialBackoffMillis; + private final int backoffPolicy; + + /** + * Unique job id associated with this class. This is assigned to your job by the scheduler. + */ + public int getId() { + return jobId; + } + + /** + * Bundle of extras which are returned to your application at execution time. + */ + public PersistableBundle getExtras() { + return extras; + } + + /** + * Name of the service endpoint that will be called back into by the JobScheduler. + */ + public ComponentName getService() { + return service; + } + + /** + * Whether this job needs the device to be plugged in. + */ + public boolean isRequireCharging() { + return requireCharging; + } + + /** + * Whether this job needs the device to be in an Idle maintenance window. + */ + public boolean isRequireDeviceIdle() { + return requireDeviceIdle; + } + + /** + * See {@link android.app.job.JobInfo.NetworkType} for a description of this value. + */ + public int getNetworkCapabilities() { + return networkCapabilities; + } + + /** + * Set for a job that does not recur periodically, to specify a delay after which the job + * will be eligible for execution. This value is not set if the job recurs periodically. + */ + public long getMinLatencyMillis() { + return minLatencyMillis; + } + + /** + * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the job recurs + * periodically. + */ + public long getMaxExecutionDelayMillis() { + return maxExecutionDelayMillis; + } + + /** + * Track whether this job will repeat with a given period. + */ + public boolean isPeriodic() { + return isPeriodic; + } + + /** + * Set to the interval between occurrences of this job. This value is <b>not</b> set if the + * job does not recur periodically. + */ + public long getIntervalMillis() { + return intervalMillis; + } + + /** + * The amount of time the JobScheduler will wait before rescheduling a failed job. This value + * will be increased depending on the backoff policy specified at job creation time. Defaults + * to 5 seconds. + */ + public long getInitialBackoffMillis() { + return initialBackoffMillis; + } + + /** + * See {@link android.app.job.JobInfo.BackoffPolicy} for an explanation of the values this field + * can take. This defaults to exponential. + */ + public int getBackoffPolicy() { + return backoffPolicy; + } + + /** + * User can specify an early constraint of 0L, which is valid, so we keep track of whether the + * function was called at all. + * @hide + */ + public boolean hasEarlyConstraint() { + return hasEarlyConstraint; + } + + /** + * User can specify a late constraint of 0L, which is valid, so we keep track of whether the + * function was called at all. + * @hide + */ + public boolean hasLateConstraint() { + return hasLateConstraint; + } + + private JobInfo(Parcel in) { + jobId = in.readInt(); + extras = in.readPersistableBundle(); + service = in.readParcelable(null); + requireCharging = in.readInt() == 1; + requireDeviceIdle = in.readInt() == 1; + networkCapabilities = in.readInt(); + minLatencyMillis = in.readLong(); + maxExecutionDelayMillis = in.readLong(); + isPeriodic = in.readInt() == 1; + intervalMillis = in.readLong(); + initialBackoffMillis = in.readLong(); + backoffPolicy = in.readInt(); + hasEarlyConstraint = in.readInt() == 1; + hasLateConstraint = in.readInt() == 1; + } + + private JobInfo(JobInfo.Builder b) { + jobId = b.mJobId; + extras = b.mExtras; + service = b.mJobService; + requireCharging = b.mRequiresCharging; + requireDeviceIdle = b.mRequiresDeviceIdle; + networkCapabilities = b.mNetworkCapabilities; + minLatencyMillis = b.mMinLatencyMillis; + maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; + isPeriodic = b.mIsPeriodic; + intervalMillis = b.mIntervalMillis; + initialBackoffMillis = b.mInitialBackoffMillis; + backoffPolicy = b.mBackoffPolicy; + hasEarlyConstraint = b.mHasEarlyConstraint; + hasLateConstraint = b.mHasLateConstraint; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(jobId); + out.writePersistableBundle(extras); + out.writeParcelable(service, flags); + out.writeInt(requireCharging ? 1 : 0); + out.writeInt(requireDeviceIdle ? 1 : 0); + out.writeInt(networkCapabilities); + out.writeLong(minLatencyMillis); + out.writeLong(maxExecutionDelayMillis); + out.writeInt(isPeriodic ? 1 : 0); + out.writeLong(intervalMillis); + out.writeLong(initialBackoffMillis); + out.writeInt(backoffPolicy); + out.writeInt(hasEarlyConstraint ? 1 : 0); + out.writeInt(hasLateConstraint ? 1 : 0); + } + + public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() { + @Override + public JobInfo createFromParcel(Parcel in) { + return new JobInfo(in); + } + + @Override + public JobInfo[] newArray(int size) { + return new JobInfo[size]; + } + }; + + /** Builder class for constructing {@link JobInfo} objects. */ + public static final class Builder { + private int mJobId; + private PersistableBundle mExtras = PersistableBundle.EMPTY; + private ComponentName mJobService; + // Requirements. + private boolean mRequiresCharging; + private boolean mRequiresDeviceIdle; + private int mNetworkCapabilities; + // One-off parameters. + private long mMinLatencyMillis; + private long mMaxExecutionDelayMillis; + // Periodic parameters. + private boolean mIsPeriodic; + private boolean mHasEarlyConstraint; + private boolean mHasLateConstraint; + private long mIntervalMillis; + // Back-off parameters. + private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS; + private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY; + /** Easy way to track whether the client has tried to set a back-off policy. */ + private boolean mBackoffPolicySet = false; + + /** + * @param jobId Application-provided id for this job. Subsequent calls to cancel, or + * jobs created with the same jobId, will update the pre-existing job with + * the same id. + * @param jobService The endpoint that you implement that will receive the callback from the + * JobScheduler. + */ + public Builder(int jobId, ComponentName jobService) { + mJobService = jobService; + mJobId = jobId; + } + + /** + * Set optional extras. This is persisted, so we only allow primitive types. + * @param extras Bundle containing extras you want the scheduler to hold on to for you. + */ + public Builder setExtras(PersistableBundle extras) { + mExtras = extras; + return this; + } + + /** + * Set some description of the kind of network capabilities you would like to have. This + * will be a parameter defined in {@link android.app.job.JobInfo.NetworkType}. + * Not calling this function means the network is not necessary. + * Bear in mind that calling this function defines network as a strict requirement for your + * job if the network requested is not available your job will never run. See + * {@link #setOverrideDeadline(long)} to change this behaviour. + */ + public Builder setRequiredNetworkCapabilities(int networkCapabilities) { + mNetworkCapabilities = networkCapabilities; + return this; + } + + /** + * Specify that to run this job, the device needs to be plugged in. This defaults to + * false. + * @param requiresCharging Whether or not the device is plugged in. + */ + public Builder setRequiresCharging(boolean requiresCharging) { + mRequiresCharging = requiresCharging; + return this; + } + + /** + * Specify that to run, the job needs the device to be in idle mode. This defaults to + * false. + * <p>Idle mode is a loose definition provided by the system, which means that the device + * is not in use, and has not been in use for some time. As such, it is a good time to + * perform resource heavy jobs. Bear in mind that battery usage will still be attributed + * to your application, and surfaced to the user in battery stats.</p> + * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance + * window. + */ + public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) { + mRequiresDeviceIdle = requiresDeviceIdle; + return this; + } + + /** + * Specify that this job should recur with the provided interval, not more than once per + * period. You have no control over when within this interval this job will be executed, + * only the guarantee that it will be executed at most once within this interval. + * A periodic job will be repeated until the phone is turned off, however it will only be + * persisted beyond boot if the client app has declared the + * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule + * periodic jobs without this permission, they simply will cease to exist after the phone + * restarts. + * Setting this function on the builder with {@link #setMinimumLatency(long)} or + * {@link #setOverrideDeadline(long)} will result in an error. + * @param intervalMillis Millisecond interval for which this job will repeat. + */ + public Builder setPeriodic(long intervalMillis) { + mIsPeriodic = true; + mIntervalMillis = intervalMillis; + mHasEarlyConstraint = mHasLateConstraint = true; + return this; + } + + /** + * Specify that this job should be delayed by the provided amount of time. + * Because it doesn't make sense setting this property on a periodic job, doing so will + * throw an {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + * @param minLatencyMillis Milliseconds before which this job will not be considered for + * execution. + */ + public Builder setMinimumLatency(long minLatencyMillis) { + mMinLatencyMillis = minLatencyMillis; + mHasEarlyConstraint = true; + return this; + } + + /** + * Set deadline which is the maximum scheduling latency. The job will be run by this + * deadline even if other requirements are not met. Because it doesn't make sense setting + * this property on a periodic job, doing so will throw an + * {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + */ + public Builder setOverrideDeadline(long maxExecutionDelayMillis) { + mMaxExecutionDelayMillis = maxExecutionDelayMillis; + mHasLateConstraint = true; + return this; + } + + /** + * Set up the back-off/retry policy. + * This defaults to some respectable values: {5 seconds, Exponential}. We cap back-off at + * 1hr. + * Note that trying to set a backoff criteria for a job with + * {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build(). + * This is because back-off typically does not make sense for these types of jobs. See + * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)} + * for more description of the return value for the case of a job executing while in idle + * mode. + * @param initialBackoffMillis Millisecond time interval to wait initially when job has + * failed. + * @param backoffPolicy is one of {@link BackoffPolicy} + */ + public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) { + mBackoffPolicySet = true; + mInitialBackoffMillis = initialBackoffMillis; + mBackoffPolicy = backoffPolicy; + return this; + } + + /** + * @return The job object to hand to the JobScheduler. This object is immutable. + */ + public JobInfo build() { + mExtras = new PersistableBundle(mExtras); // Make our own copy. + // Check that a deadline was not set on a periodic job. + if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) { + throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " + + "periodic job."); + } + if (mIsPeriodic && (mMinLatencyMillis != 0L)) { + throw new IllegalArgumentException("Can't call setMinimumLatency() on a " + + "periodic job"); + } + if (mBackoffPolicySet && mRequiresDeviceIdle) { + throw new IllegalArgumentException("An idle mode job will not respect any" + + " back-off policy, so calling setBackoffCriteria with" + + " setRequiresDeviceIdle is an error."); + } + return new JobInfo(this); + } + } + +} diff --git a/core/java/android/app/job/JobParameters.aidl b/core/java/android/app/job/JobParameters.aidl new file mode 100644 index 0000000..e7551b9 --- /dev/null +++ b/core/java/android/app/job/JobParameters.aidl @@ -0,0 +1,19 @@ +/** + * Copyright 2014, 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; + +parcelable JobParameters; diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java new file mode 100644 index 0000000..724856a --- /dev/null +++ b/core/java/android/app/job/JobParameters.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 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.app.job.IJobCallback; +import android.app.job.IJobCallback.Stub; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; + +/** + * Contains the parameters used to configure/identify your job. You do not create this object + * yourself, instead it is handed in to your application by the System. + */ +public class JobParameters implements Parcelable { + + private final int jobId; + private final PersistableBundle extras; + private final IBinder callback; + + /** @hide */ + public JobParameters(int jobId, PersistableBundle extras, IBinder callback) { + this.jobId = jobId; + this.extras = extras; + this.callback = callback; + } + + /** + * @return The unique id of this job, specified at creation time. + */ + public int getJobId() { + return jobId; + } + + /** + * @return The extras you passed in when constructing this job with + * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will + * never be null. If you did not set any extras this will be an empty bundle. + */ + public PersistableBundle getExtras() { + return extras; + } + + /** @hide */ + public IJobCallback getCallback() { + return IJobCallback.Stub.asInterface(callback); + } + + private JobParameters(Parcel in) { + jobId = in.readInt(); + extras = in.readPersistableBundle(); + callback = in.readStrongBinder(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(jobId); + dest.writePersistableBundle(extras); + dest.writeStrongBinder(callback); + } + + public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() { + @Override + public JobParameters createFromParcel(Parcel in) { + return new JobParameters(in); + } + + @Override + public JobParameters[] newArray(int size) { + return new JobParameters[size]; + } + }; +} diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java new file mode 100644 index 0000000..7fe192c --- /dev/null +++ b/core/java/android/app/job/JobScheduler.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 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 java.util.List; + +import android.content.Context; + +/** + * Class for scheduling various types of jobs with the scheduling framework on the device. + * + * <p>You do not + * instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService + * Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}. + */ +public abstract class JobScheduler { + /** + * Returned from {@link #schedule(JobInfo)} when an invalid parameter was supplied. This can occur + * if the run-time for your job is too short, or perhaps the system can't resolve the + * requisite {@link JobService} in your package. + */ + public static final int RESULT_FAILURE = 0; + /** + * Returned from {@link #schedule(JobInfo)} if this application has made too many requests for + * work over too short a time. + */ + // TODO: Determine if this is necessary. + public static final int RESULT_SUCCESS = 1; + + /** + * @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. + * @return If >0, this int returns the jobId of the successfully scheduled job. + * Otherwise you have to compare the return value to the error codes defined in this class. + */ + public abstract int schedule(JobInfo job); + + /** + * Cancel a job that is pending in the JobScheduler. + * @param jobId unique identifier for this job. Obtain this value from the jobs returned by + * {@link #getAllPendingJobs()}. + * @return + */ + public abstract void cancel(int jobId); + + /** + * Cancel all jobs that have been registered with the JobScheduler by this package. + */ + public abstract void cancelAll(); + + /** + * @return a list of all the jobs registered by this package that have not yet been executed. + */ + public abstract List<JobInfo> getAllPendingJobs(); + +} diff --git a/core/java/android/app/job/JobService.java b/core/java/android/app/job/JobService.java new file mode 100644 index 0000000..eea0268 --- /dev/null +++ b/core/java/android/app/job/JobService.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2014 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.app.Service; +import android.app.job.IJobCallback; +import android.app.job.IJobService; +import android.app.job.IJobService.Stub; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +/** + * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p> + * <p>This is the base class that handles asynchronous requests that were previously scheduled. You + * are responsible for overriding {@link JobService#onStartJob(JobParameters)}, which is where + * you will implement your job logic.</p> + * <p>This service executes each incoming job on a {@link android.os.Handler} running on your + * application's main thread. This means that you <b>must</b> offload your execution logic to + * another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result + * in blocking any future callbacks from the JobManager - specifically + * {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the + * scheduling requirements are no longer being met.</p> + */ +public abstract class JobService extends Service { + private static final String TAG = "JobService"; + + /** + * Job services must be protected with this permission: + * + * <pre class="prettyprint"> + * <service android:name="MyJobService" + * android:permission="android.permission.BIND_JOB_SERVICE" > + * ... + * </service> + * </pre> + * + * <p>If a job service is declared in the manifest but not protected with this + * permission, that service will be ignored by the OS. + */ + public static final String PERMISSION_BIND = + "android.permission.BIND_JOB_SERVICE"; + + /** + * Identifier for a message that will result in a call to + * {@link #onStartJob(android.app.job.JobParameters)}. + */ + private final int MSG_EXECUTE_JOB = 0; + /** + * Message that will result in a call to {@link #onStopJob(android.app.job.JobParameters)}. + */ + private final int MSG_STOP_JOB = 1; + /** + * Message that the client has completed execution of this job. + */ + private final int MSG_JOB_FINISHED = 2; + + /** Lock object for {@link #mHandler}. */ + private final Object mHandlerLock = new Object(); + + /** + * Handler we post jobs to. Responsible for calling into the client logic, and handling the + * callback to the system. + */ + @GuardedBy("mHandlerLock") + JobHandler mHandler; + + /** Binder for this service. */ + IJobService mBinder = new IJobService.Stub() { + @Override + public void startJob(JobParameters jobParams) { + ensureHandler(); + Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams); + m.sendToTarget(); + } + @Override + public void stopJob(JobParameters jobParams) { + ensureHandler(); + Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams); + m.sendToTarget(); + } + }; + + /** @hide */ + void ensureHandler() { + synchronized (mHandlerLock) { + if (mHandler == null) { + mHandler = new JobHandler(getMainLooper()); + } + } + } + + /** + * Runs on application's main thread - callbacks are meant to offboard work to some other + * (app-specified) mechanism. + * @hide + */ + class JobHandler extends Handler { + JobHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + final JobParameters params = (JobParameters) msg.obj; + switch (msg.what) { + case MSG_EXECUTE_JOB: + try { + boolean workOngoing = JobService.this.onStartJob(params); + ackStartMessage(params, workOngoing); + } catch (Exception e) { + Log.e(TAG, "Error while executing job: " + params.getJobId()); + throw new RuntimeException(e); + } + break; + case MSG_STOP_JOB: + try { + boolean ret = JobService.this.onStopJob(params); + ackStopMessage(params, ret); + } catch (Exception e) { + Log.e(TAG, "Application unable to handle onStopJob.", e); + throw new RuntimeException(e); + } + break; + case MSG_JOB_FINISHED: + final boolean needsReschedule = (msg.arg2 == 1); + IJobCallback callback = params.getCallback(); + if (callback != null) { + try { + callback.jobFinished(params.getJobId(), needsReschedule); + } catch (RemoteException e) { + Log.e(TAG, "Error reporting job finish to system: binder has gone" + + "away."); + } + } else { + Log.e(TAG, "finishJob() called for a nonexistent job id."); + } + break; + default: + Log.e(TAG, "Unrecognised message received."); + break; + } + } + + private void ackStartMessage(JobParameters params, boolean workOngoing) { + final IJobCallback callback = params.getCallback(); + final int jobId = params.getJobId(); + if (callback != null) { + try { + callback.acknowledgeStartMessage(jobId, workOngoing); + } catch(RemoteException e) { + Log.e(TAG, "System unreachable for starting job."); + } + } else { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Attempting to ack a job that has already been processed."); + } + } + } + + private void ackStopMessage(JobParameters params, boolean reschedule) { + final IJobCallback callback = params.getCallback(); + final int jobId = params.getJobId(); + if (callback != null) { + try { + callback.acknowledgeStopMessage(jobId, reschedule); + } catch(RemoteException e) { + Log.e(TAG, "System unreachable for stopping job."); + } + } else { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Attempting to ack a job that has already been processed."); + } + } + } + } + + /** @hide */ + public final IBinder onBind(Intent intent) { + return mBinder.asBinder(); + } + + /** + * Override this method with the callback logic for your job. Any such logic needs to be + * performed on a separate thread, as this function is executed on your application's main + * thread. + * + * @param params Parameters specifying info about this job, including the extras bundle you + * optionally provided at job-creation time. + * @return True if your service needs to process the work (on a separate thread). False if + * there's no more work to be done for this job. + */ + public abstract boolean onStartJob(JobParameters params); + + /** + * This method is called if the system has determined that you must stop execution of your job + * even before you've had a chance to call {@link #jobFinished(JobParameters, boolean)}. + * + * <p>This will happen if the requirements specified at schedule time are no longer met. For + * example you may have requested WiFi with + * {@link android.app.job.JobInfo.Builder#setRequiredNetworkCapabilities(int)}, yet while your + * job was executing the user toggled WiFi. Another example is if you had specified + * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its + * idle maintenance window. You are solely responsible for the behaviour of your application + * upon receipt of this message; your app will likely start to misbehave if you ignore it. One + * immediate repercussion is that the system will cease holding a wakelock for you.</p> + * + * @param params Parameters specifying info about this job. + * @return True to indicate to the JobManager whether you'd like to reschedule this job based + * on the retry criteria provided at job creation-time. False to drop the job. Regardless of + * the value returned, your job must stop executing. + */ + public abstract boolean onStopJob(JobParameters params); + + /** + * Callback to inform the JobManager you've finished executing. This can be called from any + * thread, as it will ultimately be run on your application's main thread. When the system + * receives this message it will release the wakelock being held. + * <p> + * You can specify post-execution behaviour to the scheduler here with + * <code>needsReschedule </code>. This will apply a back-off timer to your job based on + * the default, or what was set with + * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original + * requirements are always honoured even for a backed-off job. Note that a job running in + * idle mode will not be backed-off. Instead what will happen is the job will be re-added + * to the queue and re-executed within a future idle maintenance window. + * </p> + * + * @param params Parameters specifying system-provided info about this job, this was given to + * your application in {@link #onStartJob(JobParameters)}. + * @param needsReschedule True if this job is complete, false if you want the JobManager to + * reschedule you. + */ + public final void jobFinished(JobParameters params, boolean needsReschedule) { + ensureHandler(); + Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params); + m.arg2 = needsReschedule ? 1 : 0; + m.sendToTarget(); + } +}
\ No newline at end of file |