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 
17 package android.app.job;
18 
19 import static android.util.TimeUtils.formatDuration;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.ComponentName;
24 import android.net.Uri;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.os.PersistableBundle;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import 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  */
41 public 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      */
getMinPeriodMillis()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      */
getMinFlexMillis()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      */
getId()199     public int getId() {
200         return jobId;
201     }
202 
203     /**
204      * Bundle of extras which are returned to your application at execution time.
205      */
getExtras()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      */
getService()213     public ComponentName getService() {
214         return service;
215     }
216 
217     /** @hide */
getPriority()218     public int getPriority() {
219         return priority;
220     }
221 
222     /** @hide */
getFlags()223     public int getFlags() {
224         return flags;
225     }
226 
227     /**
228      * Whether this job needs the device to be plugged in.
229      */
isRequireCharging()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      */
isRequireDeviceIdle()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
getTriggerContentUris()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      */
getTriggerContentUpdateDelay()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      */
getTriggerContentMaxDelay()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      */
getNetworkType()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      */
getMinLatencyMillis()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      */
getMaxExecutionDelayMillis()288     public long getMaxExecutionDelayMillis() {
289         return maxExecutionDelayMillis;
290     }
291 
292     /**
293      * Track whether this job will repeat with a given period.
294      */
isPeriodic()295     public boolean isPeriodic() {
296         return isPeriodic;
297     }
298 
299     /**
300      * @return Whether or not this job should be persisted across device reboots.
301      */
isPersisted()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      */
getIntervalMillis()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      */
getFlexMillis()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      */
getInitialBackoffMillis()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      */
getBackoffPolicy()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      */
hasEarlyConstraint()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      */
hasLateConstraint()357     public boolean hasLateConstraint() {
358         return hasLateConstraint;
359     }
360 
JobInfo(Parcel in)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 
JobInfo(JobInfo.Builder b)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
describeContents()412     public int describeContents() {
413         return 0;
414     }
415 
416     @Override
writeToParcel(Parcel out, int flags)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
toString()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          */
TriggerContentUri(@onNull Uri uri, int flags)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          */
getUri()487         public Uri getUri() {
488             return mUri;
489         }
490 
491         /**
492          * Return the flags supplied for the trigger.
493          */
getFlags()494         public int getFlags() {
495             return mFlags;
496         }
497 
498         @Override
equals(Object o)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
hashCode()508         public int hashCode() {
509             return (mUri == null ? 0 : mUri.hashCode()) ^ mFlags;
510         }
511 
TriggerContentUri(Parcel in)512         private TriggerContentUri(Parcel in) {
513             mUri = Uri.CREATOR.createFromParcel(in);
514             mFlags = in.readInt();
515         }
516 
517         @Override
describeContents()518         public int describeContents() {
519             return 0;
520         }
521 
522         @Override
writeToParcel(Parcel out, int flags)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          */
Builder(int jobId, ComponentName jobService)582         public Builder(int jobId, ComponentName jobService) {
583             mJobService = jobService;
584             mJobId = jobId;
585         }
586 
587         /** @hide */
setPriority(int priority)588         public Builder setPriority(int priority) {
589             mPriority = priority;
590             return this;
591         }
592 
593         /** @hide */
setFlags(int flags)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          */
setExtras(PersistableBundle extras)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          */
setRequiredNetworkType(int networkType)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          */
setRequiresCharging(boolean requiresCharging)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          */
setRequiresDeviceIdle(boolean requiresDeviceIdle)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          */
addTriggerContentUri(@onNull TriggerContentUri uri)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          */
setTriggerContentUpdateDelay(long durationMs)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          */
setTriggerContentMaxDelay(long durationMs)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          */
setPeriodic(long intervalMillis)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          */
setPeriodic(long intervalMillis, long flexMillis)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          */
setMinimumLatency(long minLatencyMillis)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          */
setOverrideDeadline(long maxExecutionDelayMillis)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          */
setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)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          */
setPersisted(boolean isPersisted)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          */
build()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