/* * 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.annotation.TestApi; 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_UNKNOWN = -1; /** @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. /** * The user stopped the job via some UI (eg. Task Manager). * @hide */ @TestApi public static final int INTERNAL_STOP_REASON_USER_UI_STOP = JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11. /** * The app didn't respond quickly enough from JobScheduler's perspective. * @hide */ public static final int INTERNAL_STOP_REASON_ANR = JobProtoEnums.INTERNAL_STOP_REASON_ANR; // 12. /** * 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_UNKNOWN, 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, INTERNAL_STOP_REASON_USER_UI_STOP, INTERNAL_STOP_REASON_ANR, }; /** * @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"; case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop"; case INTERNAL_STOP_REASON_ANR: return "anr"; 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} and {@link JobInfo} 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, * uninstalling, or some other UI. */ 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; /** * The system's estimate of when the app will be launched changed significantly enough to * decide this job shouldn't be running right now. This will mostly apply to prefetch jobs. * * @see JobInfo#isPrefetch() * @see JobInfo.Builder#setPrefetch(boolean) */ public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15; /** @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, STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED, }) @Retention(RetentionPolicy.SOURCE) public @interface StopReason { } @UnsupportedAppUsage private final int jobId; @Nullable private final String mJobNamespace; 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 boolean mIsUserInitiated; private final Uri[] mTriggeredContentUris; private final String[] mTriggeredContentAuthorities; @Nullable private Network mNetwork; private int mStopReason = STOP_REASON_UNDEFINED; private int mInternalStopReason = INTERNAL_STOP_REASON_UNKNOWN; private String debugStopReason; // Human readable stop reason for debugging. /** @hide */ public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras, Bundle transientExtras, ClipData clipData, int clipGrantFlags, boolean overrideDeadlineExpired, boolean isExpedited, boolean isUserInitiated, 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.mIsUserInitiated = isUserInitiated; this.mTriggeredContentUris = triggeredContentUris; this.mTriggeredContentAuthorities = triggeredContentAuthorities; this.mNetwork = network; this.mJobNamespace = namespace; } /** * @return The unique id of this job, specified at creation time. */ public int getJobId() { return jobId; } /** * Get the namespace this job was placed in. * * @see JobScheduler#forNamespace(String) * @return The namespace this job was scheduled in. Will be {@code null} if there was no * explicit namespace set and this job is therefore in the default namespace. */ @Nullable public String getJobNamespace() { return mJobNamespace; } /** * @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; } /** * @return Whether this job is running as a user-initiated job or not. A job is guaranteed to * have all user-initiated job guarantees for the duration of the job execution if this returns * {@code true}. This will return {@code false} if the job wasn't requested to run as a * user-initiated job, or if it was requested to run as a user-initiated job but the app didn't * meet any of the requirements at the time of execution, such as having the * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission. * * @see JobInfo.Builder#setUserInitiated(boolean) */ public boolean isUserInitiatedJob() { return mIsUserInitiated; } /** * 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. However, * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever * run when their constraints are satisfied, therefore, the constraints will still be satisfied * for a periodic job even if the deadline has expired. */ 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. *
* 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. *
* 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. * * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, * this will return {@code null} if the app does not hold the permissions specified in * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}. * * @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 mNetwork; } /** * 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). * *
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.
* *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.
* *This example shows how to construct a JobService that will serially dequeue and * process work that is available for it:
* * {@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 not finish the overall * job -- for that to happen, you still need to call {@link #dequeueWork()} * again. * *If you are enqueueing work into a job, you must call this method for each piece * of work you process. Do not call * {@link JobService#jobFinished(JobParameters, boolean)} * or else you can lose work in your queue.
* * @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(); mJobNamespace = in.readString(); 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(); mIsUserInitiated = in.readBoolean(); mTriggeredContentUris = in.createTypedArray(Uri.CREATOR); mTriggeredContentAuthorities = in.createStringArray(); if (in.readInt() != 0) { mNetwork = Network.CREATOR.createFromParcel(in); } else { mNetwork = null; } mStopReason = in.readInt(); mInternalStopReason = in.readInt(); debugStopReason = in.readString(); } /** @hide */ public void setNetwork(@Nullable Network network) { mNetwork = network; } /** @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.writeString(mJobNamespace); 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.writeBoolean(mIsUserInitiated); dest.writeTypedArray(mTriggeredContentUris, flags); dest.writeStringArray(mTriggeredContentAuthorities); if (mNetwork != null) { dest.writeInt(1); mNetwork.writeToParcel(dest, flags); } else { dest.writeInt(0); } dest.writeInt(mStopReason); dest.writeInt(mInternalStopReason); dest.writeString(debugStopReason); } public static final @android.annotation.NonNull Creator