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