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