| /* |
| * 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.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.usage.UsageStatsManager; |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.ClipData; |
| import android.content.pm.PackageManager; |
| import android.net.Network; |
| import android.net.NetworkRequest; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.os.PersistableBundle; |
| import android.os.RemoteException; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| |
| /** |
| * 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 { |
| |
| /** @hide */ |
| public static final int INTERNAL_STOP_REASON_CANCELED = |
| JobProtoEnums.INTERNAL_STOP_REASON_CANCELLED; // 0. |
| /** @hide */ |
| public static final int INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED = |
| JobProtoEnums.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED; // 1. |
| /** @hide */ |
| public static final int INTERNAL_STOP_REASON_PREEMPT = |
| JobProtoEnums.INTERNAL_STOP_REASON_PREEMPT; // 2. |
| /** |
| * The job ran for at least its minimum execution limit. |
| * @hide |
| */ |
| public static final int INTERNAL_STOP_REASON_TIMEOUT = |
| JobProtoEnums.INTERNAL_STOP_REASON_TIMEOUT; // 3. |
| /** @hide */ |
| public static final int INTERNAL_STOP_REASON_DEVICE_IDLE = |
| JobProtoEnums.INTERNAL_STOP_REASON_DEVICE_IDLE; // 4. |
| /** @hide */ |
| public static final int INTERNAL_STOP_REASON_DEVICE_THERMAL = |
| JobProtoEnums.INTERNAL_STOP_REASON_DEVICE_THERMAL; // 5. |
| /** |
| * The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} |
| * bucket. |
| * |
| * @hide |
| */ |
| public static final int INTERNAL_STOP_REASON_RESTRICTED_BUCKET = |
| JobProtoEnums.INTERNAL_STOP_REASON_RESTRICTED_BUCKET; // 6. |
| /** |
| * The app was uninstalled. |
| * @hide |
| */ |
| public static final int INTERNAL_STOP_REASON_UNINSTALL = |
| JobProtoEnums.INTERNAL_STOP_REASON_UNINSTALL; // 7. |
| /** |
| * The app's data was cleared. |
| * @hide |
| */ |
| public static final int INTERNAL_STOP_REASON_DATA_CLEARED = |
| JobProtoEnums.INTERNAL_STOP_REASON_DATA_CLEARED; // 8. |
| /** |
| * @hide |
| */ |
| public static final int INTERNAL_STOP_REASON_RTC_UPDATED = |
| JobProtoEnums.INTERNAL_STOP_REASON_RTC_UPDATED; // 9. |
| /** |
| * The app called jobFinished() on its own. |
| * @hide |
| */ |
| public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH = |
| JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10. |
| |
| /** |
| * All the stop reason codes. This should be regarded as an immutable array at runtime. |
| * |
| * Note the order of these values will affect "dumpsys batterystats", and we do not want to |
| * change the order of existing fields, so adding new fields is okay but do not remove or |
| * change existing fields. When deprecating a field, just replace that with "-1" in this array. |
| * |
| * @hide |
| */ |
| public static final int[] JOB_STOP_REASON_CODES = { |
| INTERNAL_STOP_REASON_CANCELED, |
| INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, |
| INTERNAL_STOP_REASON_PREEMPT, |
| INTERNAL_STOP_REASON_TIMEOUT, |
| INTERNAL_STOP_REASON_DEVICE_IDLE, |
| INTERNAL_STOP_REASON_DEVICE_THERMAL, |
| INTERNAL_STOP_REASON_RESTRICTED_BUCKET, |
| INTERNAL_STOP_REASON_UNINSTALL, |
| INTERNAL_STOP_REASON_DATA_CLEARED, |
| INTERNAL_STOP_REASON_RTC_UPDATED, |
| INTERNAL_STOP_REASON_SUCCESSFUL_FINISH, |
| }; |
| |
| /** |
| * @hide |
| */ |
| // TODO(142420609): make it @SystemApi for mainline |
| @NonNull |
| public static String getInternalReasonCodeDescription(int reasonCode) { |
| switch (reasonCode) { |
| case INTERNAL_STOP_REASON_CANCELED: return "canceled"; |
| case INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints"; |
| case INTERNAL_STOP_REASON_PREEMPT: return "preempt"; |
| case INTERNAL_STOP_REASON_TIMEOUT: return "timeout"; |
| case INTERNAL_STOP_REASON_DEVICE_IDLE: return "device_idle"; |
| case INTERNAL_STOP_REASON_DEVICE_THERMAL: return "thermal"; |
| case INTERNAL_STOP_REASON_RESTRICTED_BUCKET: return "restricted_bucket"; |
| case INTERNAL_STOP_REASON_UNINSTALL: return "uninstall"; |
| case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared"; |
| case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated"; |
| case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish"; |
| default: return "unknown:" + reasonCode; |
| } |
| } |
| |
| /** @hide */ |
| @NonNull |
| public static int[] getJobStopReasonCodes() { |
| return JOB_STOP_REASON_CODES; |
| } |
| |
| /** |
| * There is no reason the job is stopped. This is the value returned from the JobParameters |
| * object passed to {@link JobService#onStartJob(JobParameters)}. |
| */ |
| public static final int STOP_REASON_UNDEFINED = 0; |
| /** |
| * The job was cancelled directly by the app, either by calling |
| * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a |
| * new job with the same job ID. |
| */ |
| public static final int STOP_REASON_CANCELLED_BY_APP = 1; |
| /** The job was stopped to run a higher priority job of the app. */ |
| public static final int STOP_REASON_PREEMPT = 2; |
| /** |
| * The job used up its maximum execution time and timed out. Each individual job has a maximum |
| * execution time limit, regardless of how much total quota the app has. See the note on |
| * {@link JobScheduler} for the execution time limits. |
| */ |
| public static final int STOP_REASON_TIMEOUT = 3; |
| /** |
| * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this |
| * job. |
| */ |
| public static final int STOP_REASON_DEVICE_STATE = 4; |
| /** |
| * The requested battery-not-low constraint is no longer satisfied. |
| * |
| * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean) |
| */ |
| public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; |
| /** |
| * The requested charging constraint is no longer satisfied. |
| * |
| * @see JobInfo.Builder#setRequiresCharging(boolean) |
| */ |
| public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; |
| /** |
| * The requested connectivity constraint is no longer satisfied. |
| * |
| * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest) |
| * @see JobInfo.Builder#setRequiredNetworkType(int) |
| */ |
| public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; |
| /** |
| * The requested idle constraint is no longer satisfied. |
| * |
| * @see JobInfo.Builder#setRequiresDeviceIdle(boolean) |
| */ |
| public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; |
| /** |
| * The requested storage-not-low constraint is no longer satisfied. |
| * |
| * @see JobInfo.Builder#setRequiresStorageNotLow(boolean) |
| */ |
| public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; |
| /** |
| * The app has consumed all of its current quota. Each app is assigned a quota of how much |
| * it can run jobs within a certain time frame. The quota is informed, in part, by app standby |
| * buckets. Once an app has used up all of its quota, it won't be able to start jobs until |
| * quota is replenished, is changed, or is temporarily not applied. |
| * |
| * @see UsageStatsManager#getAppStandbyBucket() |
| */ |
| public static final int STOP_REASON_QUOTA = 10; |
| /** |
| * The app is restricted from running in the background. |
| * |
| * @see ActivityManager#isBackgroundRestricted() |
| * @see PackageManager#isInstantApp() |
| */ |
| public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; |
| /** |
| * The current standby bucket requires that the job stop now. |
| * |
| * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED |
| */ |
| public static final int STOP_REASON_APP_STANDBY = 12; |
| /** |
| * The user stopped the job. This can happen either through force-stop, adb shell commands, |
| * or uninstalling. |
| */ |
| public static final int STOP_REASON_USER = 13; |
| /** The system is doing some processing that requires stopping this job. */ |
| public static final int STOP_REASON_SYSTEM_PROCESSING = 14; |
| |
| /** @hide */ |
| @IntDef(prefix = {"STOP_REASON_"}, value = { |
| STOP_REASON_UNDEFINED, |
| STOP_REASON_CANCELLED_BY_APP, |
| STOP_REASON_PREEMPT, |
| STOP_REASON_TIMEOUT, |
| STOP_REASON_DEVICE_STATE, |
| STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW, |
| STOP_REASON_CONSTRAINT_CHARGING, |
| STOP_REASON_CONSTRAINT_CONNECTIVITY, |
| STOP_REASON_CONSTRAINT_DEVICE_IDLE, |
| STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW, |
| STOP_REASON_QUOTA, |
| STOP_REASON_BACKGROUND_RESTRICTION, |
| STOP_REASON_APP_STANDBY, |
| STOP_REASON_USER, |
| STOP_REASON_SYSTEM_PROCESSING, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface StopReason { |
| } |
| |
| @UnsupportedAppUsage |
| private final int jobId; |
| private final PersistableBundle extras; |
| private final Bundle transientExtras; |
| private final ClipData clipData; |
| private final int clipGrantFlags; |
| @UnsupportedAppUsage |
| private final IBinder callback; |
| private final boolean overrideDeadlineExpired; |
| private final boolean mIsExpedited; |
| private final Uri[] mTriggeredContentUris; |
| private final String[] mTriggeredContentAuthorities; |
| private final Network network; |
| |
| private int mStopReason = STOP_REASON_UNDEFINED; |
| private int mInternalStopReason; // Default value is REASON_CANCELED |
| private String debugStopReason; // Human readable stop reason for debugging. |
| |
| /** @hide */ |
| public JobParameters(IBinder callback, int jobId, PersistableBundle extras, |
| Bundle transientExtras, ClipData clipData, int clipGrantFlags, |
| boolean overrideDeadlineExpired, boolean isExpedited, Uri[] triggeredContentUris, |
| String[] triggeredContentAuthorities, Network network) { |
| this.jobId = jobId; |
| this.extras = extras; |
| this.transientExtras = transientExtras; |
| this.clipData = clipData; |
| this.clipGrantFlags = clipGrantFlags; |
| this.callback = callback; |
| this.overrideDeadlineExpired = overrideDeadlineExpired; |
| this.mIsExpedited = isExpedited; |
| this.mTriggeredContentUris = triggeredContentUris; |
| this.mTriggeredContentAuthorities = triggeredContentAuthorities; |
| this.network = network; |
| } |
| |
| /** |
| * @return The unique id of this job, specified at creation time. |
| */ |
| public int getJobId() { |
| return jobId; |
| } |
| |
| /** |
| * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will |
| * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not |
| * yet been called. |
| */ |
| @StopReason |
| public int getStopReason() { |
| return mStopReason; |
| } |
| |
| /** @hide */ |
| public int getInternalStopReasonCode() { |
| return mInternalStopReason; |
| } |
| |
| /** |
| * Reason onStopJob() was called on this job. |
| * |
| * @hide |
| */ |
| public String getDebugStopReason() { |
| return debugStopReason; |
| } |
| |
| /** |
| * @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 @NonNull PersistableBundle getExtras() { |
| return extras; |
| } |
| |
| /** |
| * @return The transient extras you passed in when constructing this job with |
| * {@link android.app.job.JobInfo.Builder#setTransientExtras(android.os.Bundle)}. This will |
| * never be null. If you did not set any extras this will be an empty bundle. |
| */ |
| public @NonNull Bundle getTransientExtras() { |
| return transientExtras; |
| } |
| |
| /** |
| * @return The clip you passed in when constructing this job with |
| * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be null |
| * if it was not set. |
| */ |
| public @Nullable ClipData getClipData() { |
| return clipData; |
| } |
| |
| /** |
| * @return The clip grant flags you passed in when constructing this job with |
| * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be 0 |
| * if it was not set. |
| */ |
| public int getClipGrantFlags() { |
| return clipGrantFlags; |
| } |
| |
| /** |
| * @return Whether this job is running as an expedited job or not. A job is guaranteed to have |
| * all expedited job guarantees for the duration of the job execution if this returns |
| * {@code true}. This will return {@code false} if the job that wasn't requested to run as a |
| * expedited job, or if it was requested to run as an expedited job but the app didn't have |
| * any remaining expedited job quota at the time of execution. |
| * |
| * @see JobInfo.Builder#setExpedited(boolean) |
| */ |
| public boolean isExpeditedJob() { |
| return mIsExpedited; |
| } |
| |
| /** |
| * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this |
| * provides an easy way to tell whether the job is being executed due to the deadline |
| * expiring. Note: If the job is running because its deadline expired, it implies that its |
| * constraints will not be met. |
| */ |
| public boolean isOverrideDeadlineExpired() { |
| return overrideDeadlineExpired; |
| } |
| |
| /** |
| * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this |
| * reports which URIs have triggered the job. This will be null if either no URIs have |
| * triggered it (it went off due to a deadline or other reason), or the number of changed |
| * URIs is too large to report. Whether or not the number of URIs is too large, you can |
| * always use {@link #getTriggeredContentAuthorities()} to determine whether the job was |
| * triggered due to any content changes and the authorities they are associated with. |
| */ |
| public @Nullable Uri[] getTriggeredContentUris() { |
| return mTriggeredContentUris; |
| } |
| |
| /** |
| * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this |
| * reports which content authorities have triggered the job. It will only be null if no |
| * authorities have triggered it -- that is, the job executed for some other reason, such |
| * as a deadline expiring. If this is non-null, you can use {@link #getTriggeredContentUris()} |
| * to retrieve the details of which URIs changed (as long as that has not exceeded the maximum |
| * number it can reported). |
| */ |
| public @Nullable String[] getTriggeredContentAuthorities() { |
| return mTriggeredContentAuthorities; |
| } |
| |
| /** |
| * Return the network that should be used to perform any network requests |
| * for this job. |
| * <p> |
| * Devices may have multiple active network connections simultaneously, or |
| * they may not have a default network route at all. To correctly handle all |
| * situations like this, your job should always use the network returned by |
| * this method instead of implicitly using the default network route. |
| * <p> |
| * Note that the system may relax the constraints you originally requested, |
| * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over |
| * a metered network when there is a surplus of metered data available. |
| * |
| * @return the network that should be used to perform any network requests |
| * for this job, or {@code null} if this job didn't set any required |
| * network type or if the job executed when there was no available network to use. |
| * @see JobInfo.Builder#setRequiredNetworkType(int) |
| * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest) |
| */ |
| public @Nullable Network getNetwork() { |
| return network; |
| } |
| |
| /** |
| * 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). |
| * |
| * <p>Once you are done with the {@link JobWorkItem} returned by this method, you must call |
| * {@link #completeWork(JobWorkItem)} with it to inform the system that you are done |
| * executing the work. The job will not be finished until all dequeued work has been |
| * completed. You do not, however, have to complete each returned work item before deqeueing |
| * the next one -- you can use {@link #dequeueWork()} multiple times before completing |
| * previous work if you want to process work in parallel, and you can complete the work |
| * in whatever order you want.</p> |
| * |
| * <p>If the job runs to the end of its available time period before all work has been |
| * completed, it will stop as normal. You should return true from |
| * {@link JobService#onStopJob(JobParameters)} in order to have the job rescheduled, and by |
| * doing so any pending as well as remaining uncompleted work will be re-queued |
| * for the next time the job runs.</p> |
| * |
| * <p>This example shows how to construct a JobService that will serially dequeue and |
| * process work that is available for it:</p> |
| * |
| * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java |
| * service} |
| * |
| * @return Returns a new {@link JobWorkItem} if there is one pending, otherwise null. |
| * If null is returned, the system will also stop the job if all work has also been completed. |
| * (This means that for correct operation, you must always call dequeueWork() after you have |
| * completed other work, to check either for more work or allow the system to stop the job.) |
| */ |
| public @Nullable 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(@NonNull 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 */ |
| @UnsupportedAppUsage |
| public IJobCallback getCallback() { |
| return IJobCallback.Stub.asInterface(callback); |
| } |
| |
| private JobParameters(Parcel in) { |
| jobId = in.readInt(); |
| extras = in.readPersistableBundle(); |
| transientExtras = in.readBundle(); |
| if (in.readInt() != 0) { |
| clipData = ClipData.CREATOR.createFromParcel(in); |
| clipGrantFlags = in.readInt(); |
| } else { |
| clipData = null; |
| clipGrantFlags = 0; |
| } |
| callback = in.readStrongBinder(); |
| overrideDeadlineExpired = in.readInt() == 1; |
| mIsExpedited = in.readBoolean(); |
| mTriggeredContentUris = in.createTypedArray(Uri.CREATOR); |
| mTriggeredContentAuthorities = in.createStringArray(); |
| if (in.readInt() != 0) { |
| network = Network.CREATOR.createFromParcel(in); |
| } else { |
| network = null; |
| } |
| mStopReason = in.readInt(); |
| mInternalStopReason = in.readInt(); |
| debugStopReason = in.readString(); |
| } |
| |
| /** @hide */ |
| public void setStopReason(@StopReason int reason, int internalStopReason, |
| String debugStopReason) { |
| mStopReason = reason; |
| mInternalStopReason = internalStopReason; |
| this.debugStopReason = debugStopReason; |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(jobId); |
| dest.writePersistableBundle(extras); |
| dest.writeBundle(transientExtras); |
| if (clipData != null) { |
| dest.writeInt(1); |
| clipData.writeToParcel(dest, flags); |
| dest.writeInt(clipGrantFlags); |
| } else { |
| dest.writeInt(0); |
| } |
| dest.writeStrongBinder(callback); |
| dest.writeInt(overrideDeadlineExpired ? 1 : 0); |
| dest.writeBoolean(mIsExpedited); |
| dest.writeTypedArray(mTriggeredContentUris, flags); |
| dest.writeStringArray(mTriggeredContentAuthorities); |
| if (network != null) { |
| dest.writeInt(1); |
| network.writeToParcel(dest, flags); |
| } else { |
| dest.writeInt(0); |
| } |
| dest.writeInt(mStopReason); |
| dest.writeInt(mInternalStopReason); |
| dest.writeString(debugStopReason); |
| } |
| |
| public static final @android.annotation.NonNull 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]; |
| } |
| }; |
| } |