/* * 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 static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.util.TimeUtils.formatDuration; import android.annotation.BytesLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.Notification; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledSince; import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ComponentName; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.Uri; import android.os.BaseBundle; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Trace; import android.util.ArraySet; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Objects; import java.util.Set; /** * 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}. * The goal here is to provide the scheduler with high-level semantics about the work you want to * accomplish. *
Prior to Android version {@link Build.VERSION_CODES#Q}, you had to specify at least one * constraint on the JobInfo object that you are creating. Otherwise, the builder would throw an * exception when building. From Android version {@link Build.VERSION_CODES#Q} and onwards, it is * valid to schedule jobs with no constraints. */ public class JobInfo implements Parcelable { private static String TAG = "JobInfo"; /** * Disallow setting a deadline (via {@link Builder#setOverrideDeadline(long)}) for prefetch * jobs ({@link Builder#setPrefetch(boolean)}. Prefetch jobs are meant to run close to the next * app launch, so there's no good reason to allow them to have deadlines. * * We don't drop or cancel any previously scheduled prefetch jobs with a deadline. * There's no way for an app to keep a perpetually scheduled prefetch job with a deadline. * Prefetch jobs with a deadline will run and apps under this restriction won't be able to * schedule new prefetch jobs with a deadline. If a job is rescheduled (by providing * {@code true} via {@link JobService#jobFinished(JobParameters, boolean)} or * {@link JobService#onStopJob(JobParameters)}'s return value),the deadline is dropped. * Periodic jobs require all constraints to be met, so there's no issue with their deadlines. * * @hide */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long DISALLOW_DEADLINES_FOR_PREFETCH_JOBS = 194532703L; /** * Whether to throw an exception when an app provides an invalid priority value via * {@link Builder#setPriority(int)}. Legacy apps may be incorrectly using the API and * so the call will silently fail for them if they continue using the API. * * @hide */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long THROW_ON_INVALID_PRIORITY_VALUE = 140852299L; /** * Require that estimated network bytes are nonnegative. * * @hide */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long REJECT_NEGATIVE_NETWORK_ESTIMATES = 253665015L; /** * Enforce a minimum time window between job latencies and deadlines. * * @hide */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Overridable // Aid in testing public static final long ENFORCE_MINIMUM_TIME_WINDOWS = 311402873L; /** * Require that minimum latencies and override deadlines are nonnegative. * * @hide */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final long REJECT_NEGATIVE_DELAYS_AND_DEADLINES = 323349338L; /** @hide */ @IntDef(prefix = { "NETWORK_TYPE_" }, value = { NETWORK_TYPE_NONE, NETWORK_TYPE_ANY, NETWORK_TYPE_UNMETERED, NETWORK_TYPE_NOT_ROAMING, NETWORK_TYPE_CELLULAR, }) @Retention(RetentionPolicy.SOURCE) public @interface NetworkType {} /** Default. */ public static final int NETWORK_TYPE_NONE = 0; /** This job requires network connectivity. */ public static final int NETWORK_TYPE_ANY = 1; /** This job requires network connectivity that is unmetered. */ public static final int NETWORK_TYPE_UNMETERED = 2; /** This job requires network connectivity that is not roaming. */ public static final int NETWORK_TYPE_NOT_ROAMING = 3; /** This job requires network connectivity that is a cellular network. */ public static final int NETWORK_TYPE_CELLULAR = 4; /** * This job requires metered connectivity such as most cellular data * networks. * * @deprecated Cellular networks may be unmetered, or Wi-Fi networks may be * metered, so this isn't a good way of selecting a specific * transport. Instead, use {@link #NETWORK_TYPE_CELLULAR} or * {@link android.net.NetworkRequest.Builder#addTransportType(int)} * if your job requires a specific network transport. */ @Deprecated public static final int NETWORK_TYPE_METERED = NETWORK_TYPE_CELLULAR; /** Sentinel value indicating that bytes are unknown. */ public static final int NETWORK_BYTES_UNKNOWN = -1; /** * Amount of backoff a job has initially by default, in milliseconds. */ public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 30 seconds. /** * Maximum backoff we allow for a job, in milliseconds. */ public static final long MAX_BACKOFF_DELAY_MILLIS = 5 * 60 * 60 * 1000; // 5 hours. /** @hide */ @IntDef(prefix = { "BACKOFF_POLICY_" }, value = { BACKOFF_POLICY_LINEAR, BACKOFF_POLICY_EXPONENTIAL, }) @Retention(RetentionPolicy.SOURCE) public @interface BackoffPolicy {} /** * Linearly back-off a failed job. See * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} * retry_time(current_time, num_failures) = * current_time + initial_backoff_millis * num_failures, num_failures >= 1 */ public static final int BACKOFF_POLICY_LINEAR = 0; /** * Exponentially back-off a failed job. See * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} * * retry_time(current_time, num_failures) = * current_time + initial_backoff_millis * 2 ^ (num_failures - 1), num_failures >= 1 */ public static final int BACKOFF_POLICY_EXPONENTIAL = 1; /* Minimum interval for a periodic job, in milliseconds. */ private static final long MIN_PERIOD_MILLIS = 15 * 60 * 1000L; // 15 minutes /* Minimum flex for a periodic job, in milliseconds. */ private static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes private static final long MIN_ALLOWED_TIME_WINDOW_MILLIS = MIN_PERIOD_MILLIS; /** * Minimum backoff interval for a job, in milliseconds * @hide */ public static final long MIN_BACKOFF_MILLIS = 10 * 1000L; // 10 seconds /** * Query the minimum interval allowed for periodic scheduled jobs. Attempting * to declare a smaller period than this when scheduling a job will result in a * job that is still periodic, but will run with this effective period. * * @return The minimum available interval for scheduling periodic jobs, in milliseconds. */ public static final long getMinPeriodMillis() { return MIN_PERIOD_MILLIS; } /** * Query the minimum flex time allowed for periodic scheduled jobs. Attempting * to declare a shorter flex time than this when scheduling such a job will * result in this amount as the effective flex time for the job. * * @return The minimum available flex time for scheduling periodic jobs, in milliseconds. */ public static final long getMinFlexMillis() { return MIN_FLEX_MILLIS; } /** * Query the minimum automatic-reschedule backoff interval permitted for jobs. * @hide */ public static final long getMinBackoffMillis() { return MIN_BACKOFF_MILLIS; } /** * Default type of backoff. * @hide */ public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL; /** * Job has minimal value to the user. The user has absolutely no expectation * or knowledge of this task and it has no bearing on the user's perception of * the app whatsoever. JobScheduler may decide to defer these tasks while * there are higher priority tasks in order to ensure there is sufficient quota * available for the higher priority tasks. * A sample task of min priority: uploading analytics */ public static final int PRIORITY_MIN = 100; /** * Low priority. The task provides some benefit to users, but is not critical * and is more of a nice-to-have. This is more important than minimum priority * jobs and will be prioritized ahead of them, but may still be deferred in lieu * of higher priority jobs. JobScheduler may decide to defer these tasks * while there are higher priority tasks in order to ensure there is sufficient * quota available for the higher priority tasks. * A sample task of low priority: prefetching data the user hasn't requested */ public static final int PRIORITY_LOW = 200; /** * Default value for all regular jobs. As noted in {@link JobScheduler}, * these jobs have a general execution time of 10 minutes. * Receives the standard job management policy. */ public static final int PRIORITY_DEFAULT = 300; /** * This task should be ordered ahead of most other tasks. It may be * deferred a little, but if it doesn't run at some point, the user may think * something is wrong. Assuming all constraints remain satisfied * (including ideal system load conditions), these jobs can have an * execution time of at least 4 minutes. Setting all of your jobs to high * priority will not be beneficial to your app and in fact may hurt its * performance in the long run. */ public static final int PRIORITY_HIGH = 400; /** * This task is critical to user experience or functionality * and should be run ahead of all other tasks. Only * {@link Builder#setExpedited(boolean) expedited jobs} and * {@link Builder#setUserInitiated(boolean) user-initiated jobs} can have this priority. *
* Example tasks of max priority: *
* When set, the internal scheduling of this job will ignore any background * network restrictions for the requesting app. Note that this flag alone * doesn't actually place your {@link JobService} in the foreground; you * still need to post the notification yourself. *
* To use this flag, the caller must hold the
* {@link android.Manifest.permission#CONNECTIVITY_INTERNAL} permission.
*
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
/**
* Allows this job to run despite doze restrictions as long as the app is in the foreground
* or on the temporary allowlist
* @hide
*/
public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1;
/**
* @hide
*/
public static final int FLAG_PREFETCH = 1 << 2;
/**
* This job needs to be exempted from the app standby throttling. Only the system (UID 1000)
* can set it. Jobs with a time constraint must not have it.
*
* @hide
*/
public static final int FLAG_EXEMPT_FROM_APP_STANDBY = 1 << 3;
/**
* Whether it's an expedited job or not.
*
* @hide
*/
public static final int FLAG_EXPEDITED = 1 << 4;
/**
* Whether it's a user initiated job or not.
*
* @hide
*/
public static final int FLAG_USER_INITIATED = 1 << 5;
/**
* @hide
*/
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
/**
* @hide
*/
public static final int CONSTRAINT_FLAG_BATTERY_NOT_LOW = 1 << 1;
/**
* @hide
*/
public static final int CONSTRAINT_FLAG_DEVICE_IDLE = 1 << 2;
/**
* @hide
*/
public static final int CONSTRAINT_FLAG_STORAGE_NOT_LOW = 1 << 3;
/** @hide */
public static final int MAX_NUM_DEBUG_TAGS = 32;
/** @hide */
public static final int MAX_DEBUG_TAG_LENGTH = 127;
/** @hide */
public static final int MAX_TRACE_TAG_LENGTH = Trace.MAX_SECTION_NAME_LEN;
@UnsupportedAppUsage
private final int jobId;
private final PersistableBundle extras;
private final Bundle transientExtras;
private final ClipData clipData;
private final int clipGrantFlags;
@UnsupportedAppUsage
private final ComponentName service;
private final int constraintFlags;
private final TriggerContentUri[] triggerContentUris;
private final long triggerContentUpdateDelay;
private final long triggerContentMaxDelay;
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
private final NetworkRequest networkRequest;
private final long networkDownloadBytes;
private final long networkUploadBytes;
private final long minimumNetworkChunkBytes;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
private final boolean isPersisted;
private final long intervalMillis;
private final long flexMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
private final int mBias;
@Priority
private final int mPriority;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final int flags;
private final ArraySet
* Tags have the following requirements:
* Because setting this property is not compatible with persisted
* jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called. The main purpose of providing a ClipData is to allow granting of
* URI permissions for data associated with the clip. The exact kind
* of permission grant to perform is specified through grantFlags.
*
* If the ClipData contains items that are Intents, any
* grant flags in those Intents will be ignored. Only flags provided as an argument
* to this method are respected, and will be applied to all Uri or
* Intent items in the clip (or sub-items of the clip).
*
* Because setting this property is not compatible with persisted
* jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
* If your job doesn't need a network connection, you don't need to call
* this method, as the default value is {@link #NETWORK_TYPE_NONE}.
*
* Calling this method 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 behavior.
* Calling this method will override any requirements previously defined
* by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
* want to call one of these methods.
*
* Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
* an app must hold the
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permission to
* schedule a job that requires a network.
*
* Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
* {@link JobScheduler} may try to shift the execution of jobs requiring
* {@link #NETWORK_TYPE_ANY} to when there is access to an un-metered network.
*
*
* When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
* specific network returned by {@link JobParameters#getNetwork()},
* otherwise you'll use the default network which may not meet this
* constraint.
*
* @see #setRequiredNetwork(NetworkRequest)
* @see JobInfo#getNetworkType()
* @see JobParameters#getNetwork()
*/
public Builder setRequiredNetworkType(@NetworkType int networkType) {
if (networkType == NETWORK_TYPE_NONE) {
return setRequiredNetwork(null);
} else {
final NetworkRequest.Builder builder = new NetworkRequest.Builder();
// All types require validated Internet
builder.addCapability(NET_CAPABILITY_INTERNET);
builder.addCapability(NET_CAPABILITY_VALIDATED);
builder.removeCapability(NET_CAPABILITY_NOT_VPN);
builder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
if (networkType == NETWORK_TYPE_ANY) {
// No other capabilities
} else if (networkType == NETWORK_TYPE_UNMETERED) {
builder.addCapability(NET_CAPABILITY_NOT_METERED);
} else if (networkType == NETWORK_TYPE_NOT_ROAMING) {
builder.addCapability(NET_CAPABILITY_NOT_ROAMING);
} else if (networkType == NETWORK_TYPE_CELLULAR) {
builder.addTransportType(TRANSPORT_CELLULAR);
}
return setRequiredNetwork(builder.build());
}
}
/**
* Set detailed description of the kind of network your job requires.
*
* If your job doesn't need a network connection, you don't need to call
* this method, as the default is {@code null}.
*
* Calling this method 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 behavior.
* Calling this method will override any requirements previously defined
* by {@link #setRequiredNetworkType(int)}; you typically only want to
* call one of these methods.
*
* When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
* specific network returned by {@link JobParameters#getNetwork()},
* otherwise you'll use the default network which may not meet this
* constraint.
*
* Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
* an app must hold the
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permission to
* schedule a job that requires a network.
*
* @param networkRequest The detailed description of the kind of network
* this job requires, or {@code null} if no specific kind of
* network is required. Defining a {@link NetworkSpecifier}
* is only supported for jobs that aren't persisted.
* @see #setRequiredNetworkType(int)
* @see JobInfo#getRequiredNetwork()
* @see JobParameters#getNetwork()
*/
public Builder setRequiredNetwork(@Nullable NetworkRequest networkRequest) {
mNetworkRequest = networkRequest;
return this;
}
/**
* Set the estimated size of network traffic that will be performed by
* this job, in bytes.
*
* Apps are encouraged to provide values that are as accurate as
* possible, but when the exact size isn't available, an
* order-of-magnitude estimate can be provided instead. Here are some
* specific examples:
*
* The values provided here only reflect the traffic that will be
* performed by the base job; if you're using {@link JobWorkItem} then
* you also need to define the network traffic used by each work item
* when constructing them.
*
*
* Prior to Android version {@link Build.VERSION_CODES#TIRAMISU}, JobScheduler used the
* estimated transfer numbers in a similar fashion to
* {@link #setMinimumNetworkChunkBytes(long)} (to estimate if the work would complete
* within the time available to job). In other words, JobScheduler treated the transfer as
* all-or-nothing. Starting from Android version {@link Build.VERSION_CODES#TIRAMISU},
* JobScheduler will only use the estimated transfer numbers in this manner if minimum
* chunk sizes have not been provided via {@link #setMinimumNetworkChunkBytes(long)}.
*
* @param downloadBytes The estimated size of network traffic that will
* be downloaded by this job, in bytes.
* @param uploadBytes The estimated size of network traffic that will be
* uploaded by this job, in bytes.
* @see JobInfo#getEstimatedNetworkDownloadBytes()
* @see JobInfo#getEstimatedNetworkUploadBytes()
* @see JobWorkItem#JobWorkItem(android.content.Intent, long, long)
*/
// TODO(b/255371817): update documentation to reflect how this data will be used
public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
@BytesLong long uploadBytes) {
mNetworkDownloadBytes = downloadBytes;
mNetworkUploadBytes = uploadBytes;
return this;
}
/**
* Set the minimum size of non-resumable network traffic this job requires, in bytes. When
* the upload or download can be easily paused and resumed, use this to set the smallest
* size that must be transmitted between start and stop events to be considered successful.
* If the transfer cannot be paused and resumed, then this should be the sum of the values
* provided to {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)}.
*
*
* Apps are encouraged to provide values that are as accurate as possible since JobScheduler
* will try to run the job at a time when at least the minimum chunk can be transmitted to
* reduce the amount of repetitive data that's transferred. Jobs that cannot provide
* reasonable estimates should use the sentinel value {@link JobInfo#NETWORK_BYTES_UNKNOWN}.
*
*
* The values provided here only reflect the minimum non-resumable traffic that will be
* performed by the base job; if you're using {@link JobWorkItem} then
* you also need to define the network traffic used by each work item
* when constructing them.
*
* @param chunkSizeBytes The smallest piece of data that cannot be easily paused and
* resumed, in bytes.
* @see JobInfo#getMinimumNetworkChunkBytes()
* @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long)
*/
@NonNull
public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) {
if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) {
throw new IllegalArgumentException("Minimum chunk size must be positive");
}
mMinimumNetworkChunkBytes = chunkSizeBytes;
return this;
}
/**
* Specify that to run this job, the device must be charging (or be a
* non-battery-powered device connected to permanent power, such as Android TV
* devices). This defaults to {@code false}. Setting this to {@code false} DOES NOT
* mean the job will only run when the device is not charging.
*
* For purposes of running jobs, a battery-powered device
* "charging" is not quite the same as simply being connected to power. If the
* device is so busy that the battery is draining despite a power connection, jobs
* with this constraint will not run. This can happen during some
* common use cases such as video chat, particularly if the device is plugged in
* to USB rather than to wall power.
*
* @param requiresCharging Pass {@code true} to require that the device be
* charging in order to run the job.
* @see JobInfo#isRequireCharging()
*/
public Builder setRequiresCharging(boolean requiresCharging) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING)
| (requiresCharging ? CONSTRAINT_FLAG_CHARGING : 0);
return this;
}
/**
* Specify that to run this job, the device's battery level must not be low.
* This defaults to false. If true, the job will only run when the battery level
* is not low, which is generally the point where the user is given a "low battery"
* warning. Setting this to {@code false} DOES NOT mean the job will only run
* when the battery is low.
*
* @param batteryNotLow Whether or not the device's battery level must not be low.
* @see JobInfo#isRequireBatteryNotLow()
*/
public Builder setRequiresBatteryNotLow(boolean batteryNotLow) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_BATTERY_NOT_LOW)
| (batteryNotLow ? CONSTRAINT_FLAG_BATTERY_NOT_LOW : 0);
return this;
}
/**
* When set {@code true}, ensure that this job will not run if the device is in active use.
* The default state is {@code false}: that is, the for the job to be runnable even when
* someone is interacting with the device. Setting this to {@code false} DOES NOT
* mean the job will only run when the device is not idle.
*
* This state is a loose definition provided by the system. In general, it means that
* the device is not currently being used interactively, 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. Despite the similar naming, this job constraint is not
* related to the system's "device idle" or "doze" states. This constraint only
* determines whether a job is allowed to run while the device is directly in use.
*
* @param requiresDeviceIdle Pass {@code true} to prevent the job from running
* while the device is being used interactively.
* @see JobInfo#isRequireDeviceIdle()
*/
public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE)
| (requiresDeviceIdle ? CONSTRAINT_FLAG_DEVICE_IDLE : 0);
return this;
}
/**
* Specify that to run this job, the device's available storage must not be low.
* This defaults to false. If true, the job will only run when the device is not
* in a low storage state, which is generally the point where the user is given a
* "low storage" warning.
* @param storageNotLow Whether or not the device's available storage must not be low.
* @see JobInfo#isRequireStorageNotLow()
*/
public Builder setRequiresStorageNotLow(boolean storageNotLow) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_STORAGE_NOT_LOW)
| (storageNotLow ? CONSTRAINT_FLAG_STORAGE_NOT_LOW : 0);
return this;
}
/**
* Add a new content: URI that will be monitored with a
* {@link android.database.ContentObserver}, and will cause the job to execute if changed.
* If you have any trigger content URIs associated with a job, it will not execute until
* there has been a change report for one or more of them.
*
* Note that trigger URIs can not be used in combination with
* {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}. To continually monitor
* for content changes, you need to schedule a new JobInfo using the same job ID and
* observing the same URIs in place of calling
* {@link JobService#jobFinished(JobParameters, boolean)}. Remember that
* {@link JobScheduler#schedule(JobInfo)} stops a running job if it uses the same job ID,
* so only call it after you've finished processing the most recent changes (in other words,
* call {@link JobScheduler#schedule(JobInfo)} where you would have normally called
* {@link JobService#jobFinished(JobParameters, boolean)}.
* Following this pattern will ensure you do not lose any content changes: while your
* job is running, the system will continue monitoring for content changes, and propagate
* any changes it sees over to the next job you schedule, so you do not have to worry
* about missing new changes. Scheduling the new job
* before or during processing will cause the current job to be stopped (as described in
* {@link JobScheduler#schedule(JobInfo)}), meaning the wakelock will be released for the
* current job and your app process may be killed since it will no longer be in a valid
* component lifecycle.
* Since {@link JobScheduler#schedule(JobInfo)} stops the current job, you do not
* need to call {@link JobService#jobFinished(JobParameters, boolean)} if you call
* {@link JobScheduler#schedule(JobInfo)} using the same job ID as the
* currently running job. Because setting this property is not compatible with periodic or
* persisted jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called. The following example shows how this feature can be used to monitor for changes
* in the photos on a device.
* 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.
*
* Negative latencies also don't make sense for a job and are indicative of an error,
* so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
* setting a negative deadline will result in
* {@link android.app.job.JobInfo.Builder#build()} throwing an
* {@link java.lang.IllegalArgumentException}.
*
* @param minLatencyMillis Milliseconds before which this job will not be considered for
* execution.
* @see JobInfo#getMinLatencyMillis()
*/
public Builder setMinimumLatency(long minLatencyMillis) {
mMinLatencyMillis = minLatencyMillis;
mHasEarlyConstraint = true;
return this;
}
/**
* Set a deadline after which all other functional requested constraints will be ignored.
* After the deadline has passed, the job can run even if other requirements (including
* a delay set through {@link #setMinimumLatency(long)}) are not met.
* {@link JobParameters#isOverrideDeadlineExpired()} will return {@code true} if the job's
* deadline has passed. The job's execution may be delayed beyond the set deadline by
* other factors such as Doze mode and system health signals.
*
*
* 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.
*
*
* Negative deadlines also don't make sense for a job and are indicative of an error,
* so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
* setting a negative deadline will result in
* {@link android.app.job.JobInfo.Builder#build()} throwing an
* {@link java.lang.IllegalArgumentException}.
*
*
* Since a job will run once the deadline has passed regardless of the status of other
* constraints, setting a deadline of 0 (or a {@link #setMinimumLatency(long) delay} equal
* to the deadline) with other constraints makes those constraints
* meaningless when it comes to execution decisions. Since doing so is indicative of an
* error in the logic, starting in Android version
* {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, jobs with extremely short
* time windows will fail to build. Time windows are
* defined as the time between a job's {@link #setMinimumLatency(long) minimum latency}
* and its deadline. If the minimum latency is not set, it is assumed to be 0.
*
* Work that must happen immediately should use {@link #setExpedited(boolean)} or
* {@link #setUserInitiated(boolean)} in the appropriate manner.
*
*
* This API aimed to guarantee execution of the job by the deadline only on Android version
* {@link android.os.Build.VERSION_CODES#LOLLIPOP}. That aim and guarantee has not existed
* since {@link android.os.Build.VERSION_CODES#M}.
*
* @see JobInfo#getMaxExecutionDelayMillis()
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
mMaxExecutionDelayMillis = maxExecutionDelayMillis;
mHasLateConstraint = true;
return this;
}
/**
* Set up the back-off/retry policy.
* This defaults to some respectable values: {30 seconds, Exponential}. We cap back-off at
* 5hrs.
*
* 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.
* @see JobInfo#getInitialBackoffMillis()
* @see JobInfo#getBackoffPolicy()
*/
public Builder setBackoffCriteria(long initialBackoffMillis,
@BackoffPolicy int backoffPolicy) {
final long minBackoff = getMinBackoffMillis();
if (initialBackoffMillis < minBackoff) {
Log.w(TAG, "Requested backoff " + formatDuration(initialBackoffMillis) + " for job "
+ mJobId + " is too small; raising to " + formatDuration(minBackoff));
initialBackoffMillis = minBackoff;
}
mBackoffPolicySet = true;
mInitialBackoffMillis = initialBackoffMillis;
mBackoffPolicy = backoffPolicy;
return this;
}
/**
* Setting this to true indicates that this job is important and needs to run as soon as
* possible with stronger guarantees than regular jobs. These "expedited" jobs will:
*
* Expedited jobs are given {@link #PRIORITY_MAX} by default.
*
*
* Since these jobs have stronger guarantees than regular jobs, they will be subject to
* stricter quotas. As long as an app has available expedited quota, jobs scheduled with
* this set to true will run with these guarantees. If an app has run out of available
* expedited quota, any pending expedited jobs will run as regular jobs.
* {@link JobParameters#isExpeditedJob()} can be used to know whether the executing job
* has expedited guarantees or not. In addition, {@link JobScheduler#schedule(JobInfo)}
* will immediately return {@link JobScheduler#RESULT_FAILURE} if the app does not have
* available quota (and the job will not be successfully scheduled).
*
*
* Expedited job quota will replenish over time and as the user interacts with the app,
* so you should not have to worry about running out of quota because of processing from
* frequent user engagement.
*
*
* Expedited jobs may only set network, storage-not-low, and persistence constraints.
* No other constraints are allowed.
*
*
* Assuming all constraints remain satisfied (including ideal system load conditions),
* expedited jobs can have an execution time of at least 1 minute. If your
* app has remaining expedited job quota, then the expedited job may potentially run
* longer until remaining quota is used up. Just like with regular jobs, quota is not
* consumed while the app is on top and visible to the user.
*
*
* Note: Even though expedited jobs are meant to run as soon as possible, they may be
* deferred if the system is under heavy load or requested constraints are not satisfied.
* This delay may be true for expedited jobs of the foreground app on Android version
* {@link Build.VERSION_CODES#S}, but starting from Android version
* {@link Build.VERSION_CODES#TIRAMISU}, expedited jobs for the foreground app are
* guaranteed to be started before {@link JobScheduler#schedule(JobInfo)} returns (assuming
* all requested constraints are satisfied), similar to foreground services.
*
* @see JobInfo#isExpedited()
*/
@NonNull
public Builder setExpedited(boolean expedited) {
if (expedited) {
mFlags |= FLAG_EXPEDITED;
if (mPriority == PRIORITY_DEFAULT) {
// The default priority for EJs is MAX, but only change this if .setPriority()
// hasn't been called yet.
mPriority = PRIORITY_MAX;
}
} else {
if (mPriority == PRIORITY_MAX && (mFlags & FLAG_EXPEDITED) != 0) {
// Reset the priority for the job, but only change this if .setPriority()
// hasn't been called yet.
mPriority = PRIORITY_DEFAULT;
}
mFlags &= (~FLAG_EXPEDITED);
}
return this;
}
/**
* Indicates that this job is being scheduled to fulfill an explicit user request.
* As such, user-initiated jobs can only be scheduled when the app is in the foreground
* or in a state where launching an activity is allowed, as defined
*
* here. Attempting to schedule one outside of these conditions will return a
* {@link JobScheduler#RESULT_FAILURE}.
*
*
* This should NOT be used for automatic features.
*
*
* All user-initiated jobs must have an associated notification, set via
* {@link JobService#setNotification(JobParameters, int, Notification, int)}, and will be
* shown in the Task Manager when running. These jobs cannot be rescheduled by the app
* if the user stops the job via system provided affordance (such as the Task Manager).
* Thus, it is best practice and recommended to provide action buttons in the
* associated notification to allow the user to stop the job gracefully
* and allow for rescheduling.
*
*
* If the app doesn't hold the {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS}
* permission when scheduling a user-initiated job, JobScheduler will throw a
* {@link SecurityException}.
*
*
* In {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, user-initiated jobs can only
* be used for network data transfers. As such, they must specify a required network via
* {@link #setRequiredNetwork(NetworkRequest)} or {@link #setRequiredNetworkType(int)}.
*
*
* These jobs will not be subject to quotas and will be started immediately once scheduled
* if all constraints are met and the device system health allows for additional tasks.
* They are also given {@link #PRIORITY_MAX} by default, and the priority cannot be changed.
*
* @see JobInfo#isUserInitiated()
*/
@RequiresPermission(android.Manifest.permission.RUN_USER_INITIATED_JOBS)
@NonNull
public Builder setUserInitiated(boolean userInitiated) {
if (userInitiated) {
mFlags |= FLAG_USER_INITIATED;
if (mPriority == PRIORITY_DEFAULT) {
// The default priority for UIJs is MAX, but only change this if .setPriority()
// hasn't been called yet.
mPriority = PRIORITY_MAX;
}
} else {
if (mPriority == PRIORITY_MAX && (mFlags & FLAG_USER_INITIATED) != 0) {
// Reset the priority for the job, but only change this if .setPriority()
// hasn't been called yet.
mPriority = PRIORITY_DEFAULT;
}
mFlags &= (~FLAG_USER_INITIATED);
}
return this;
}
/**
* Setting this to true indicates that this job is important while the scheduling app
* is in the foreground or on the temporary allowlist for background restrictions.
* This means that the system will relax doze restrictions on this job during this time.
*
* Apps should use this flag only for short jobs that are essential for the app to function
* properly in the foreground.
*
* Note that once the scheduling app is no longer allowlisted from background restrictions
* and in the background, or the job failed due to unsatisfied constraints,
* this job should be expected to behave like other jobs without this flag.
*
*
* Jobs marked as important-while-foreground are given {@link #PRIORITY_HIGH} by default.
*
* @param importantWhileForeground whether to relax doze restrictions for this job when the
* app is in the foreground. False by default.
* @see JobInfo#isImportantWhileForeground()
* @deprecated Use {@link #setExpedited(boolean)} instead.
*/
@Deprecated
public Builder setImportantWhileForeground(boolean importantWhileForeground) {
if (importantWhileForeground) {
mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
if (mPriority == PRIORITY_DEFAULT) {
// The default priority for important-while-foreground is HIGH, but only change
// this if .setPriority() hasn't been called yet.
mPriority = PRIORITY_HIGH;
}
} else {
if (mPriority == PRIORITY_HIGH
&& (mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
// Reset the priority for the job, but only change this if .setPriority()
// hasn't been called yet.
mPriority = PRIORITY_DEFAULT;
}
mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND);
}
return this;
}
/**
* Setting this to true indicates that this job is designed to prefetch
* content that will make a material improvement to the experience of
* the specific user of this device. For example, fetching top headlines
* of interest to the current user.
*
* Apps targeting Android version {@link Build.VERSION_CODES#TIRAMISU} or later are
* not allowed to have deadlines (set via {@link #setOverrideDeadline(long)} on their
* prefetch jobs.
*
* The system may use this signal to relax the network 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. The system
* may also use this signal in combination with end user usage patterns
* to ensure data is prefetched before the user launches your app.
* @see JobInfo#isPrefetch()
*/
public Builder setPrefetch(boolean prefetch) {
if (prefetch) {
mFlags |= FLAG_PREFETCH;
} else {
mFlags &= (~FLAG_PREFETCH);
}
return this;
}
/**
* Set whether or not to persist this job across device reboots.
*
* @param isPersisted True to indicate that the job will be written to
* disk and loaded at boot.
* @see JobInfo#isPersisted()
*/
@RequiresPermission(android.Manifest.permission.RECEIVE_BOOT_COMPLETED)
public Builder setPersisted(boolean isPersisted) {
mIsPersisted = isPersisted;
return this;
}
/**
* Set a tag that will be used in {@link android.os.Trace traces}.
* Since this is a trace tag, it must follow the rules set in
* {@link android.os.Trace#beginSection(String)}, such as it cannot be more
* than 127 Unicode code units.
* Additionally, since leading and trailing whitespace can lead to hard-to-debug issues,
* they will be {@link String#trim() trimmed}.
* An empty String (after trimming) is not allowed.
* @param traceTag The tag to use in traces.
* @return This object for method chaining
*/
@FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
@NonNull
public Builder setTraceTag(@Nullable String traceTag) {
mTraceTag = validateTraceTag(traceTag);
return this;
}
/**
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS),
Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES),
Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS),
Compatibility.isChangeEnabled(REJECT_NEGATIVE_DELAYS_AND_DEADLINES));
}
/** @hide */
public JobInfo build(boolean disallowPrefetchDeadlines,
boolean rejectNegativeNetworkEstimates,
boolean enforceMinimumTimeWindows,
boolean rejectNegativeDelaysAndDeadlines) {
// This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
// check that would ideally be phased out instead.
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
JobInfo jobInfo = new JobInfo(this);
jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates,
enforceMinimumTimeWindows, rejectNegativeDelaysAndDeadlines);
return jobInfo;
}
/**
* @hide
*/
public String summarize() {
final String service = (mJobService != null)
? mJobService.flattenToShortString()
: "null";
return "JobInfo.Builder{job:" + mJobId + "/" + service + "}";
}
}
/**
* @hide
*/
public final void enforceValidity(boolean disallowPrefetchDeadlines,
boolean rejectNegativeNetworkEstimates,
boolean enforceMinimumTimeWindows,
boolean rejectNegativeDelaysAndDeadlines) {
// Check that network estimates require network type and are reasonable values.
if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
&& networkRequest == null) {
throw new IllegalArgumentException(
"Can't provide estimated network usage without requiring a network");
}
if (networkRequest != null && rejectNegativeNetworkEstimates) {
if (networkUploadBytes != NETWORK_BYTES_UNKNOWN && networkUploadBytes < 0) {
throw new IllegalArgumentException(
"Invalid network upload bytes: " + networkUploadBytes);
}
if (networkDownloadBytes != NETWORK_BYTES_UNKNOWN && networkDownloadBytes < 0) {
throw new IllegalArgumentException(
"Invalid network download bytes: " + networkDownloadBytes);
}
}
final long estimatedTransfer;
if (networkUploadBytes == NETWORK_BYTES_UNKNOWN) {
estimatedTransfer = networkDownloadBytes;
} else {
estimatedTransfer = networkUploadBytes
+ (networkDownloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : networkDownloadBytes);
}
if (minimumNetworkChunkBytes != NETWORK_BYTES_UNKNOWN
&& estimatedTransfer != NETWORK_BYTES_UNKNOWN
&& minimumNetworkChunkBytes > estimatedTransfer) {
throw new IllegalArgumentException(
"Minimum chunk size can't be greater than estimated network usage");
}
if (minimumNetworkChunkBytes != NETWORK_BYTES_UNKNOWN && minimumNetworkChunkBytes <= 0) {
throw new IllegalArgumentException("Minimum chunk size must be positive");
}
if (rejectNegativeDelaysAndDeadlines) {
if (minLatencyMillis < 0) {
throw new IllegalArgumentException(
"Minimum latency is negative: " + minLatencyMillis);
}
if (maxExecutionDelayMillis < 0) {
throw new IllegalArgumentException(
"Override deadline is negative: " + maxExecutionDelayMillis);
}
}
final boolean hasDeadline = maxExecutionDelayMillis != 0L;
// Check that a deadline was not set on a periodic job.
if (isPeriodic) {
if (hasDeadline) {
throw new IllegalArgumentException(
"Can't call setOverrideDeadline() on a periodic job.");
}
if (minLatencyMillis != 0L) {
throw new IllegalArgumentException(
"Can't call setMinimumLatency() on a periodic job");
}
if (triggerContentUris != null) {
throw new IllegalArgumentException(
"Can't call addTriggerContentUri() on a periodic job");
}
}
// Prefetch jobs should not have deadlines
if (disallowPrefetchDeadlines && hasDeadline && (flags & FLAG_PREFETCH) != 0) {
throw new IllegalArgumentException(
"Can't call setOverrideDeadline() on a prefetch job.");
}
if (isPersisted) {
// We can't serialize network specifiers
if (networkRequest != null
&& networkRequest.getNetworkSpecifier() != null) {
throw new IllegalArgumentException(
"Network specifiers aren't supported for persistent jobs");
}
if (triggerContentUris != null) {
throw new IllegalArgumentException(
"Can't call addTriggerContentUri() on a persisted job");
}
if (!transientExtras.isEmpty()) {
throw new IllegalArgumentException(
"Can't call setTransientExtras() on a persisted job");
}
if (clipData != null) {
throw new IllegalArgumentException(
"Can't call setClipData() on a persisted job");
}
}
if ((flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
if (hasEarlyConstraint) {
throw new IllegalArgumentException(
"An important while foreground job cannot have a time delay");
}
if (mPriority != PRIORITY_HIGH && mPriority != PRIORITY_DEFAULT) {
throw new IllegalArgumentException(
"An important while foreground job must be high or default priority."
+ " Don't mark unimportant tasks as important while foreground.");
}
}
final boolean isExpedited = (flags & FLAG_EXPEDITED) != 0;
final boolean isUserInitiated = (flags & FLAG_USER_INITIATED) != 0;
switch (mPriority) {
case PRIORITY_MAX:
if (!(isExpedited || isUserInitiated)) {
throw new IllegalArgumentException(
"Only expedited or user-initiated jobs can have max priority");
}
break;
case PRIORITY_HIGH:
if ((flags & FLAG_PREFETCH) != 0) {
throw new IllegalArgumentException("Prefetch jobs cannot be high priority");
}
if (isPeriodic) {
throw new IllegalArgumentException("Periodic jobs cannot be high priority");
}
break;
case PRIORITY_DEFAULT:
case PRIORITY_LOW:
case PRIORITY_MIN:
break;
default:
throw new IllegalArgumentException("Invalid priority level provided: " + mPriority);
}
final boolean hasFunctionalConstraint = networkRequest != null
|| constraintFlags != 0
|| (triggerContentUris != null && triggerContentUris.length > 0);
if (hasLateConstraint && !isPeriodic) {
if (!hasFunctionalConstraint) {
Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'"
+ " has a deadline with no functional constraints."
+ " The deadline won't improve job execution latency."
+ " Consider removing the deadline.");
} else {
final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0;
if (maxExecutionDelayMillis - windowStart < MIN_ALLOWED_TIME_WINDOW_MILLIS) {
if (enforceMinimumTimeWindows
&& Flags.enforceMinimumTimeWindows()) {
throw new IllegalArgumentException("Time window too short. Constraints"
+ " unlikely to be satisfied. Increase deadline to a reasonable"
+ " duration."
+ " Job '" + service.flattenToShortString() + "#" + jobId + "'"
+ " has delay=" + windowStart
+ ", deadline=" + maxExecutionDelayMillis);
} else {
Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'"
+ " has a deadline with functional constraints and an extremely"
+ " short time window of "
+ (maxExecutionDelayMillis - windowStart) + " ms"
+ " (delay=" + windowStart
+ ", deadline=" + maxExecutionDelayMillis + ")."
+ " The functional constraints are not likely to be satisfied when"
+ " the job runs.");
}
}
}
}
if (isExpedited) {
if (hasEarlyConstraint) {
throw new IllegalArgumentException("An expedited job cannot have a time delay");
}
if (hasLateConstraint) {
throw new IllegalArgumentException("An expedited job cannot have a deadline");
}
if (isPeriodic) {
throw new IllegalArgumentException("An expedited job cannot be periodic");
}
if (isUserInitiated) {
throw new IllegalArgumentException("An expedited job cannot be user-initiated");
}
if (mPriority != PRIORITY_MAX && mPriority != PRIORITY_HIGH) {
throw new IllegalArgumentException(
"An expedited job must be high or max priority. Don't use expedited jobs"
+ " for unimportant tasks.");
}
if ((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
|| (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY)) != 0) {
throw new IllegalArgumentException(
"An expedited job can only have network and storage-not-low constraints");
}
if (triggerContentUris != null && triggerContentUris.length > 0) {
throw new IllegalArgumentException(
"Can't call addTriggerContentUri() on an expedited job");
}
}
if (isUserInitiated) {
if (hasEarlyConstraint) {
throw new IllegalArgumentException("A user-initiated job cannot have a time delay");
}
if (hasLateConstraint) {
throw new IllegalArgumentException("A user-initiated job cannot have a deadline");
}
if (isPeriodic) {
throw new IllegalArgumentException("A user-initiated job cannot be periodic");
}
if ((flags & FLAG_PREFETCH) != 0) {
throw new IllegalArgumentException(
"A user-initiated job cannot also be a prefetch job");
}
if (mPriority != PRIORITY_MAX) {
throw new IllegalArgumentException("A user-initiated job must be max priority.");
}
if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException(
"A user-initiated job cannot have a device-idle constraint");
}
if (triggerContentUris != null && triggerContentUris.length > 0) {
throw new IllegalArgumentException(
"Can't call addTriggerContentUri() on a user-initiated job");
}
// UIDTs
if (networkRequest == null) {
throw new IllegalArgumentException(
"A user-initiated data transfer job must specify a valid network type");
}
}
if (mDebugTags.size() > MAX_NUM_DEBUG_TAGS) {
throw new IllegalArgumentException(
"Can't have more than " + MAX_NUM_DEBUG_TAGS + " tags");
}
final ArraySet
*
*
* @param tag A debug tag that helps describe what the job is for.
* @return This object for method chaining
*/
@FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
@NonNull
public Builder addDebugTag(@NonNull String tag) {
mDebugTags.add(validateDebugTag(tag));
return this;
}
/** @hide */
@NonNull
public void addDebugTags(@NonNull Set
*
* Note that the system may choose to delay jobs with large network
* usage estimates when the device has a poor network connection, in
* order to save battery and possible network costs.
* Starting from Android version {@link Build.VERSION_CODES#S}, JobScheduler may attempt
* to run large jobs when the device is charging and on an unmetered network, even if the
* network is slow. This gives large jobs an opportunity to make forward progress, even if
* they risk timing out.
*
*
*
*