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