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