Home | History | Annotate | Download | only in job
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package android.app.job;
     18 
     19 import static android.util.TimeUtils.formatDuration;
     20 
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.content.ComponentName;
     24 import android.net.Uri;
     25 import android.os.Parcel;
     26 import android.os.Parcelable;
     27 import android.os.PersistableBundle;
     28 import android.util.Log;
     29 
     30 import java.util.ArrayList;
     31 import java.util.Objects;
     32 
     33 /**
     34  * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the
     35  * parameters required to schedule work against the calling application. These are constructed
     36  * using the {@link JobInfo.Builder}.
     37  * You must specify at least one sort of constraint on the JobInfo object that you are creating.
     38  * The goal here is to provide the scheduler with high-level semantics about the work you want to
     39  * accomplish. Doing otherwise with throw an exception in your app.
     40  */
     41 public class JobInfo implements Parcelable {
     42     private static String TAG = "JobInfo";
     43     /** Default. */
     44     public static final int NETWORK_TYPE_NONE = 0;
     45     /** This job requires network connectivity. */
     46     public static final int NETWORK_TYPE_ANY = 1;
     47     /** This job requires network connectivity that is unmetered. */
     48     public static final int NETWORK_TYPE_UNMETERED = 2;
     49     /** This job requires network connectivity that is not roaming. */
     50     public static final int NETWORK_TYPE_NOT_ROAMING = 3;
     51 
     52     /**
     53      * Amount of backoff a job has initially by default, in milliseconds.
     54      */
     55     public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L;  // 30 seconds.
     56 
     57     /**
     58      * Maximum backoff we allow for a job, in milliseconds.
     59      */
     60     public static final long MAX_BACKOFF_DELAY_MILLIS = 5 * 60 * 60 * 1000;  // 5 hours.
     61 
     62     /**
     63      * Linearly back-off a failed job. See
     64      * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}
     65      * retry_time(current_time, num_failures) =
     66      *     current_time + initial_backoff_millis * num_failures, num_failures >= 1
     67      */
     68     public static final int BACKOFF_POLICY_LINEAR = 0;
     69 
     70     /**
     71      * Exponentially back-off a failed job. See
     72      * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}
     73      *
     74      * retry_time(current_time, num_failures) =
     75      *     current_time + initial_backoff_millis * 2 ^ (num_failures - 1), num_failures >= 1
     76      */
     77     public static final int BACKOFF_POLICY_EXPONENTIAL = 1;
     78 
     79     /* Minimum interval for a periodic job, in milliseconds. */
     80     private static final long MIN_PERIOD_MILLIS = 15 * 60 * 1000L;   // 15 minutes
     81 
     82     /* Minimum flex for a periodic job, in milliseconds. */
     83     private static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
     84 
     85     /**
     86      * Query the minimum interval allowed for periodic scheduled jobs.  Attempting
     87      * to declare a smaller period that this when scheduling a job will result in a
     88      * job that is still periodic, but will run with this effective period.
     89      *
     90      * @return The minimum available interval for scheduling periodic jobs, in milliseconds.
     91      */
     92     public static final long getMinPeriodMillis() {
     93         return MIN_PERIOD_MILLIS;
     94     }
     95 
     96     /**
     97      * Query the minimum flex time allowed for periodic scheduled jobs.  Attempting
     98      * to declare a shorter flex time than this when scheduling such a job will
     99      * result in this amount as the effective flex time for the job.
    100      *
    101      * @return The minimum available flex time for scheduling periodic jobs, in milliseconds.
    102      */
    103     public static final long getMinFlexMillis() {
    104         return MIN_FLEX_MILLIS;
    105     }
    106 
    107     /**
    108      * Default type of backoff.
    109      * @hide
    110      */
    111     public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL;
    112 
    113     /**
    114      * Default of {@link #getPriority}.
    115      * @hide
    116      */
    117     public static final int PRIORITY_DEFAULT = 0;
    118 
    119     /**
    120      * Value of {@link #getPriority} for expedited syncs.
    121      * @hide
    122      */
    123     public static final int PRIORITY_SYNC_EXPEDITED = 10;
    124 
    125     /**
    126      * Value of {@link #getPriority} for first time initialization syncs.
    127      * @hide
    128      */
    129     public static final int PRIORITY_SYNC_INITIALIZATION = 20;
    130 
    131     /**
    132      * Value of {@link #getPriority} for a foreground app (overrides the supplied
    133      * JobInfo priority if it is smaller).
    134      * @hide
    135      */
    136     public static final int PRIORITY_FOREGROUND_APP = 30;
    137 
    138     /**
    139      * Value of {@link #getPriority} for the current top app (overrides the supplied
    140      * JobInfo priority if it is smaller).
    141      * @hide
    142      */
    143     public static final int PRIORITY_TOP_APP = 40;
    144 
    145     /**
    146      * Adjustment of {@link #getPriority} if the app has often (50% or more of the time)
    147      * been running jobs.
    148      * @hide
    149      */
    150     public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40;
    151 
    152     /**
    153      * Adjustment of {@link #getPriority} if the app has always (90% or more of the time)
    154      * been running jobs.
    155      * @hide
    156      */
    157     public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
    158 
    159     /**
    160      * Indicates that the implementation of this job will be using
    161      * {@link JobService#startForeground(int, android.app.Notification)} to run
    162      * in the foreground.
    163      * <p>
    164      * When set, the internal scheduling of this job will ignore any background
    165      * network restrictions for the requesting app. Note that this flag alone
    166      * doesn't actually place your {@link JobService} in the foreground; you
    167      * still need to post the notification yourself.
    168      * <p>
    169      * To use this flag, the caller must hold the
    170      * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL} permission.
    171      *
    172      * @hide
    173      */
    174     public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
    175 
    176     private final int jobId;
    177     private final PersistableBundle extras;
    178     private final ComponentName service;
    179     private final boolean requireCharging;
    180     private final boolean requireDeviceIdle;
    181     private final TriggerContentUri[] triggerContentUris;
    182     private final long triggerContentUpdateDelay;
    183     private final long triggerContentMaxDelay;
    184     private final boolean hasEarlyConstraint;
    185     private final boolean hasLateConstraint;
    186     private final int networkType;
    187     private final long minLatencyMillis;
    188     private final long maxExecutionDelayMillis;
    189     private final boolean isPeriodic;
    190     private final boolean isPersisted;
    191     private final long intervalMillis;
    192     private final long flexMillis;
    193     private final long initialBackoffMillis;
    194     private final int backoffPolicy;
    195     private final int priority;
    196     private final int flags;
    197 
    198     /**
    199      * Unique job id associated with this application (uid).  This is the same job ID
    200      * you supplied in the {@link Builder} constructor.
    201      */
    202     public int getId() {
    203         return jobId;
    204     }
    205 
    206     /**
    207      * Bundle of extras which are returned to your application at execution time.
    208      */
    209     public PersistableBundle getExtras() {
    210         return extras;
    211     }
    212 
    213     /**
    214      * Name of the service endpoint that will be called back into by the JobScheduler.
    215      */
    216     public ComponentName getService() {
    217         return service;
    218     }
    219 
    220     /** @hide */
    221     public int getPriority() {
    222         return priority;
    223     }
    224 
    225     /** @hide */
    226     public int getFlags() {
    227         return flags;
    228     }
    229 
    230     /**
    231      * Whether this job needs the device to be plugged in.
    232      */
    233     public boolean isRequireCharging() {
    234         return requireCharging;
    235     }
    236 
    237     /**
    238      * Whether this job needs the device to be in an Idle maintenance window.
    239      */
    240     public boolean isRequireDeviceIdle() {
    241         return requireDeviceIdle;
    242     }
    243 
    244     /**
    245      * Which content: URIs must change for the job to be scheduled.  Returns null
    246      * if there are none required.
    247      */
    248     @Nullable
    249     public TriggerContentUri[] getTriggerContentUris() {
    250         return triggerContentUris;
    251     }
    252 
    253     /**
    254      * When triggering on content URI changes, this is the delay from when a change
    255      * is detected until the job is scheduled.
    256      */
    257     public long getTriggerContentUpdateDelay() {
    258         return triggerContentUpdateDelay;
    259     }
    260 
    261     /**
    262      * When triggering on content URI changes, this is the maximum delay we will
    263      * use before scheduling the job.
    264      */
    265     public long getTriggerContentMaxDelay() {
    266         return triggerContentMaxDelay;
    267     }
    268 
    269     /**
    270      * One of {@link android.app.job.JobInfo#NETWORK_TYPE_ANY},
    271      * {@link android.app.job.JobInfo#NETWORK_TYPE_NONE},
    272      * {@link android.app.job.JobInfo#NETWORK_TYPE_UNMETERED}, or
    273      * {@link android.app.job.JobInfo#NETWORK_TYPE_NOT_ROAMING}.
    274      */
    275     public int getNetworkType() {
    276         return networkType;
    277     }
    278 
    279     /**
    280      * Set for a job that does not recur periodically, to specify a delay after which the job
    281      * will be eligible for execution. This value is not set if the job recurs periodically.
    282      */
    283     public long getMinLatencyMillis() {
    284         return minLatencyMillis;
    285     }
    286 
    287     /**
    288      * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the job recurs
    289      * periodically.
    290      */
    291     public long getMaxExecutionDelayMillis() {
    292         return maxExecutionDelayMillis;
    293     }
    294 
    295     /**
    296      * Track whether this job will repeat with a given period.
    297      */
    298     public boolean isPeriodic() {
    299         return isPeriodic;
    300     }
    301 
    302     /**
    303      * @return Whether or not this job should be persisted across device reboots.
    304      */
    305     public boolean isPersisted() {
    306         return isPersisted;
    307     }
    308 
    309     /**
    310      * Set to the interval between occurrences of this job. This value is <b>not</b> set if the
    311      * job does not recur periodically.
    312      */
    313     public long getIntervalMillis() {
    314         return intervalMillis >= getMinPeriodMillis() ? intervalMillis : getMinPeriodMillis();
    315     }
    316 
    317     /**
    318      * Flex time for this job. Only valid if this is a periodic job.  The job can
    319      * execute at any time in a window of flex length at the end of the period.
    320      */
    321     public long getFlexMillis() {
    322         long interval = getIntervalMillis();
    323         long percentClamp = 5 * interval / 100;
    324         long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
    325         return clampedFlex <= interval ? clampedFlex : interval;
    326     }
    327 
    328     /**
    329      * The amount of time the JobScheduler will wait before rescheduling a failed job. This value
    330      * will be increased depending on the backoff policy specified at job creation time. Defaults
    331      * to 5 seconds.
    332      */
    333     public long getInitialBackoffMillis() {
    334         return initialBackoffMillis;
    335     }
    336 
    337     /**
    338      * One of either {@link android.app.job.JobInfo#BACKOFF_POLICY_EXPONENTIAL}, or
    339      * {@link android.app.job.JobInfo#BACKOFF_POLICY_LINEAR}, depending on which criteria you set
    340      * when creating this job.
    341      */
    342     public int getBackoffPolicy() {
    343         return backoffPolicy;
    344     }
    345 
    346     /**
    347      * User can specify an early constraint of 0L, which is valid, so we keep track of whether the
    348      * function was called at all.
    349      * @hide
    350      */
    351     public boolean hasEarlyConstraint() {
    352         return hasEarlyConstraint;
    353     }
    354 
    355     /**
    356      * User can specify a late constraint of 0L, which is valid, so we keep track of whether the
    357      * function was called at all.
    358      * @hide
    359      */
    360     public boolean hasLateConstraint() {
    361         return hasLateConstraint;
    362     }
    363 
    364     private JobInfo(Parcel in) {
    365         jobId = in.readInt();
    366         extras = in.readPersistableBundle();
    367         service = in.readParcelable(null);
    368         requireCharging = in.readInt() == 1;
    369         requireDeviceIdle = in.readInt() == 1;
    370         triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
    371         triggerContentUpdateDelay = in.readLong();
    372         triggerContentMaxDelay = in.readLong();
    373         networkType = in.readInt();
    374         minLatencyMillis = in.readLong();
    375         maxExecutionDelayMillis = in.readLong();
    376         isPeriodic = in.readInt() == 1;
    377         isPersisted = in.readInt() == 1;
    378         intervalMillis = in.readLong();
    379         flexMillis = in.readLong();
    380         initialBackoffMillis = in.readLong();
    381         backoffPolicy = in.readInt();
    382         hasEarlyConstraint = in.readInt() == 1;
    383         hasLateConstraint = in.readInt() == 1;
    384         priority = in.readInt();
    385         flags = in.readInt();
    386     }
    387 
    388     private JobInfo(JobInfo.Builder b) {
    389         jobId = b.mJobId;
    390         extras = b.mExtras;
    391         service = b.mJobService;
    392         requireCharging = b.mRequiresCharging;
    393         requireDeviceIdle = b.mRequiresDeviceIdle;
    394         triggerContentUris = b.mTriggerContentUris != null
    395                 ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()])
    396                 : null;
    397         triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
    398         triggerContentMaxDelay = b.mTriggerContentMaxDelay;
    399         networkType = b.mNetworkType;
    400         minLatencyMillis = b.mMinLatencyMillis;
    401         maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
    402         isPeriodic = b.mIsPeriodic;
    403         isPersisted = b.mIsPersisted;
    404         intervalMillis = b.mIntervalMillis;
    405         flexMillis = b.mFlexMillis;
    406         initialBackoffMillis = b.mInitialBackoffMillis;
    407         backoffPolicy = b.mBackoffPolicy;
    408         hasEarlyConstraint = b.mHasEarlyConstraint;
    409         hasLateConstraint = b.mHasLateConstraint;
    410         priority = b.mPriority;
    411         flags = b.mFlags;
    412     }
    413 
    414     @Override
    415     public int describeContents() {
    416         return 0;
    417     }
    418 
    419     @Override
    420     public void writeToParcel(Parcel out, int flags) {
    421         out.writeInt(jobId);
    422         out.writePersistableBundle(extras);
    423         out.writeParcelable(service, flags);
    424         out.writeInt(requireCharging ? 1 : 0);
    425         out.writeInt(requireDeviceIdle ? 1 : 0);
    426         out.writeTypedArray(triggerContentUris, flags);
    427         out.writeLong(triggerContentUpdateDelay);
    428         out.writeLong(triggerContentMaxDelay);
    429         out.writeInt(networkType);
    430         out.writeLong(minLatencyMillis);
    431         out.writeLong(maxExecutionDelayMillis);
    432         out.writeInt(isPeriodic ? 1 : 0);
    433         out.writeInt(isPersisted ? 1 : 0);
    434         out.writeLong(intervalMillis);
    435         out.writeLong(flexMillis);
    436         out.writeLong(initialBackoffMillis);
    437         out.writeInt(backoffPolicy);
    438         out.writeInt(hasEarlyConstraint ? 1 : 0);
    439         out.writeInt(hasLateConstraint ? 1 : 0);
    440         out.writeInt(priority);
    441         out.writeInt(this.flags);
    442     }
    443 
    444     public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
    445         @Override
    446         public JobInfo createFromParcel(Parcel in) {
    447             return new JobInfo(in);
    448         }
    449 
    450         @Override
    451         public JobInfo[] newArray(int size) {
    452             return new JobInfo[size];
    453         }
    454     };
    455 
    456     @Override
    457     public String toString() {
    458         return "(job:" + jobId + "/" + service.flattenToShortString() + ")";
    459     }
    460 
    461     /**
    462      * Information about a content URI modification that a job would like to
    463      * trigger on.
    464      */
    465     public static final class TriggerContentUri implements Parcelable {
    466         private final Uri mUri;
    467         private final int mFlags;
    468 
    469         /**
    470          * Flag for trigger: also trigger if any descendants of the given URI change.
    471          * Corresponds to the <var>notifyForDescendants</var> of
    472          * {@link android.content.ContentResolver#registerContentObserver}.
    473          */
    474         public static final int FLAG_NOTIFY_FOR_DESCENDANTS = 1<<0;
    475 
    476         /**
    477          * Create a new trigger description.
    478          * @param uri The URI to observe.  Must be non-null.
    479          * @param flags Optional flags for the observer, either 0 or
    480          * {@link #FLAG_NOTIFY_FOR_DESCENDANTS}.
    481          */
    482         public TriggerContentUri(@NonNull Uri uri, int flags) {
    483             mUri = uri;
    484             mFlags = flags;
    485         }
    486 
    487         /**
    488          * Return the Uri this trigger was created for.
    489          */
    490         public Uri getUri() {
    491             return mUri;
    492         }
    493 
    494         /**
    495          * Return the flags supplied for the trigger.
    496          */
    497         public int getFlags() {
    498             return mFlags;
    499         }
    500 
    501         @Override
    502         public boolean equals(Object o) {
    503             if (!(o instanceof TriggerContentUri)) {
    504                 return false;
    505             }
    506             TriggerContentUri t = (TriggerContentUri) o;
    507             return Objects.equals(t.mUri, mUri) && t.mFlags == mFlags;
    508         }
    509 
    510         @Override
    511         public int hashCode() {
    512             return (mUri == null ? 0 : mUri.hashCode()) ^ mFlags;
    513         }
    514 
    515         private TriggerContentUri(Parcel in) {
    516             mUri = Uri.CREATOR.createFromParcel(in);
    517             mFlags = in.readInt();
    518         }
    519 
    520         @Override
    521         public int describeContents() {
    522             return 0;
    523         }
    524 
    525         @Override
    526         public void writeToParcel(Parcel out, int flags) {
    527             mUri.writeToParcel(out, flags);
    528             out.writeInt(mFlags);
    529         }
    530 
    531         public static final Creator<TriggerContentUri> CREATOR = new Creator<TriggerContentUri>() {
    532             @Override
    533             public TriggerContentUri createFromParcel(Parcel in) {
    534                 return new TriggerContentUri(in);
    535             }
    536 
    537             @Override
    538             public TriggerContentUri[] newArray(int size) {
    539                 return new TriggerContentUri[size];
    540             }
    541         };
    542     }
    543 
    544     /** Builder class for constructing {@link JobInfo} objects. */
    545     public static final class Builder {
    546         private final int mJobId;
    547         private final ComponentName mJobService;
    548         private PersistableBundle mExtras = PersistableBundle.EMPTY;
    549         private int mPriority = PRIORITY_DEFAULT;
    550         private int mFlags;
    551         // Requirements.
    552         private boolean mRequiresCharging;
    553         private boolean mRequiresDeviceIdle;
    554         private int mNetworkType;
    555         private ArrayList<TriggerContentUri> mTriggerContentUris;
    556         private long mTriggerContentUpdateDelay = -1;
    557         private long mTriggerContentMaxDelay = -1;
    558         private boolean mIsPersisted;
    559         // One-off parameters.
    560         private long mMinLatencyMillis;
    561         private long mMaxExecutionDelayMillis;
    562         // Periodic parameters.
    563         private boolean mIsPeriodic;
    564         private boolean mHasEarlyConstraint;
    565         private boolean mHasLateConstraint;
    566         private long mIntervalMillis;
    567         private long mFlexMillis;
    568         // Back-off parameters.
    569         private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS;
    570         private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
    571         /** Easy way to track whether the client has tried to set a back-off policy. */
    572         private boolean mBackoffPolicySet = false;
    573 
    574         /**
    575          * Initialize a new Builder to construct a {@link JobInfo}.
    576          *
    577          * @param jobId Application-provided id for this job. Subsequent calls to cancel, or
    578          * jobs created with the same jobId, will update the pre-existing job with
    579          * the same id.  This ID must be unique across all clients of the same uid
    580          * (not just the same package).  You will want to make sure this is a stable
    581          * id across app updates, so probably not based on a resource ID.
    582          * @param jobService The endpoint that you implement that will receive the callback from the
    583          * JobScheduler.
    584          */
    585         public Builder(int jobId, ComponentName jobService) {
    586             mJobService = jobService;
    587             mJobId = jobId;
    588         }
    589 
    590         /** @hide */
    591         public Builder setPriority(int priority) {
    592             mPriority = priority;
    593             return this;
    594         }
    595 
    596         /** @hide */
    597         public Builder setFlags(int flags) {
    598             mFlags = flags;
    599             return this;
    600         }
    601 
    602         /**
    603          * Set optional extras. This is persisted, so we only allow primitive types.
    604          * @param extras Bundle containing extras you want the scheduler to hold on to for you.
    605          */
    606         public Builder setExtras(PersistableBundle extras) {
    607             mExtras = extras;
    608             return this;
    609         }
    610 
    611         /**
    612          * Set some description of the kind of network type your job needs to have.
    613          * Not calling this function means the network is not necessary, as the default is
    614          * {@link #NETWORK_TYPE_NONE}.
    615          * Bear in mind that calling this function defines network as a strict requirement for your
    616          * job. If the network requested is not available your job will never run. See
    617          * {@link #setOverrideDeadline(long)} to change this behaviour.
    618          */
    619         public Builder setRequiredNetworkType(int networkType) {
    620             mNetworkType = networkType;
    621             return this;
    622         }
    623 
    624         /**
    625          * Specify that to run this job, the device needs to be plugged in. This defaults to
    626          * false.
    627          * @param requiresCharging Whether or not the device is plugged in.
    628          */
    629         public Builder setRequiresCharging(boolean requiresCharging) {
    630             mRequiresCharging = requiresCharging;
    631             return this;
    632         }
    633 
    634         /**
    635          * Specify that to run, the job needs the device to be in idle mode. This defaults to
    636          * false.
    637          * <p>Idle mode is a loose definition provided by the system, which means that the device
    638          * is not in use, and has not been in use for some time. As such, it is a good time to
    639          * perform resource heavy jobs. Bear in mind that battery usage will still be attributed
    640          * to your application, and surfaced to the user in battery stats.</p>
    641          * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
    642          *                           window.
    643          */
    644         public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
    645             mRequiresDeviceIdle = requiresDeviceIdle;
    646             return this;
    647         }
    648 
    649         /**
    650          * Add a new content: URI that will be monitored with a
    651          * {@link android.database.ContentObserver}, and will cause the job to execute if changed.
    652          * If you have any trigger content URIs associated with a job, it will not execute until
    653          * there has been a change report for one or more of them.
    654          * <p>Note that trigger URIs can not be used in combination with
    655          * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
    656          * for content changes, you need to schedule a new JobInfo observing the same URIs
    657          * before you finish execution of the JobService handling the most recent changes.</p>
    658          * <p>Because because setting this property is not compatible with periodic or
    659          * persisted jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
    660          * {@link android.app.job.JobInfo.Builder#build()} is called.</p>
    661          *
    662          * <p>The following example shows how this feature can be used to monitor for changes
    663          * in the photos on a device.</p>
    664          *
    665          * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/PhotosContentJob.java
    666          *      job}
    667          *
    668          * @param uri The content: URI to monitor.
    669          */
    670         public Builder addTriggerContentUri(@NonNull TriggerContentUri uri) {
    671             if (mTriggerContentUris == null) {
    672                 mTriggerContentUris = new ArrayList<>();
    673             }
    674             mTriggerContentUris.add(uri);
    675             return this;
    676         }
    677 
    678         /**
    679          * Set the delay (in milliseconds) from when a content change is detected until
    680          * the job is scheduled.  If there are more changes during that time, the delay
    681          * will be reset to start at the time of the most recent change.
    682          * @param durationMs Delay after most recent content change, in milliseconds.
    683          */
    684         public Builder setTriggerContentUpdateDelay(long durationMs) {
    685             mTriggerContentUpdateDelay = durationMs;
    686             return this;
    687         }
    688 
    689         /**
    690          * Set the maximum total delay (in milliseconds) that is allowed from the first
    691          * time a content change is detected until the job is scheduled.
    692          * @param durationMs Delay after initial content change, in milliseconds.
    693          */
    694         public Builder setTriggerContentMaxDelay(long durationMs) {
    695             mTriggerContentMaxDelay = durationMs;
    696             return this;
    697         }
    698 
    699         /**
    700          * Specify that this job should recur with the provided interval, not more than once per
    701          * period. You have no control over when within this interval this job will be executed,
    702          * only the guarantee that it will be executed at most once within this interval.
    703          * Setting this function on the builder with {@link #setMinimumLatency(long)} or
    704          * {@link #setOverrideDeadline(long)} will result in an error.
    705          * @param intervalMillis Millisecond interval for which this job will repeat.
    706          */
    707         public Builder setPeriodic(long intervalMillis) {
    708             return setPeriodic(intervalMillis, intervalMillis);
    709         }
    710 
    711         /**
    712          * Specify that this job should recur with the provided interval and flex. The job can
    713          * execute at any time in a window of flex length at the end of the period.
    714          * @param intervalMillis Millisecond interval for which this job will repeat. A minimum
    715          *                       value of {@link #getMinPeriodMillis()} is enforced.
    716          * @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
    717          *                   {@link #getMinFlexMillis()} or 5 percent of the period, whichever is
    718          *                   higher.
    719          */
    720         public Builder setPeriodic(long intervalMillis, long flexMillis) {
    721             mIsPeriodic = true;
    722             mIntervalMillis = intervalMillis;
    723             mFlexMillis = flexMillis;
    724             mHasEarlyConstraint = mHasLateConstraint = true;
    725             return this;
    726         }
    727 
    728         /**
    729          * Specify that this job should be delayed by the provided amount of time.
    730          * Because it doesn't make sense setting this property on a periodic job, doing so will
    731          * throw an {@link java.lang.IllegalArgumentException} when
    732          * {@link android.app.job.JobInfo.Builder#build()} is called.
    733          * @param minLatencyMillis Milliseconds before which this job will not be considered for
    734          *                         execution.
    735          */
    736         public Builder setMinimumLatency(long minLatencyMillis) {
    737             mMinLatencyMillis = minLatencyMillis;
    738             mHasEarlyConstraint = true;
    739             return this;
    740         }
    741 
    742         /**
    743          * Set deadline which is the maximum scheduling latency. The job will be run by this
    744          * deadline even if other requirements are not met. Because it doesn't make sense setting
    745          * this property on a periodic job, doing so will throw an
    746          * {@link java.lang.IllegalArgumentException} when
    747          * {@link android.app.job.JobInfo.Builder#build()} is called.
    748          */
    749         public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
    750             mMaxExecutionDelayMillis = maxExecutionDelayMillis;
    751             mHasLateConstraint = true;
    752             return this;
    753         }
    754 
    755         /**
    756          * Set up the back-off/retry policy.
    757          * This defaults to some respectable values: {30 seconds, Exponential}. We cap back-off at
    758          * 5hrs.
    759          * Note that trying to set a backoff criteria for a job with
    760          * {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
    761          * This is because back-off typically does not make sense for these types of jobs. See
    762          * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
    763          * for more description of the return value for the case of a job executing while in idle
    764          * mode.
    765          * @param initialBackoffMillis Millisecond time interval to wait initially when job has
    766          *                             failed.
    767          * @param backoffPolicy is one of {@link #BACKOFF_POLICY_LINEAR} or
    768          * {@link #BACKOFF_POLICY_EXPONENTIAL}
    769          */
    770         public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) {
    771             mBackoffPolicySet = true;
    772             mInitialBackoffMillis = initialBackoffMillis;
    773             mBackoffPolicy = backoffPolicy;
    774             return this;
    775         }
    776 
    777         /**
    778          * Set whether or not to persist this job across device reboots. This will only have an
    779          * effect if your application holds the permission
    780          * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will
    781          * be thrown.
    782          * @param isPersisted True to indicate that the job will be written to disk and loaded at
    783          *                    boot.
    784          */
    785         public Builder setPersisted(boolean isPersisted) {
    786             mIsPersisted = isPersisted;
    787             return this;
    788         }
    789 
    790         /**
    791          * @return The job object to hand to the JobScheduler. This object is immutable.
    792          */
    793         public JobInfo build() {
    794             // Allow jobs with no constraints - What am I, a database?
    795             if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging &&
    796                     !mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE &&
    797                     mTriggerContentUris == null) {
    798                 throw new IllegalArgumentException("You're trying to build a job with no " +
    799                         "constraints, this is not allowed.");
    800             }
    801             mExtras = new PersistableBundle(mExtras);  // Make our own copy.
    802             // Check that a deadline was not set on a periodic job.
    803             if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
    804                 throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
    805                         "periodic job.");
    806             }
    807             if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
    808                 throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
    809                         "periodic job");
    810             }
    811             if (mIsPeriodic && (mTriggerContentUris != null)) {
    812                 throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
    813                         "periodic job");
    814             }
    815             if (mIsPersisted && (mTriggerContentUris != null)) {
    816                 throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
    817                         "persisted job");
    818             }
    819             if (mBackoffPolicySet && mRequiresDeviceIdle) {
    820                 throw new IllegalArgumentException("An idle mode job will not respect any" +
    821                         " back-off policy, so calling setBackoffCriteria with" +
    822                         " setRequiresDeviceIdle is an error.");
    823             }
    824             JobInfo job = new JobInfo(this);
    825             if (job.isPeriodic()) {
    826                 if (job.intervalMillis != job.getIntervalMillis()) {
    827                     StringBuilder builder = new StringBuilder();
    828                     builder.append("Specified interval for ")
    829                             .append(String.valueOf(mJobId))
    830                             .append(" is ");
    831                     formatDuration(mIntervalMillis, builder);
    832                     builder.append(". Clamped to ");
    833                     formatDuration(job.getIntervalMillis(), builder);
    834                     Log.w(TAG, builder.toString());
    835                 }
    836                 if (job.flexMillis != job.getFlexMillis()) {
    837                     StringBuilder builder = new StringBuilder();
    838                     builder.append("Specified flex for ")
    839                             .append(String.valueOf(mJobId))
    840                             .append(" is ");
    841                     formatDuration(mFlexMillis, builder);
    842                     builder.append(". Clamped to ");
    843                     formatDuration(job.getFlexMillis(), builder);
    844                     Log.w(TAG, builder.toString());
    845                 }
    846             }
    847             return job;
    848         }
    849     }
    850 
    851 }
    852