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