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