[go: nahoru, domu]

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