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