JobInfo.java revision 970510b9c5299d480b3ec7f8403afd8265456f87
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.app.job;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.ComponentName;
22import android.net.Uri;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.os.PersistableBundle;
26import android.util.Log;
27import static android.util.TimeUtils.formatForLogging;
28
29import java.util.ArrayList;
30
31/**
32 * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the
33 * parameters required to schedule work against the calling application. These are constructed
34 * using the {@link JobInfo.Builder}.
35 * You must specify at least one sort of constraint on the JobInfo object that you are creating.
36 * The goal here is to provide the scheduler with high-level semantics about the work you want to
37 * accomplish. Doing otherwise with throw an exception in your app.
38 */
39public class JobInfo implements Parcelable {
40    private static String TAG = "JobInfo";
41    /** Default. */
42    public static final int NETWORK_TYPE_NONE = 0;
43    /** This job requires network connectivity. */
44    public static final int NETWORK_TYPE_ANY = 1;
45    /** This job requires network connectivity that is unmetered. */
46    public static final int NETWORK_TYPE_UNMETERED = 2;
47
48    /**
49     * Amount of backoff a job has initially by default, in milliseconds.
50     */
51    public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L;  // 30 seconds.
52
53    /**
54     * Maximum backoff we allow for a job, in milliseconds.
55     */
56    public static final long MAX_BACKOFF_DELAY_MILLIS = 5 * 60 * 60 * 1000;  // 5 hours.
57
58    /**
59     * Linearly back-off a failed job. See
60     * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}
61     * retry_time(current_time, num_failures) =
62     *     current_time + initial_backoff_millis * num_failures, num_failures >= 1
63     */
64    public static final int BACKOFF_POLICY_LINEAR = 0;
65
66    /**
67     * Exponentially back-off a failed job. See
68     * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}
69     *
70     * retry_time(current_time, num_failures) =
71     *     current_time + initial_backoff_millis * 2 ^ (num_failures - 1), num_failures >= 1
72     */
73    public static final int BACKOFF_POLICY_EXPONENTIAL = 1;
74
75    /* Minimum interval for a periodic job, in milliseconds. */
76    public static final long MIN_PERIOD_MILLIS = 60 * 60 * 1000L;   // 60 minutes
77    /* Minimum flex for a periodic job, in milliseconds. */
78    public static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
79
80    /**
81     * Default type of backoff.
82     * @hide
83     */
84    public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL;
85
86    /**
87     * Default of {@link #getPriority}.
88     * @hide
89     */
90    public static final int PRIORITY_DEFAULT = 0;
91
92    /**
93     * Value of {@link #getPriority} for expedited syncs.
94     * @hide
95     */
96    public static final int PRIORITY_SYNC_EXPEDITED = 10;
97
98    /**
99     * Value of {@link #getPriority} for first time initialization syncs.
100     * @hide
101     */
102    public static final int PRIORITY_SYNC_INITIALIZATION = 20;
103
104    /**
105     * Value of {@link #getPriority} for a foreground app (overrides the supplied
106     * JobInfo priority if it is smaller).
107     * @hide
108     */
109    public static final int PRIORITY_FOREGROUND_APP = 30;
110
111    /**
112     * Value of {@link #getPriority} for the current top app (overrides the supplied
113     * JobInfo priority if it is smaller).
114     * @hide
115     */
116    public static final int PRIORITY_TOP_APP = 40;
117
118    private final int jobId;
119    private final PersistableBundle extras;
120    private final ComponentName service;
121    private final boolean requireCharging;
122    private final boolean requireDeviceIdle;
123    private final TriggerContentUri[] triggerContentUris;
124    private final boolean hasEarlyConstraint;
125    private final boolean hasLateConstraint;
126    private final int networkType;
127    private final long minLatencyMillis;
128    private final long maxExecutionDelayMillis;
129    private final boolean isPeriodic;
130    private final boolean isPersisted;
131    private final long intervalMillis;
132    private final long flexMillis;
133    private final long initialBackoffMillis;
134    private final int backoffPolicy;
135    private final int priority;
136
137    /**
138     * Unique job id associated with this class. This is assigned to your job by the scheduler.
139     */
140    public int getId() {
141        return jobId;
142    }
143
144    /**
145     * Bundle of extras which are returned to your application at execution time.
146     */
147    public PersistableBundle getExtras() {
148        return extras;
149    }
150
151    /**
152     * Name of the service endpoint that will be called back into by the JobScheduler.
153     */
154    public ComponentName getService() {
155        return service;
156    }
157
158    /** @hide */
159    public int getPriority() {
160        return priority;
161    }
162
163    /**
164     * Whether this job needs the device to be plugged in.
165     */
166    public boolean isRequireCharging() {
167        return requireCharging;
168    }
169
170    /**
171     * Whether this job needs the device to be in an Idle maintenance window.
172     */
173    public boolean isRequireDeviceIdle() {
174        return requireDeviceIdle;
175    }
176
177    /**
178     * Which content: URIs must change for the job to be scheduled.  Returns null
179     * if there are none required.
180     */
181    @Nullable
182    public TriggerContentUri[] getTriggerContentUris() {
183        return triggerContentUris;
184    }
185
186    /**
187     * One of {@link android.app.job.JobInfo#NETWORK_TYPE_ANY},
188     * {@link android.app.job.JobInfo#NETWORK_TYPE_NONE}, or
189     * {@link android.app.job.JobInfo#NETWORK_TYPE_UNMETERED}.
190     */
191    public int getNetworkType() {
192        return networkType;
193    }
194
195    /**
196     * Set for a job that does not recur periodically, to specify a delay after which the job
197     * will be eligible for execution. This value is not set if the job recurs periodically.
198     */
199    public long getMinLatencyMillis() {
200        return minLatencyMillis;
201    }
202
203    /**
204     * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the job recurs
205     * periodically.
206     */
207    public long getMaxExecutionDelayMillis() {
208        return maxExecutionDelayMillis;
209    }
210
211    /**
212     * Track whether this job will repeat with a given period.
213     */
214    public boolean isPeriodic() {
215        return isPeriodic;
216    }
217
218    /**
219     * @return Whether or not this job should be persisted across device reboots.
220     */
221    public boolean isPersisted() {
222        return isPersisted;
223    }
224
225    /**
226     * Set to the interval between occurrences of this job. This value is <b>not</b> set if the
227     * job does not recur periodically.
228     */
229    public long getIntervalMillis() {
230        return intervalMillis >= MIN_PERIOD_MILLIS ? intervalMillis : MIN_PERIOD_MILLIS;
231    }
232
233    /**
234     * Flex time for this job. Only valid if this is a periodic job.
235     */
236    public long getFlexMillis() {
237        long interval = getIntervalMillis();
238        long percentClamp = 5 * interval / 100;
239        long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, MIN_FLEX_MILLIS));
240        return clampedFlex <= interval ? clampedFlex : interval;
241    }
242
243    /**
244     * The amount of time the JobScheduler will wait before rescheduling a failed job. This value
245     * will be increased depending on the backoff policy specified at job creation time. Defaults
246     * to 5 seconds.
247     */
248    public long getInitialBackoffMillis() {
249        return initialBackoffMillis;
250    }
251
252    /**
253     * One of either {@link android.app.job.JobInfo#BACKOFF_POLICY_EXPONENTIAL}, or
254     * {@link android.app.job.JobInfo#BACKOFF_POLICY_LINEAR}, depending on which criteria you set
255     * when creating this job.
256     */
257    public int getBackoffPolicy() {
258        return backoffPolicy;
259    }
260
261    /**
262     * User can specify an early constraint of 0L, which is valid, so we keep track of whether the
263     * function was called at all.
264     * @hide
265     */
266    public boolean hasEarlyConstraint() {
267        return hasEarlyConstraint;
268    }
269
270    /**
271     * User can specify a late constraint of 0L, which is valid, so we keep track of whether the
272     * function was called at all.
273     * @hide
274     */
275    public boolean hasLateConstraint() {
276        return hasLateConstraint;
277    }
278
279    private JobInfo(Parcel in) {
280        jobId = in.readInt();
281        extras = in.readPersistableBundle();
282        service = in.readParcelable(null);
283        requireCharging = in.readInt() == 1;
284        requireDeviceIdle = in.readInt() == 1;
285        triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
286        networkType = in.readInt();
287        minLatencyMillis = in.readLong();
288        maxExecutionDelayMillis = in.readLong();
289        isPeriodic = in.readInt() == 1;
290        isPersisted = in.readInt() == 1;
291        intervalMillis = in.readLong();
292        flexMillis = in.readLong();
293        initialBackoffMillis = in.readLong();
294        backoffPolicy = in.readInt();
295        hasEarlyConstraint = in.readInt() == 1;
296        hasLateConstraint = in.readInt() == 1;
297        priority = in.readInt();
298    }
299
300    private JobInfo(JobInfo.Builder b) {
301        jobId = b.mJobId;
302        extras = b.mExtras;
303        service = b.mJobService;
304        requireCharging = b.mRequiresCharging;
305        requireDeviceIdle = b.mRequiresDeviceIdle;
306        triggerContentUris = b.mTriggerContentUris != null
307                ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()])
308                : null;
309        networkType = b.mNetworkType;
310        minLatencyMillis = b.mMinLatencyMillis;
311        maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
312        isPeriodic = b.mIsPeriodic;
313        isPersisted = b.mIsPersisted;
314        intervalMillis = b.mIntervalMillis;
315        flexMillis = b.mFlexMillis;
316        initialBackoffMillis = b.mInitialBackoffMillis;
317        backoffPolicy = b.mBackoffPolicy;
318        hasEarlyConstraint = b.mHasEarlyConstraint;
319        hasLateConstraint = b.mHasLateConstraint;
320        priority = b.mPriority;
321    }
322
323    @Override
324    public int describeContents() {
325        return 0;
326    }
327
328    @Override
329    public void writeToParcel(Parcel out, int flags) {
330        out.writeInt(jobId);
331        out.writePersistableBundle(extras);
332        out.writeParcelable(service, flags);
333        out.writeInt(requireCharging ? 1 : 0);
334        out.writeInt(requireDeviceIdle ? 1 : 0);
335        out.writeTypedArray(triggerContentUris, flags);
336        out.writeInt(networkType);
337        out.writeLong(minLatencyMillis);
338        out.writeLong(maxExecutionDelayMillis);
339        out.writeInt(isPeriodic ? 1 : 0);
340        out.writeInt(isPersisted ? 1 : 0);
341        out.writeLong(intervalMillis);
342        out.writeLong(flexMillis);
343        out.writeLong(initialBackoffMillis);
344        out.writeInt(backoffPolicy);
345        out.writeInt(hasEarlyConstraint ? 1 : 0);
346        out.writeInt(hasLateConstraint ? 1 : 0);
347        out.writeInt(priority);
348    }
349
350    public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
351        @Override
352        public JobInfo createFromParcel(Parcel in) {
353            return new JobInfo(in);
354        }
355
356        @Override
357        public JobInfo[] newArray(int size) {
358            return new JobInfo[size];
359        }
360    };
361
362    @Override
363    public String toString() {
364        return "(job:" + jobId + "/" + service.flattenToShortString() + ")";
365    }
366
367    /**
368     * Information about a content URI modification that a job would like to
369     * trigger on.
370     */
371    public static final class TriggerContentUri implements Parcelable {
372        private final Uri mUri;
373        private final int mFlags;
374
375        /**
376         * Flag for trigger: also trigger if any descendants of the given URI change.
377         * Corresponds to the <var>notifyForDescendants</var> of
378         * {@link android.content.ContentResolver#registerContentObserver}.
379         */
380        public static final int FLAG_NOTIFY_FOR_DESCENDANTS = 1<<0;
381
382        /**
383         * Create a new trigger description.
384         * @param uri The URI to observe.  Must be non-null.
385         * @param flags Optional flags for the observer, either 0 or
386         * {@link #FLAG_NOTIFY_FOR_DESCENDANTS}.
387         */
388        public TriggerContentUri(@NonNull Uri uri, int flags) {
389            mUri = uri;
390            mFlags = flags;
391        }
392
393        /**
394         * Return the Uri this trigger was created for.
395         */
396        public Uri getUri() {
397            return mUri;
398        }
399
400        /**
401         * Return the flags supplied for the trigger.
402         */
403        public int getFlags() {
404            return mFlags;
405        }
406
407        private TriggerContentUri(Parcel in) {
408            mUri = Uri.CREATOR.createFromParcel(in);
409            mFlags = in.readInt();
410        }
411
412        @Override
413        public int describeContents() {
414            return 0;
415        }
416
417        @Override
418        public void writeToParcel(Parcel out, int flags) {
419            mUri.writeToParcel(out, flags);
420            out.writeInt(mFlags);
421        }
422
423        public static final Creator<TriggerContentUri> CREATOR = new Creator<TriggerContentUri>() {
424            @Override
425            public TriggerContentUri createFromParcel(Parcel in) {
426                return new TriggerContentUri(in);
427            }
428
429            @Override
430            public TriggerContentUri[] newArray(int size) {
431                return new TriggerContentUri[size];
432            }
433        };
434    }
435
436    /** Builder class for constructing {@link JobInfo} objects. */
437    public static final class Builder {
438        private int mJobId;
439        private PersistableBundle mExtras = PersistableBundle.EMPTY;
440        private ComponentName mJobService;
441        private int mPriority = PRIORITY_DEFAULT;
442        // Requirements.
443        private boolean mRequiresCharging;
444        private boolean mRequiresDeviceIdle;
445        private int mNetworkType;
446        private ArrayList<TriggerContentUri> mTriggerContentUris;
447        private boolean mIsPersisted;
448        // One-off parameters.
449        private long mMinLatencyMillis;
450        private long mMaxExecutionDelayMillis;
451        // Periodic parameters.
452        private boolean mIsPeriodic;
453        private boolean mHasEarlyConstraint;
454        private boolean mHasLateConstraint;
455        private long mIntervalMillis;
456        private long mFlexMillis;
457        // Back-off parameters.
458        private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS;
459        private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
460        /** Easy way to track whether the client has tried to set a back-off policy. */
461        private boolean mBackoffPolicySet = false;
462
463        /**
464         * @param jobId Application-provided id for this job. Subsequent calls to cancel, or
465         *               jobs created with the same jobId, will update the pre-existing job with
466         *               the same id.
467         * @param jobService The endpoint that you implement that will receive the callback from the
468         *            JobScheduler.
469         */
470        public Builder(int jobId, ComponentName jobService) {
471            mJobService = jobService;
472            mJobId = jobId;
473        }
474
475        /**
476         * @hide
477         */
478        public Builder setPriority(int priority) {
479            mPriority = priority;
480            return this;
481        }
482
483        /**
484         * Set optional extras. This is persisted, so we only allow primitive types.
485         * @param extras Bundle containing extras you want the scheduler to hold on to for you.
486         */
487        public Builder setExtras(PersistableBundle extras) {
488            mExtras = extras;
489            return this;
490        }
491
492        /**
493         * Set some description of the kind of network type your job needs to have.
494         * Not calling this function means the network is not necessary, as the default is
495         * {@link #NETWORK_TYPE_NONE}.
496         * Bear in mind that calling this function defines network as a strict requirement for your
497         * job. If the network requested is not available your job will never run. See
498         * {@link #setOverrideDeadline(long)} to change this behaviour.
499         */
500        public Builder setRequiredNetworkType(int networkType) {
501            mNetworkType = networkType;
502            return this;
503        }
504
505        /**
506         * Specify that to run this job, the device needs to be plugged in. This defaults to
507         * false.
508         * @param requiresCharging Whether or not the device is plugged in.
509         */
510        public Builder setRequiresCharging(boolean requiresCharging) {
511            mRequiresCharging = requiresCharging;
512            return this;
513        }
514
515        /**
516         * Specify that to run, the job needs the device to be in idle mode. This defaults to
517         * false.
518         * <p>Idle mode is a loose definition provided by the system, which means that the device
519         * is not in use, and has not been in use for some time. As such, it is a good time to
520         * perform resource heavy jobs. Bear in mind that battery usage will still be attributed
521         * to your application, and surfaced to the user in battery stats.</p>
522         * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
523         *                           window.
524         */
525        public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
526            mRequiresDeviceIdle = requiresDeviceIdle;
527            return this;
528        }
529
530        /**
531         * Add a new content: URI that will be monitored with a
532         * {@link android.database.ContentObserver}, and will cause the job to execute if changed.
533         * If you have any trigger content URIs associated with a job, it will not execute until
534         * there has been a change report for one or more of them.
535         * <p>Note that trigger URIs can not be used in combination with
536         * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
537         * for content changes, you need to schedule a new JobInfo observing the same URIs
538         * before you finish execution of the JobService handling the most recent changes.</p>
539         * @param uri The content: URI to monitor.
540         */
541        public Builder addTriggerContentUri(@NonNull TriggerContentUri uri) {
542            if (mTriggerContentUris == null) {
543                mTriggerContentUris = new ArrayList<>();
544            }
545            mTriggerContentUris.add(uri);
546            return this;
547        }
548
549        /**
550         * Specify that this job should recur with the provided interval, not more than once per
551         * period. You have no control over when within this interval this job will be executed,
552         * only the guarantee that it will be executed at most once within this interval.
553         * Setting this function on the builder with {@link #setMinimumLatency(long)} or
554         * {@link #setOverrideDeadline(long)} will result in an error.
555         * @param intervalMillis Millisecond interval for which this job will repeat.
556         */
557        public Builder setPeriodic(long intervalMillis) {
558            return setPeriodic(intervalMillis, intervalMillis);
559        }
560
561        /**
562         * Specify that this job should recur with the provided interval and flex. The job can
563         * execute at any time in a window of flex length at the end of the period.
564         * @param intervalMillis Millisecond interval for which this job will repeat. A minimum
565         *                       value of {@link #MIN_PERIOD_MILLIS} is enforced.
566         * @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
567         *                   {@link #MIN_FLEX_MILLIS} or 5 percent of the period, whichever is
568         *                   higher.
569         */
570        public Builder setPeriodic(long intervalMillis, long flexMillis) {
571            mIsPeriodic = true;
572            mIntervalMillis = intervalMillis;
573            mFlexMillis = flexMillis;
574            mHasEarlyConstraint = mHasLateConstraint = true;
575            return this;
576        }
577
578        /**
579         * Specify that this job should be delayed by the provided amount of time.
580         * Because it doesn't make sense setting this property on a periodic job, doing so will
581         * throw an {@link java.lang.IllegalArgumentException} when
582         * {@link android.app.job.JobInfo.Builder#build()} is called.
583         * @param minLatencyMillis Milliseconds before which this job will not be considered for
584         *                         execution.
585         */
586        public Builder setMinimumLatency(long minLatencyMillis) {
587            mMinLatencyMillis = minLatencyMillis;
588            mHasEarlyConstraint = true;
589            return this;
590        }
591
592        /**
593         * Set deadline which is the maximum scheduling latency. The job will be run by this
594         * deadline even if other requirements are not met. Because it doesn't make sense setting
595         * this property on a periodic job, doing so will throw an
596         * {@link java.lang.IllegalArgumentException} when
597         * {@link android.app.job.JobInfo.Builder#build()} is called.
598         */
599        public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
600            mMaxExecutionDelayMillis = maxExecutionDelayMillis;
601            mHasLateConstraint = true;
602            return this;
603        }
604
605        /**
606         * Set up the back-off/retry policy.
607         * This defaults to some respectable values: {30 seconds, Exponential}. We cap back-off at
608         * 5hrs.
609         * Note that trying to set a backoff criteria for a job with
610         * {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
611         * This is because back-off typically does not make sense for these types of jobs. See
612         * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
613         * for more description of the return value for the case of a job executing while in idle
614         * mode.
615         * @param initialBackoffMillis Millisecond time interval to wait initially when job has
616         *                             failed.
617         * @param backoffPolicy is one of {@link #BACKOFF_POLICY_LINEAR} or
618         * {@link #BACKOFF_POLICY_EXPONENTIAL}
619         */
620        public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) {
621            mBackoffPolicySet = true;
622            mInitialBackoffMillis = initialBackoffMillis;
623            mBackoffPolicy = backoffPolicy;
624            return this;
625        }
626
627        /**
628         * Set whether or not to persist this job across device reboots. This will only have an
629         * effect if your application holds the permission
630         * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will
631         * be thrown.
632         * @param isPersisted True to indicate that the job will be written to disk and loaded at
633         *                    boot.
634         */
635        public Builder setPersisted(boolean isPersisted) {
636            mIsPersisted = isPersisted;
637            return this;
638        }
639
640        /**
641         * @return The job object to hand to the JobScheduler. This object is immutable.
642         */
643        public JobInfo build() {
644            // Allow jobs with no constraints - What am I, a database?
645            if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging &&
646                    !mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE &&
647                    mTriggerContentUris == null) {
648                throw new IllegalArgumentException("You're trying to build a job with no " +
649                        "constraints, this is not allowed.");
650            }
651            mExtras = new PersistableBundle(mExtras);  // Make our own copy.
652            // Check that a deadline was not set on a periodic job.
653            if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
654                throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
655                        "periodic job.");
656            }
657            if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
658                throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
659                        "periodic job");
660            }
661            if (mIsPeriodic && (mTriggerContentUris != null)) {
662                throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
663                        "periodic job");
664            }
665            if (mIsPersisted && (mTriggerContentUris != null)) {
666                throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
667                        "persisted job");
668            }
669            if (mBackoffPolicySet && mRequiresDeviceIdle) {
670                throw new IllegalArgumentException("An idle mode job will not respect any" +
671                        " back-off policy, so calling setBackoffCriteria with" +
672                        " setRequiresDeviceIdle is an error.");
673            }
674            JobInfo job = new JobInfo(this);
675            if (job.intervalMillis != job.getIntervalMillis()) {
676                Log.w(TAG, "Specified interval for " + mJobService.getPackageName() + " is "
677                        + formatForLogging(mIntervalMillis) + ". Clamped to " +
678                        formatForLogging(job.getIntervalMillis()));
679            }
680            if (job.flexMillis != job.getFlexMillis()) {
681                Log.w(TAG, "Specified interval for " + mJobService.getPackageName() + " is "
682                        + formatForLogging(mFlexMillis) + ". Clamped to " +
683                        formatForLogging(job.getFlexMillis()));
684            }
685            return job;
686        }
687    }
688
689}
690