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