• 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.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.content.ClipData;
26 import android.content.ComponentName;
27 import android.net.Uri;
28 import android.os.BaseBundle;
29 import android.os.Bundle;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.os.PersistableBundle;
33 import android.util.Log;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Objects;
40 
41 /**
42  * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the
43  * parameters required to schedule work against the calling application. These are constructed
44  * using the {@link JobInfo.Builder}.
45  * You must specify at least one sort of constraint on the JobInfo object that you are creating.
46  * The goal here is to provide the scheduler with high-level semantics about the work you want to
47  * accomplish. Doing otherwise with throw an exception in your app.
48  */
49 public class JobInfo implements Parcelable {
50     private static String TAG = "JobInfo";
51 
52     /** @hide */
53     @IntDef(prefix = { "NETWORK_TYPE_" }, value = {
54             NETWORK_TYPE_NONE,
55             NETWORK_TYPE_ANY,
56             NETWORK_TYPE_UNMETERED,
57             NETWORK_TYPE_NOT_ROAMING,
58             NETWORK_TYPE_METERED,
59     })
60     @Retention(RetentionPolicy.SOURCE)
61     public @interface NetworkType {}
62 
63     /** Default. */
64     public static final int NETWORK_TYPE_NONE = 0;
65     /** This job requires network connectivity. */
66     public static final int NETWORK_TYPE_ANY = 1;
67     /** This job requires network connectivity that is unmetered. */
68     public static final int NETWORK_TYPE_UNMETERED = 2;
69     /** This job requires network connectivity that is not roaming. */
70     public static final int NETWORK_TYPE_NOT_ROAMING = 3;
71     /** This job requires metered connectivity such as most cellular data networks. */
72     public static final int NETWORK_TYPE_METERED = 4;
73 
74     /**
75      * Amount of backoff a job has initially by default, in milliseconds.
76      */
77     public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L;  // 30 seconds.
78 
79     /**
80      * Maximum backoff we allow for a job, in milliseconds.
81      */
82     public static final long MAX_BACKOFF_DELAY_MILLIS = 5 * 60 * 60 * 1000;  // 5 hours.
83 
84     /** @hide */
85     @IntDef(prefix = { "BACKOFF_POLICY_" }, value = {
86             BACKOFF_POLICY_LINEAR,
87             BACKOFF_POLICY_EXPONENTIAL,
88     })
89     @Retention(RetentionPolicy.SOURCE)
90     public @interface BackoffPolicy {}
91 
92     /**
93      * Linearly back-off a failed job. See
94      * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}
95      * retry_time(current_time, num_failures) =
96      *     current_time + initial_backoff_millis * num_failures, num_failures >= 1
97      */
98     public static final int BACKOFF_POLICY_LINEAR = 0;
99 
100     /**
101      * Exponentially back-off a failed job. See
102      * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}
103      *
104      * retry_time(current_time, num_failures) =
105      *     current_time + initial_backoff_millis * 2 ^ (num_failures - 1), num_failures >= 1
106      */
107     public static final int BACKOFF_POLICY_EXPONENTIAL = 1;
108 
109     /* Minimum interval for a periodic job, in milliseconds. */
110     private static final long MIN_PERIOD_MILLIS = 15 * 60 * 1000L;   // 15 minutes
111 
112     /* Minimum flex for a periodic job, in milliseconds. */
113     private static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
114 
115     /**
116      * Minimum backoff interval for a job, in milliseconds
117      * @hide
118      */
119     public static final long MIN_BACKOFF_MILLIS = 10 * 1000L;      // 10 seconds
120 
121     /**
122      * Query the minimum interval allowed for periodic scheduled jobs.  Attempting
123      * to declare a smaller period that this when scheduling a job will result in a
124      * job that is still periodic, but will run with this effective period.
125      *
126      * @return The minimum available interval for scheduling periodic jobs, in milliseconds.
127      */
getMinPeriodMillis()128     public static final long getMinPeriodMillis() {
129         return MIN_PERIOD_MILLIS;
130     }
131 
132     /**
133      * Query the minimum flex time allowed for periodic scheduled jobs.  Attempting
134      * to declare a shorter flex time than this when scheduling such a job will
135      * result in this amount as the effective flex time for the job.
136      *
137      * @return The minimum available flex time for scheduling periodic jobs, in milliseconds.
138      */
getMinFlexMillis()139     public static final long getMinFlexMillis() {
140         return MIN_FLEX_MILLIS;
141     }
142 
143     /**
144      * Query the minimum automatic-reschedule backoff interval permitted for jobs.
145      * @hide
146      */
getMinBackoffMillis()147     public static final long getMinBackoffMillis() {
148         return MIN_BACKOFF_MILLIS;
149     }
150 
151     /**
152      * Default type of backoff.
153      * @hide
154      */
155     public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL;
156 
157     /**
158      * Default of {@link #getPriority}.
159      * @hide
160      */
161     public static final int PRIORITY_DEFAULT = 0;
162 
163     /**
164      * Value of {@link #getPriority} for expedited syncs.
165      * @hide
166      */
167     public static final int PRIORITY_SYNC_EXPEDITED = 10;
168 
169     /**
170      * Value of {@link #getPriority} for first time initialization syncs.
171      * @hide
172      */
173     public static final int PRIORITY_SYNC_INITIALIZATION = 20;
174 
175     /**
176      * Value of {@link #getPriority} for a foreground app (overrides the supplied
177      * JobInfo priority if it is smaller).
178      * @hide
179      */
180     public static final int PRIORITY_FOREGROUND_APP = 30;
181 
182     /**
183      * Value of {@link #getPriority} for the current top app (overrides the supplied
184      * JobInfo priority if it is smaller).
185      * @hide
186      */
187     public static final int PRIORITY_TOP_APP = 40;
188 
189     /**
190      * Adjustment of {@link #getPriority} if the app has often (50% or more of the time)
191      * been running jobs.
192      * @hide
193      */
194     public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40;
195 
196     /**
197      * Adjustment of {@link #getPriority} if the app has always (90% or more of the time)
198      * been running jobs.
199      * @hide
200      */
201     public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
202 
203     /**
204      * Indicates that the implementation of this job will be using
205      * {@link JobService#startForeground(int, android.app.Notification)} to run
206      * in the foreground.
207      * <p>
208      * When set, the internal scheduling of this job will ignore any background
209      * network restrictions for the requesting app. Note that this flag alone
210      * doesn't actually place your {@link JobService} in the foreground; you
211      * still need to post the notification yourself.
212      * <p>
213      * To use this flag, the caller must hold the
214      * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL} permission.
215      *
216      * @hide
217      */
218     public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
219 
220     /**
221      * @hide
222      */
223     public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
224 
225     /**
226      * @hide
227      */
228     public static final int CONSTRAINT_FLAG_BATTERY_NOT_LOW = 1 << 1;
229 
230     /**
231      * @hide
232      */
233     public static final int CONSTRAINT_FLAG_DEVICE_IDLE = 1 << 2;
234 
235     /**
236      * @hide
237      */
238     public static final int CONSTRAINT_FLAG_STORAGE_NOT_LOW = 1 << 3;
239 
240     private final int jobId;
241     private final PersistableBundle extras;
242     private final Bundle transientExtras;
243     private final ClipData clipData;
244     private final int clipGrantFlags;
245     private final ComponentName service;
246     private final int constraintFlags;
247     private final TriggerContentUri[] triggerContentUris;
248     private final long triggerContentUpdateDelay;
249     private final long triggerContentMaxDelay;
250     private final boolean hasEarlyConstraint;
251     private final boolean hasLateConstraint;
252     private final int networkType;
253     private final long minLatencyMillis;
254     private final long maxExecutionDelayMillis;
255     private final boolean isPeriodic;
256     private final boolean isPersisted;
257     private final long intervalMillis;
258     private final long flexMillis;
259     private final long initialBackoffMillis;
260     private final int backoffPolicy;
261     private final int priority;
262     private final int flags;
263 
264     /**
265      * Unique job id associated with this application (uid).  This is the same job ID
266      * you supplied in the {@link Builder} constructor.
267      */
getId()268     public int getId() {
269         return jobId;
270     }
271 
272     /**
273      * Bundle of extras which are returned to your application at execution time.
274      */
getExtras()275     public @NonNull PersistableBundle getExtras() {
276         return extras;
277     }
278 
279     /**
280      * Bundle of transient extras which are returned to your application at execution time,
281      * but not persisted by the system.
282      */
getTransientExtras()283     public @NonNull Bundle getTransientExtras() {
284         return transientExtras;
285     }
286 
287     /**
288      * ClipData of information that is returned to your application at execution time,
289      * but not persisted by the system.
290      */
getClipData()291     public @Nullable ClipData getClipData() {
292         return clipData;
293     }
294 
295     /**
296      * Permission grants that go along with {@link #getClipData}.
297      */
getClipGrantFlags()298     public int getClipGrantFlags() {
299         return clipGrantFlags;
300     }
301 
302     /**
303      * Name of the service endpoint that will be called back into by the JobScheduler.
304      */
getService()305     public @NonNull ComponentName getService() {
306         return service;
307     }
308 
309     /** @hide */
getPriority()310     public int getPriority() {
311         return priority;
312     }
313 
314     /** @hide */
getFlags()315     public int getFlags() {
316         return flags;
317     }
318 
319     /**
320      * Whether this job needs the device to be plugged in.
321      */
isRequireCharging()322     public boolean isRequireCharging() {
323         return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
324     }
325 
326     /**
327      * Whether this job needs the device's battery level to not be at below the critical threshold.
328      */
isRequireBatteryNotLow()329     public boolean isRequireBatteryNotLow() {
330         return (constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0;
331     }
332 
333     /**
334      * Whether this job needs the device to be in an Idle maintenance window.
335      */
isRequireDeviceIdle()336     public boolean isRequireDeviceIdle() {
337         return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
338     }
339 
340     /**
341      * Whether this job needs the device's storage to not be low.
342      */
isRequireStorageNotLow()343     public boolean isRequireStorageNotLow() {
344         return (constraintFlags & CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0;
345     }
346 
347     /**
348      * @hide
349      */
getConstraintFlags()350     public int getConstraintFlags() {
351         return constraintFlags;
352     }
353 
354     /**
355      * Which content: URIs must change for the job to be scheduled.  Returns null
356      * if there are none required.
357      */
getTriggerContentUris()358     public @Nullable TriggerContentUri[] getTriggerContentUris() {
359         return triggerContentUris;
360     }
361 
362     /**
363      * When triggering on content URI changes, this is the delay from when a change
364      * is detected until the job is scheduled.
365      */
getTriggerContentUpdateDelay()366     public long getTriggerContentUpdateDelay() {
367         return triggerContentUpdateDelay;
368     }
369 
370     /**
371      * When triggering on content URI changes, this is the maximum delay we will
372      * use before scheduling the job.
373      */
getTriggerContentMaxDelay()374     public long getTriggerContentMaxDelay() {
375         return triggerContentMaxDelay;
376     }
377 
378     /**
379      * The kind of connectivity requirements that the job has.
380      */
getNetworkType()381     public @NetworkType int getNetworkType() {
382         return networkType;
383     }
384 
385     /**
386      * Set for a job that does not recur periodically, to specify a delay after which the job
387      * will be eligible for execution. This value is not set if the job recurs periodically.
388      */
getMinLatencyMillis()389     public long getMinLatencyMillis() {
390         return minLatencyMillis;
391     }
392 
393     /**
394      * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the job recurs
395      * periodically.
396      */
getMaxExecutionDelayMillis()397     public long getMaxExecutionDelayMillis() {
398         return maxExecutionDelayMillis;
399     }
400 
401     /**
402      * Track whether this job will repeat with a given period.
403      */
isPeriodic()404     public boolean isPeriodic() {
405         return isPeriodic;
406     }
407 
408     /**
409      * @return Whether or not this job should be persisted across device reboots.
410      */
isPersisted()411     public boolean isPersisted() {
412         return isPersisted;
413     }
414 
415     /**
416      * Set to the interval between occurrences of this job. This value is <b>not</b> set if the
417      * job does not recur periodically.
418      */
getIntervalMillis()419     public long getIntervalMillis() {
420         final long minInterval = getMinPeriodMillis();
421         return intervalMillis >= minInterval ? intervalMillis : minInterval;
422     }
423 
424     /**
425      * Flex time for this job. Only valid if this is a periodic job.  The job can
426      * execute at any time in a window of flex length at the end of the period.
427      */
getFlexMillis()428     public long getFlexMillis() {
429         long interval = getIntervalMillis();
430         long percentClamp = 5 * interval / 100;
431         long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
432         return clampedFlex <= interval ? clampedFlex : interval;
433     }
434 
435     /**
436      * The amount of time the JobScheduler will wait before rescheduling a failed job. This value
437      * will be increased depending on the backoff policy specified at job creation time. Defaults
438      * to 30 seconds, minimum is currently 10 seconds.
439      */
getInitialBackoffMillis()440     public long getInitialBackoffMillis() {
441         final long minBackoff = getMinBackoffMillis();
442         return initialBackoffMillis >= minBackoff ? initialBackoffMillis : minBackoff;
443     }
444 
445     /**
446      * Return the backoff policy of this job.
447      */
getBackoffPolicy()448     public @BackoffPolicy int getBackoffPolicy() {
449         return backoffPolicy;
450     }
451 
452     /**
453      * User can specify an early constraint of 0L, which is valid, so we keep track of whether the
454      * function was called at all.
455      * @hide
456      */
hasEarlyConstraint()457     public boolean hasEarlyConstraint() {
458         return hasEarlyConstraint;
459     }
460 
461     /**
462      * User can specify a late constraint of 0L, which is valid, so we keep track of whether the
463      * function was called at all.
464      * @hide
465      */
hasLateConstraint()466     public boolean hasLateConstraint() {
467         return hasLateConstraint;
468     }
469 
kindofEqualsBundle(BaseBundle a, BaseBundle b)470     private static boolean kindofEqualsBundle(BaseBundle a, BaseBundle b) {
471         return (a == b) || (a != null && a.kindofEquals(b));
472     }
473 
474     @Override
equals(Object o)475     public boolean equals(Object o) {
476         if (!(o instanceof JobInfo)) {
477             return false;
478         }
479         JobInfo j = (JobInfo) o;
480         if (jobId != j.jobId) {
481             return false;
482         }
483         // XXX won't be correct if one is parcelled and the other not.
484         if (!kindofEqualsBundle(extras, j.extras)) {
485             return false;
486         }
487         // XXX won't be correct if one is parcelled and the other not.
488         if (!kindofEqualsBundle(transientExtras, j.transientExtras)) {
489             return false;
490         }
491         // XXX for now we consider two different clip data objects to be different,
492         // regardless of whether their contents are the same.
493         if (clipData != j.clipData) {
494             return false;
495         }
496         if (clipGrantFlags != j.clipGrantFlags) {
497             return false;
498         }
499         if (!Objects.equals(service, j.service)) {
500             return false;
501         }
502         if (constraintFlags != j.constraintFlags) {
503             return false;
504         }
505         if (!Arrays.equals(triggerContentUris, j.triggerContentUris)) {
506             return false;
507         }
508         if (triggerContentUpdateDelay != j.triggerContentUpdateDelay) {
509             return false;
510         }
511         if (triggerContentMaxDelay != j.triggerContentMaxDelay) {
512             return false;
513         }
514         if (hasEarlyConstraint != j.hasEarlyConstraint) {
515             return false;
516         }
517         if (hasLateConstraint != j.hasLateConstraint) {
518             return false;
519         }
520         if (networkType != j.networkType) {
521             return false;
522         }
523         if (minLatencyMillis != j.minLatencyMillis) {
524             return false;
525         }
526         if (maxExecutionDelayMillis != j.maxExecutionDelayMillis) {
527             return false;
528         }
529         if (isPeriodic != j.isPeriodic) {
530             return false;
531         }
532         if (isPersisted != j.isPersisted) {
533             return false;
534         }
535         if (intervalMillis != j.intervalMillis) {
536             return false;
537         }
538         if (flexMillis != j.flexMillis) {
539             return false;
540         }
541         if (initialBackoffMillis != j.initialBackoffMillis) {
542             return false;
543         }
544         if (backoffPolicy != j.backoffPolicy) {
545             return false;
546         }
547         if (priority != j.priority) {
548             return false;
549         }
550         if (flags != j.flags) {
551             return false;
552         }
553         return true;
554     }
555 
556     @Override
hashCode()557     public int hashCode() {
558         int hashCode = jobId;
559         if (extras != null) {
560             hashCode = 31 * hashCode + extras.hashCode();
561         }
562         if (transientExtras != null) {
563             hashCode = 31 * hashCode + transientExtras.hashCode();
564         }
565         if (clipData != null) {
566             hashCode = 31 * hashCode + clipData.hashCode();
567         }
568         hashCode = 31*hashCode + clipGrantFlags;
569         if (service != null) {
570             hashCode = 31 * hashCode + service.hashCode();
571         }
572         hashCode = 31 * hashCode + constraintFlags;
573         if (triggerContentUris != null) {
574             hashCode = 31 * hashCode + Arrays.hashCode(triggerContentUris);
575         }
576         hashCode = 31 * hashCode + Long.hashCode(triggerContentUpdateDelay);
577         hashCode = 31 * hashCode + Long.hashCode(triggerContentMaxDelay);
578         hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
579         hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
580         hashCode = 31 * hashCode + networkType;
581         hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
582         hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
583         hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic);
584         hashCode = 31 * hashCode + Boolean.hashCode(isPersisted);
585         hashCode = 31 * hashCode + Long.hashCode(intervalMillis);
586         hashCode = 31 * hashCode + Long.hashCode(flexMillis);
587         hashCode = 31 * hashCode + Long.hashCode(initialBackoffMillis);
588         hashCode = 31 * hashCode + backoffPolicy;
589         hashCode = 31 * hashCode + priority;
590         hashCode = 31 * hashCode + flags;
591         return hashCode;
592     }
593 
JobInfo(Parcel in)594     private JobInfo(Parcel in) {
595         jobId = in.readInt();
596         extras = in.readPersistableBundle();
597         transientExtras = in.readBundle();
598         if (in.readInt() != 0) {
599             clipData = ClipData.CREATOR.createFromParcel(in);
600             clipGrantFlags = in.readInt();
601         } else {
602             clipData = null;
603             clipGrantFlags = 0;
604         }
605         service = in.readParcelable(null);
606         constraintFlags = in.readInt();
607         triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
608         triggerContentUpdateDelay = in.readLong();
609         triggerContentMaxDelay = in.readLong();
610         networkType = in.readInt();
611         minLatencyMillis = in.readLong();
612         maxExecutionDelayMillis = in.readLong();
613         isPeriodic = in.readInt() == 1;
614         isPersisted = in.readInt() == 1;
615         intervalMillis = in.readLong();
616         flexMillis = in.readLong();
617         initialBackoffMillis = in.readLong();
618         backoffPolicy = in.readInt();
619         hasEarlyConstraint = in.readInt() == 1;
620         hasLateConstraint = in.readInt() == 1;
621         priority = in.readInt();
622         flags = in.readInt();
623     }
624 
JobInfo(JobInfo.Builder b)625     private JobInfo(JobInfo.Builder b) {
626         jobId = b.mJobId;
627         extras = b.mExtras.deepCopy();
628         transientExtras = b.mTransientExtras.deepCopy();
629         clipData = b.mClipData;
630         clipGrantFlags = b.mClipGrantFlags;
631         service = b.mJobService;
632         constraintFlags = b.mConstraintFlags;
633         triggerContentUris = b.mTriggerContentUris != null
634                 ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()])
635                 : null;
636         triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
637         triggerContentMaxDelay = b.mTriggerContentMaxDelay;
638         networkType = b.mNetworkType;
639         minLatencyMillis = b.mMinLatencyMillis;
640         maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
641         isPeriodic = b.mIsPeriodic;
642         isPersisted = b.mIsPersisted;
643         intervalMillis = b.mIntervalMillis;
644         flexMillis = b.mFlexMillis;
645         initialBackoffMillis = b.mInitialBackoffMillis;
646         backoffPolicy = b.mBackoffPolicy;
647         hasEarlyConstraint = b.mHasEarlyConstraint;
648         hasLateConstraint = b.mHasLateConstraint;
649         priority = b.mPriority;
650         flags = b.mFlags;
651     }
652 
653     @Override
describeContents()654     public int describeContents() {
655         return 0;
656     }
657 
658     @Override
writeToParcel(Parcel out, int flags)659     public void writeToParcel(Parcel out, int flags) {
660         out.writeInt(jobId);
661         out.writePersistableBundle(extras);
662         out.writeBundle(transientExtras);
663         if (clipData != null) {
664             out.writeInt(1);
665             clipData.writeToParcel(out, flags);
666             out.writeInt(clipGrantFlags);
667         } else {
668             out.writeInt(0);
669         }
670         out.writeParcelable(service, flags);
671         out.writeInt(constraintFlags);
672         out.writeTypedArray(triggerContentUris, flags);
673         out.writeLong(triggerContentUpdateDelay);
674         out.writeLong(triggerContentMaxDelay);
675         out.writeInt(networkType);
676         out.writeLong(minLatencyMillis);
677         out.writeLong(maxExecutionDelayMillis);
678         out.writeInt(isPeriodic ? 1 : 0);
679         out.writeInt(isPersisted ? 1 : 0);
680         out.writeLong(intervalMillis);
681         out.writeLong(flexMillis);
682         out.writeLong(initialBackoffMillis);
683         out.writeInt(backoffPolicy);
684         out.writeInt(hasEarlyConstraint ? 1 : 0);
685         out.writeInt(hasLateConstraint ? 1 : 0);
686         out.writeInt(priority);
687         out.writeInt(this.flags);
688     }
689 
690     public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
691         @Override
692         public JobInfo createFromParcel(Parcel in) {
693             return new JobInfo(in);
694         }
695 
696         @Override
697         public JobInfo[] newArray(int size) {
698             return new JobInfo[size];
699         }
700     };
701 
702     @Override
toString()703     public String toString() {
704         return "(job:" + jobId + "/" + service.flattenToShortString() + ")";
705     }
706 
707     /**
708      * Information about a content URI modification that a job would like to
709      * trigger on.
710      */
711     public static final class TriggerContentUri implements Parcelable {
712         private final Uri mUri;
713         private final int mFlags;
714 
715         /** @hide */
716         @Retention(RetentionPolicy.SOURCE)
717         @IntDef(flag = true, prefix = { "FLAG_" }, value = {
718                 FLAG_NOTIFY_FOR_DESCENDANTS,
719         })
720         public @interface Flags { }
721 
722         /**
723          * Flag for trigger: also trigger if any descendants of the given URI change.
724          * Corresponds to the <var>notifyForDescendants</var> of
725          * {@link android.content.ContentResolver#registerContentObserver}.
726          */
727         public static final int FLAG_NOTIFY_FOR_DESCENDANTS = 1<<0;
728 
729         /**
730          * Create a new trigger description.
731          * @param uri The URI to observe.  Must be non-null.
732          * @param flags Flags for the observer.
733          */
TriggerContentUri(@onNull Uri uri, @Flags int flags)734         public TriggerContentUri(@NonNull Uri uri, @Flags int flags) {
735             mUri = uri;
736             mFlags = flags;
737         }
738 
739         /**
740          * Return the Uri this trigger was created for.
741          */
getUri()742         public Uri getUri() {
743             return mUri;
744         }
745 
746         /**
747          * Return the flags supplied for the trigger.
748          */
getFlags()749         public @Flags int getFlags() {
750             return mFlags;
751         }
752 
753         @Override
equals(Object o)754         public boolean equals(Object o) {
755             if (!(o instanceof TriggerContentUri)) {
756                 return false;
757             }
758             TriggerContentUri t = (TriggerContentUri) o;
759             return Objects.equals(t.mUri, mUri) && t.mFlags == mFlags;
760         }
761 
762         @Override
hashCode()763         public int hashCode() {
764             return (mUri == null ? 0 : mUri.hashCode()) ^ mFlags;
765         }
766 
TriggerContentUri(Parcel in)767         private TriggerContentUri(Parcel in) {
768             mUri = Uri.CREATOR.createFromParcel(in);
769             mFlags = in.readInt();
770         }
771 
772         @Override
describeContents()773         public int describeContents() {
774             return 0;
775         }
776 
777         @Override
writeToParcel(Parcel out, int flags)778         public void writeToParcel(Parcel out, int flags) {
779             mUri.writeToParcel(out, flags);
780             out.writeInt(mFlags);
781         }
782 
783         public static final Creator<TriggerContentUri> CREATOR = new Creator<TriggerContentUri>() {
784             @Override
785             public TriggerContentUri createFromParcel(Parcel in) {
786                 return new TriggerContentUri(in);
787             }
788 
789             @Override
790             public TriggerContentUri[] newArray(int size) {
791                 return new TriggerContentUri[size];
792             }
793         };
794     }
795 
796     /** Builder class for constructing {@link JobInfo} objects. */
797     public static final class Builder {
798         private final int mJobId;
799         private final ComponentName mJobService;
800         private PersistableBundle mExtras = PersistableBundle.EMPTY;
801         private Bundle mTransientExtras = Bundle.EMPTY;
802         private ClipData mClipData;
803         private int mClipGrantFlags;
804         private int mPriority = PRIORITY_DEFAULT;
805         private int mFlags;
806         // Requirements.
807         private int mConstraintFlags;
808         private int mNetworkType;
809         private ArrayList<TriggerContentUri> mTriggerContentUris;
810         private long mTriggerContentUpdateDelay = -1;
811         private long mTriggerContentMaxDelay = -1;
812         private boolean mIsPersisted;
813         // One-off parameters.
814         private long mMinLatencyMillis;
815         private long mMaxExecutionDelayMillis;
816         // Periodic parameters.
817         private boolean mIsPeriodic;
818         private boolean mHasEarlyConstraint;
819         private boolean mHasLateConstraint;
820         private long mIntervalMillis;
821         private long mFlexMillis;
822         // Back-off parameters.
823         private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS;
824         private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
825         /** Easy way to track whether the client has tried to set a back-off policy. */
826         private boolean mBackoffPolicySet = false;
827 
828         /**
829          * Initialize a new Builder to construct a {@link JobInfo}.
830          *
831          * @param jobId Application-provided id for this job. Subsequent calls to cancel, or
832          * jobs created with the same jobId, will update the pre-existing job with
833          * the same id.  This ID must be unique across all clients of the same uid
834          * (not just the same package).  You will want to make sure this is a stable
835          * id across app updates, so probably not based on a resource ID.
836          * @param jobService The endpoint that you implement that will receive the callback from the
837          * JobScheduler.
838          */
Builder(int jobId, @NonNull ComponentName jobService)839         public Builder(int jobId, @NonNull ComponentName jobService) {
840             mJobService = jobService;
841             mJobId = jobId;
842         }
843 
844         /** @hide */
setPriority(int priority)845         public Builder setPriority(int priority) {
846             mPriority = priority;
847             return this;
848         }
849 
850         /** @hide */
setFlags(int flags)851         public Builder setFlags(int flags) {
852             mFlags = flags;
853             return this;
854         }
855 
856         /**
857          * Set optional extras. This is persisted, so we only allow primitive types.
858          * @param extras Bundle containing extras you want the scheduler to hold on to for you.
859          */
setExtras(@onNull PersistableBundle extras)860         public Builder setExtras(@NonNull PersistableBundle extras) {
861             mExtras = extras;
862             return this;
863         }
864 
865         /**
866          * Set optional transient extras.
867          *
868          * <p>Because setting this property is not compatible with persisted
869          * jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
870          * {@link android.app.job.JobInfo.Builder#build()} is called.</p>
871          *
872          * @param extras Bundle containing extras you want the scheduler to hold on to for you.
873          */
setTransientExtras(@onNull Bundle extras)874         public Builder setTransientExtras(@NonNull Bundle extras) {
875             mTransientExtras = extras;
876             return this;
877         }
878 
879         /**
880          * Set a {@link ClipData} associated with this Job.
881          *
882          * <p>The main purpose of providing a ClipData is to allow granting of
883          * URI permissions for data associated with the clip.  The exact kind
884          * of permission grant to perform is specified through <var>grantFlags</var>.
885          *
886          * <p>If the ClipData contains items that are Intents, any
887          * grant flags in those Intents will be ignored.  Only flags provided as an argument
888          * to this method are respected, and will be applied to all Uri or
889          * Intent items in the clip (or sub-items of the clip).
890          *
891          * <p>Because setting this property is not compatible with persisted
892          * jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
893          * {@link android.app.job.JobInfo.Builder#build()} is called.</p>
894          *
895          * @param clip The new clip to set.  May be null to clear the current clip.
896          * @param grantFlags The desired permissions to grant for any URIs.  This should be
897          * a combination of {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION},
898          * {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, and
899          * {@link android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
900          */
setClipData(@ullable ClipData clip, int grantFlags)901         public Builder setClipData(@Nullable ClipData clip, int grantFlags) {
902             mClipData = clip;
903             mClipGrantFlags = grantFlags;
904             return this;
905         }
906 
907         /**
908          * Set some description of the kind of network type your job needs to have.
909          * Not calling this function means the network is not necessary, as the default is
910          * {@link #NETWORK_TYPE_NONE}.
911          * Bear in mind that calling this function defines network as a strict requirement for your
912          * job. If the network requested is not available your job will never run. See
913          * {@link #setOverrideDeadline(long)} to change this behaviour.
914          */
setRequiredNetworkType(@etworkType int networkType)915         public Builder setRequiredNetworkType(@NetworkType int networkType) {
916             mNetworkType = networkType;
917             return this;
918         }
919 
920         /**
921          * Specify that to run this job, the device needs to be plugged in. This defaults to
922          * false.
923          * @param requiresCharging Whether or not the device is plugged in.
924          */
setRequiresCharging(boolean requiresCharging)925         public Builder setRequiresCharging(boolean requiresCharging) {
926             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING)
927                     | (requiresCharging ? CONSTRAINT_FLAG_CHARGING : 0);
928             return this;
929         }
930 
931         /**
932          * Specify that to run this job, the device's battery level must not be low.
933          * This defaults to false.  If true, the job will only run when the battery level
934          * is not low, which is generally the point where the user is given a "low battery"
935          * warning.
936          * @param batteryNotLow Whether or not the device's battery level must not be low.
937          */
setRequiresBatteryNotLow(boolean batteryNotLow)938         public Builder setRequiresBatteryNotLow(boolean batteryNotLow) {
939             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_BATTERY_NOT_LOW)
940                     | (batteryNotLow ? CONSTRAINT_FLAG_BATTERY_NOT_LOW : 0);
941             return this;
942         }
943 
944         /**
945          * Specify that to run, the job needs the device to be in idle mode. This defaults to
946          * false.
947          * <p>Idle mode is a loose definition provided by the system, which means that the device
948          * is not in use, and has not been in use for some time. As such, it is a good time to
949          * perform resource heavy jobs. Bear in mind that battery usage will still be attributed
950          * to your application, and surfaced to the user in battery stats.</p>
951          * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
952          *                           window.
953          */
setRequiresDeviceIdle(boolean requiresDeviceIdle)954         public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
955             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE)
956                     | (requiresDeviceIdle ? CONSTRAINT_FLAG_DEVICE_IDLE : 0);
957             return this;
958         }
959 
960         /**
961          * Specify that to run this job, the device's available storage must not be low.
962          * This defaults to false.  If true, the job will only run when the device is not
963          * in a low storage state, which is generally the point where the user is given a
964          * "low storage" warning.
965          * @param storageNotLow Whether or not the device's available storage must not be low.
966          */
setRequiresStorageNotLow(boolean storageNotLow)967         public Builder setRequiresStorageNotLow(boolean storageNotLow) {
968             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_STORAGE_NOT_LOW)
969                     | (storageNotLow ? CONSTRAINT_FLAG_STORAGE_NOT_LOW : 0);
970             return this;
971         }
972 
973         /**
974          * Add a new content: URI that will be monitored with a
975          * {@link android.database.ContentObserver}, and will cause the job to execute if changed.
976          * If you have any trigger content URIs associated with a job, it will not execute until
977          * there has been a change report for one or more of them.
978          *
979          * <p>Note that trigger URIs can not be used in combination with
980          * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
981          * for content changes, you need to schedule a new JobInfo observing the same URIs
982          * before you finish execution of the JobService handling the most recent changes.
983          * Following this pattern will ensure you do not lost any content changes: while your
984          * job is running, the system will continue monitoring for content changes, and propagate
985          * any it sees over to the next job you schedule.</p>
986          *
987          * <p>Because setting this property is not compatible with periodic or
988          * persisted jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
989          * {@link android.app.job.JobInfo.Builder#build()} is called.</p>
990          *
991          * <p>The following example shows how this feature can be used to monitor for changes
992          * in the photos on a device.</p>
993          *
994          * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/PhotosContentJob.java
995          *      job}
996          *
997          * @param uri The content: URI to monitor.
998          */
addTriggerContentUri(@onNull TriggerContentUri uri)999         public Builder addTriggerContentUri(@NonNull TriggerContentUri uri) {
1000             if (mTriggerContentUris == null) {
1001                 mTriggerContentUris = new ArrayList<>();
1002             }
1003             mTriggerContentUris.add(uri);
1004             return this;
1005         }
1006 
1007         /**
1008          * Set the delay (in milliseconds) from when a content change is detected until
1009          * the job is scheduled.  If there are more changes during that time, the delay
1010          * will be reset to start at the time of the most recent change.
1011          * @param durationMs Delay after most recent content change, in milliseconds.
1012          */
setTriggerContentUpdateDelay(long durationMs)1013         public Builder setTriggerContentUpdateDelay(long durationMs) {
1014             mTriggerContentUpdateDelay = durationMs;
1015             return this;
1016         }
1017 
1018         /**
1019          * Set the maximum total delay (in milliseconds) that is allowed from the first
1020          * time a content change is detected until the job is scheduled.
1021          * @param durationMs Delay after initial content change, in milliseconds.
1022          */
setTriggerContentMaxDelay(long durationMs)1023         public Builder setTriggerContentMaxDelay(long durationMs) {
1024             mTriggerContentMaxDelay = durationMs;
1025             return this;
1026         }
1027 
1028         /**
1029          * Specify that this job should recur with the provided interval, not more than once per
1030          * period. You have no control over when within this interval this job will be executed,
1031          * only the guarantee that it will be executed at most once within this interval.
1032          * Setting this function on the builder with {@link #setMinimumLatency(long)} or
1033          * {@link #setOverrideDeadline(long)} will result in an error.
1034          * @param intervalMillis Millisecond interval for which this job will repeat.
1035          */
setPeriodic(long intervalMillis)1036         public Builder setPeriodic(long intervalMillis) {
1037             return setPeriodic(intervalMillis, intervalMillis);
1038         }
1039 
1040         /**
1041          * Specify that this job should recur with the provided interval and flex. The job can
1042          * execute at any time in a window of flex length at the end of the period.
1043          * @param intervalMillis Millisecond interval for which this job will repeat. A minimum
1044          *                       value of {@link #getMinPeriodMillis()} is enforced.
1045          * @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
1046          *                   {@link #getMinFlexMillis()} or 5 percent of the period, whichever is
1047          *                   higher.
1048          */
setPeriodic(long intervalMillis, long flexMillis)1049         public Builder setPeriodic(long intervalMillis, long flexMillis) {
1050             mIsPeriodic = true;
1051             mIntervalMillis = intervalMillis;
1052             mFlexMillis = flexMillis;
1053             mHasEarlyConstraint = mHasLateConstraint = true;
1054             return this;
1055         }
1056 
1057         /**
1058          * Specify that this job should be delayed by the provided amount of time.
1059          * Because it doesn't make sense setting this property on a periodic job, doing so will
1060          * throw an {@link java.lang.IllegalArgumentException} when
1061          * {@link android.app.job.JobInfo.Builder#build()} is called.
1062          * @param minLatencyMillis Milliseconds before which this job will not be considered for
1063          *                         execution.
1064          */
setMinimumLatency(long minLatencyMillis)1065         public Builder setMinimumLatency(long minLatencyMillis) {
1066             mMinLatencyMillis = minLatencyMillis;
1067             mHasEarlyConstraint = true;
1068             return this;
1069         }
1070 
1071         /**
1072          * Set deadline which is the maximum scheduling latency. The job will be run by this
1073          * deadline even if other requirements are not met. Because it doesn't make sense setting
1074          * this property on a periodic job, doing so will throw an
1075          * {@link java.lang.IllegalArgumentException} when
1076          * {@link android.app.job.JobInfo.Builder#build()} is called.
1077          */
setOverrideDeadline(long maxExecutionDelayMillis)1078         public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
1079             mMaxExecutionDelayMillis = maxExecutionDelayMillis;
1080             mHasLateConstraint = true;
1081             return this;
1082         }
1083 
1084         /**
1085          * Set up the back-off/retry policy.
1086          * This defaults to some respectable values: {30 seconds, Exponential}. We cap back-off at
1087          * 5hrs.
1088          * Note that trying to set a backoff criteria for a job with
1089          * {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
1090          * This is because back-off typically does not make sense for these types of jobs. See
1091          * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
1092          * for more description of the return value for the case of a job executing while in idle
1093          * mode.
1094          * @param initialBackoffMillis Millisecond time interval to wait initially when job has
1095          *                             failed.
1096          */
setBackoffCriteria(long initialBackoffMillis, @BackoffPolicy int backoffPolicy)1097         public Builder setBackoffCriteria(long initialBackoffMillis,
1098                 @BackoffPolicy int backoffPolicy) {
1099             mBackoffPolicySet = true;
1100             mInitialBackoffMillis = initialBackoffMillis;
1101             mBackoffPolicy = backoffPolicy;
1102             return this;
1103         }
1104 
1105         /**
1106          * Set whether or not to persist this job across device reboots.
1107          *
1108          * @param isPersisted True to indicate that the job will be written to
1109          *            disk and loaded at boot.
1110          */
1111         @RequiresPermission(android.Manifest.permission.RECEIVE_BOOT_COMPLETED)
setPersisted(boolean isPersisted)1112         public Builder setPersisted(boolean isPersisted) {
1113             mIsPersisted = isPersisted;
1114             return this;
1115         }
1116 
1117         /**
1118          * @return The job object to hand to the JobScheduler. This object is immutable.
1119          */
build()1120         public JobInfo build() {
1121             // Allow jobs with no constraints - What am I, a database?
1122             if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
1123                     mNetworkType == NETWORK_TYPE_NONE &&
1124                     mTriggerContentUris == null) {
1125                 throw new IllegalArgumentException("You're trying to build a job with no " +
1126                         "constraints, this is not allowed.");
1127             }
1128             // Check that a deadline was not set on a periodic job.
1129             if (mIsPeriodic) {
1130                 if (mMaxExecutionDelayMillis != 0L) {
1131                     throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
1132                             "periodic job.");
1133                 }
1134                 if (mMinLatencyMillis != 0L) {
1135                     throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
1136                             "periodic job");
1137                 }
1138                 if (mTriggerContentUris != null) {
1139                     throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
1140                             "periodic job");
1141                 }
1142             }
1143             if (mIsPersisted) {
1144                 if (mTriggerContentUris != null) {
1145                     throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
1146                             "persisted job");
1147                 }
1148                 if (!mTransientExtras.isEmpty()) {
1149                     throw new IllegalArgumentException("Can't call setTransientExtras() on a " +
1150                             "persisted job");
1151                 }
1152                 if (mClipData != null) {
1153                     throw new IllegalArgumentException("Can't call setClipData() on a " +
1154                             "persisted job");
1155                 }
1156             }
1157             if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
1158                 throw new IllegalArgumentException("An idle mode job will not respect any" +
1159                         " back-off policy, so calling setBackoffCriteria with" +
1160                         " setRequiresDeviceIdle is an error.");
1161             }
1162             JobInfo job = new JobInfo(this);
1163             if (job.isPeriodic()) {
1164                 if (job.intervalMillis != job.getIntervalMillis()) {
1165                     StringBuilder builder = new StringBuilder();
1166                     builder.append("Specified interval for ")
1167                             .append(String.valueOf(mJobId))
1168                             .append(" is ");
1169                     formatDuration(mIntervalMillis, builder);
1170                     builder.append(". Clamped to ");
1171                     formatDuration(job.getIntervalMillis(), builder);
1172                     Log.w(TAG, builder.toString());
1173                 }
1174                 if (job.flexMillis != job.getFlexMillis()) {
1175                     StringBuilder builder = new StringBuilder();
1176                     builder.append("Specified flex for ")
1177                             .append(String.valueOf(mJobId))
1178                             .append(" is ");
1179                     formatDuration(mFlexMillis, builder);
1180                     builder.append(". Clamped to ");
1181                     formatDuration(job.getFlexMillis(), builder);
1182                     Log.w(TAG, builder.toString());
1183                 }
1184             }
1185             return job;
1186         }
1187     }
1188 
1189 }
1190