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