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