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