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