1 /* 2 * Copyright (C) 2017 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.app.job.JobInfo.NETWORK_BYTES_UNKNOWN; 20 21 import android.annotation.BytesLong; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SuppressLint; 25 import android.compat.Compatibility; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.Intent; 28 import android.os.Build; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.os.PersistableBundle; 32 33 /** 34 * A unit of work that can be enqueued for a job using 35 * {@link JobScheduler#enqueue JobScheduler.enqueue}. See 36 * {@link JobParameters#dequeueWork() JobParameters.dequeueWork} for more details. 37 * 38 * <p class="caution"><strong>Note:</strong> Prior to Android version 39 * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted. 40 * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw 41 * an {@link IllegalArgumentException} if they attempted to do so. Starting with 42 * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems can be persisted alongside 43 * the hosting job. However, Intents cannot be persisted. Set a {@link PersistableBundle} using 44 * {@link Builder#setExtras(PersistableBundle)} for any information that needs to be persisted. 45 */ 46 final public class JobWorkItem implements Parcelable { 47 @NonNull 48 private final PersistableBundle mExtras; 49 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 50 final Intent mIntent; 51 private final long mNetworkDownloadBytes; 52 private final long mNetworkUploadBytes; 53 private final long mMinimumChunkBytes; 54 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 55 int mDeliveryCount; 56 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 57 int mWorkId; 58 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 59 Object mGrants; 60 61 /** 62 * Create a new piece of work, which can be submitted to 63 * {@link JobScheduler#enqueue JobScheduler.enqueue}. 64 * 65 * <p> 66 * Intents cannot be used for persisted JobWorkItems. 67 * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems. 68 * 69 * @param intent The general Intent describing this work. 70 */ JobWorkItem(Intent intent)71 public JobWorkItem(Intent intent) { 72 this(intent, NETWORK_BYTES_UNKNOWN, NETWORK_BYTES_UNKNOWN); 73 } 74 75 /** 76 * Create a new piece of work, which can be submitted to 77 * {@link JobScheduler#enqueue JobScheduler.enqueue}. 78 * <p> 79 * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for 80 * details about how to estimate network traffic. 81 * 82 * <p> 83 * Intents cannot be used for persisted JobWorkItems. 84 * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems. 85 * 86 * @param intent The general Intent describing this work. 87 * @param downloadBytes The estimated size of network traffic that will be 88 * downloaded by this job work item, in bytes. 89 * @param uploadBytes The estimated size of network traffic that will be 90 * uploaded by this job work item, in bytes. 91 */ JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes)92 public JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes) { 93 this(intent, downloadBytes, uploadBytes, NETWORK_BYTES_UNKNOWN); 94 } 95 96 /** 97 * Create a new piece of work, which can be submitted to 98 * {@link JobScheduler#enqueue JobScheduler.enqueue}. 99 * <p> 100 * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for 101 * details about how to estimate network traffic. 102 * 103 * <p> 104 * Intents cannot be used for persisted JobWorkItems. 105 * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems. 106 * 107 * @param intent The general Intent describing this work. 108 * @param downloadBytes The estimated size of network traffic that will be 109 * downloaded by this job work item, in bytes. 110 * @param uploadBytes The estimated size of network traffic that will be 111 * uploaded by this job work item, in bytes. 112 * @param minimumChunkBytes The smallest piece of data that cannot be easily paused and 113 * resumed, in bytes. 114 */ JobWorkItem(@ullable Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes)115 public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes, 116 @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) { 117 mExtras = PersistableBundle.EMPTY; 118 mIntent = intent; 119 mNetworkDownloadBytes = downloadBytes; 120 mNetworkUploadBytes = uploadBytes; 121 mMinimumChunkBytes = minimumChunkBytes; 122 enforceValidity(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES)); 123 } 124 JobWorkItem(@onNull Builder builder)125 private JobWorkItem(@NonNull Builder builder) { 126 mDeliveryCount = builder.mDeliveryCount; 127 mExtras = builder.mExtras.deepCopy(); 128 mIntent = builder.mIntent; 129 mNetworkDownloadBytes = builder.mNetworkDownloadBytes; 130 mNetworkUploadBytes = builder.mNetworkUploadBytes; 131 mMinimumChunkBytes = builder.mMinimumNetworkChunkBytes; 132 } 133 134 /** 135 * Return the extras associated with this work. 136 * 137 * @see Builder#setExtras(PersistableBundle) 138 */ 139 @NonNull getExtras()140 public PersistableBundle getExtras() { 141 return mExtras; 142 } 143 144 /** 145 * Return the Intent associated with this work. 146 */ getIntent()147 public Intent getIntent() { 148 return mIntent; 149 } 150 151 /** 152 * Return the estimated size of download traffic that will be performed by 153 * this job, in bytes. 154 * 155 * @return Estimated size of download traffic, or 156 * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown. 157 */ getEstimatedNetworkDownloadBytes()158 public @BytesLong long getEstimatedNetworkDownloadBytes() { 159 return mNetworkDownloadBytes; 160 } 161 162 /** 163 * Return the estimated size of upload traffic that will be performed by 164 * this job work item, in bytes. 165 * 166 * @return Estimated size of upload traffic, or 167 * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown. 168 */ getEstimatedNetworkUploadBytes()169 public @BytesLong long getEstimatedNetworkUploadBytes() { 170 return mNetworkUploadBytes; 171 } 172 173 /** 174 * Return the smallest piece of data that cannot be easily paused and resumed, in bytes. 175 * 176 * @return Smallest piece of data that cannot be easily paused and resumed, or 177 * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown. 178 */ getMinimumNetworkChunkBytes()179 public @BytesLong long getMinimumNetworkChunkBytes() { 180 return mMinimumChunkBytes; 181 } 182 183 /** 184 * Return the count of the number of times this work item has been delivered 185 * to the job. The value will be > 1 if it has been redelivered because the job 186 * was stopped or crashed while it had previously been delivered but before the 187 * job had called {@link JobParameters#completeWork JobParameters.completeWork} for it. 188 */ getDeliveryCount()189 public int getDeliveryCount() { 190 return mDeliveryCount; 191 } 192 193 /** 194 * @hide 195 */ bumpDeliveryCount()196 public void bumpDeliveryCount() { 197 mDeliveryCount++; 198 } 199 200 /** 201 * @hide 202 */ setWorkId(int id)203 public void setWorkId(int id) { 204 mWorkId = id; 205 } 206 207 /** 208 * @hide 209 */ getWorkId()210 public int getWorkId() { 211 return mWorkId; 212 } 213 214 /** 215 * @hide 216 */ setGrants(Object grants)217 public void setGrants(Object grants) { 218 mGrants = grants; 219 } 220 221 /** 222 * @hide 223 */ 224 @Nullable getGrants()225 public Object getGrants() { 226 return mGrants; 227 } 228 toString()229 public String toString() { 230 StringBuilder sb = new StringBuilder(64); 231 sb.append("JobWorkItem{id="); 232 sb.append(mWorkId); 233 sb.append(" intent="); 234 sb.append(mIntent); 235 sb.append(" extras="); 236 sb.append(mExtras); 237 if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN) { 238 sb.append(" downloadBytes="); 239 sb.append(mNetworkDownloadBytes); 240 } 241 if (mNetworkUploadBytes != NETWORK_BYTES_UNKNOWN) { 242 sb.append(" uploadBytes="); 243 sb.append(mNetworkUploadBytes); 244 } 245 if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN) { 246 sb.append(" minimumChunkBytes="); 247 sb.append(mMinimumChunkBytes); 248 } 249 if (mDeliveryCount != 0) { 250 sb.append(" dcount="); 251 sb.append(mDeliveryCount); 252 } 253 sb.append("}"); 254 return sb.toString(); 255 } 256 257 /** 258 * Builder class for constructing {@link JobWorkItem} objects. 259 */ 260 public static final class Builder { 261 private int mDeliveryCount; 262 private PersistableBundle mExtras = PersistableBundle.EMPTY; 263 private Intent mIntent; 264 private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; 265 private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; 266 private long mMinimumNetworkChunkBytes = NETWORK_BYTES_UNKNOWN; 267 268 /** 269 * Initialize a new Builder to construct a {@link JobWorkItem} object. 270 */ Builder()271 public Builder() { 272 } 273 274 /** 275 * @see JobWorkItem#getDeliveryCount() 276 * @return This object for method chaining 277 * @hide 278 */ 279 @NonNull setDeliveryCount(int deliveryCount)280 public Builder setDeliveryCount(int deliveryCount) { 281 mDeliveryCount = deliveryCount; 282 return this; 283 } 284 285 /** 286 * Set optional extras. This can be persisted, so we only allow primitive types. 287 * @param extras Bundle containing extras you want the scheduler to hold on to for you. 288 * @return This object for method chaining 289 * @see JobWorkItem#getExtras() 290 */ 291 @NonNull setExtras(@onNull PersistableBundle extras)292 public Builder setExtras(@NonNull PersistableBundle extras) { 293 if (extras == null) { 294 throw new IllegalArgumentException("extras cannot be null"); 295 } 296 mExtras = extras; 297 return this; 298 } 299 300 /** 301 * Set an intent with information relevant to this work item. 302 * 303 * <p> 304 * Intents cannot be used for persisted JobWorkItems. 305 * Use {@link #setExtras(PersistableBundle)} instead for persisted JobWorkItems. 306 * 307 * @return This object for method chaining 308 * @see JobWorkItem#getIntent() 309 */ 310 @NonNull setIntent(@onNull Intent intent)311 public Builder setIntent(@NonNull Intent intent) { 312 mIntent = intent; 313 return this; 314 } 315 316 /** 317 * Set the estimated size of network traffic that will be performed for this work item, 318 * in bytes. 319 * 320 * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for 321 * details about how to estimate network traffic. 322 * 323 * @param downloadBytes The estimated size of network traffic that will be 324 * downloaded for this work item, in bytes. 325 * @param uploadBytes The estimated size of network traffic that will be 326 * uploaded for this work item, in bytes. 327 * @return This object for method chaining 328 * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long) 329 * @see JobWorkItem#getEstimatedNetworkDownloadBytes() 330 * @see JobWorkItem#getEstimatedNetworkUploadBytes() 331 */ 332 @NonNull 333 @SuppressLint("MissingGetterMatchingBuilder") setEstimatedNetworkBytes(@ytesLong long downloadBytes, @BytesLong long uploadBytes)334 public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes, 335 @BytesLong long uploadBytes) { 336 if (downloadBytes != NETWORK_BYTES_UNKNOWN && downloadBytes < 0) { 337 throw new IllegalArgumentException( 338 "Invalid network download bytes: " + downloadBytes); 339 } 340 if (uploadBytes != NETWORK_BYTES_UNKNOWN && uploadBytes < 0) { 341 throw new IllegalArgumentException("Invalid network upload bytes: " + uploadBytes); 342 } 343 mNetworkDownloadBytes = downloadBytes; 344 mNetworkUploadBytes = uploadBytes; 345 return this; 346 } 347 348 /** 349 * Set the minimum size of non-resumable network traffic this work item requires, in bytes. 350 * When the upload or download can be easily paused and resumed, use this to set the 351 * smallest size that must be transmitted between start and stop events to be considered 352 * successful. If the transfer cannot be paused and resumed, then this should be the sum 353 * of the values provided to {@link #setEstimatedNetworkBytes(long, long)}. 354 * 355 * See {@link JobInfo.Builder#setMinimumNetworkChunkBytes(long)} for 356 * details about how to set the minimum chunk. 357 * 358 * @param chunkSizeBytes The smallest piece of data that cannot be easily paused and 359 * resumed, in bytes. 360 * @return This object for method chaining 361 * @see JobInfo.Builder#setMinimumNetworkChunkBytes(long) 362 * @see JobWorkItem#getMinimumNetworkChunkBytes() 363 * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long) 364 */ 365 @NonNull setMinimumNetworkChunkBytes(@ytesLong long chunkSizeBytes)366 public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) { 367 if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) { 368 throw new IllegalArgumentException("Minimum chunk size must be positive"); 369 } 370 mMinimumNetworkChunkBytes = chunkSizeBytes; 371 return this; 372 } 373 374 /** 375 * @return The JobWorkItem object to hand to the JobScheduler. This object is immutable. 376 */ 377 @NonNull build()378 public JobWorkItem build() { 379 return build(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES)); 380 } 381 382 /** @hide */ 383 @NonNull build(boolean rejectNegativeNetworkEstimates)384 public JobWorkItem build(boolean rejectNegativeNetworkEstimates) { 385 JobWorkItem jobWorkItem = new JobWorkItem(this); 386 jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates); 387 return jobWorkItem; 388 } 389 } 390 391 /** 392 * @hide 393 */ enforceValidity(boolean rejectNegativeNetworkEstimates)394 public void enforceValidity(boolean rejectNegativeNetworkEstimates) { 395 if (rejectNegativeNetworkEstimates) { 396 if (mNetworkUploadBytes != NETWORK_BYTES_UNKNOWN && mNetworkUploadBytes < 0) { 397 throw new IllegalArgumentException( 398 "Invalid network upload bytes: " + mNetworkUploadBytes); 399 } 400 if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN && mNetworkDownloadBytes < 0) { 401 throw new IllegalArgumentException( 402 "Invalid network download bytes: " + mNetworkDownloadBytes); 403 } 404 } 405 final long estimatedTransfer; 406 if (mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) { 407 estimatedTransfer = mNetworkDownloadBytes; 408 } else { 409 estimatedTransfer = mNetworkUploadBytes 410 + (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : mNetworkDownloadBytes); 411 } 412 if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN 413 && estimatedTransfer != NETWORK_BYTES_UNKNOWN 414 && mMinimumChunkBytes > estimatedTransfer) { 415 throw new IllegalArgumentException( 416 "Minimum chunk size can't be greater than estimated network usage"); 417 } 418 if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN && mMinimumChunkBytes <= 0) { 419 throw new IllegalArgumentException("Minimum chunk size must be positive"); 420 } 421 } 422 describeContents()423 public int describeContents() { 424 return 0; 425 } 426 writeToParcel(Parcel out, int flags)427 public void writeToParcel(Parcel out, int flags) { 428 if (mIntent != null) { 429 out.writeInt(1); 430 mIntent.writeToParcel(out, 0); 431 } else { 432 out.writeInt(0); 433 } 434 out.writePersistableBundle(mExtras); 435 out.writeLong(mNetworkDownloadBytes); 436 out.writeLong(mNetworkUploadBytes); 437 out.writeLong(mMinimumChunkBytes); 438 out.writeInt(mDeliveryCount); 439 out.writeInt(mWorkId); 440 } 441 442 public static final @android.annotation.NonNull Parcelable.Creator<JobWorkItem> CREATOR 443 = new Parcelable.Creator<JobWorkItem>() { 444 public JobWorkItem createFromParcel(Parcel in) { 445 return new JobWorkItem(in); 446 } 447 448 public JobWorkItem[] newArray(int size) { 449 return new JobWorkItem[size]; 450 } 451 }; 452 453 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) JobWorkItem(Parcel in)454 JobWorkItem(Parcel in) { 455 if (in.readInt() != 0) { 456 mIntent = Intent.CREATOR.createFromParcel(in); 457 } else { 458 mIntent = null; 459 } 460 final PersistableBundle extras = in.readPersistableBundle(); 461 mExtras = extras != null ? extras : PersistableBundle.EMPTY; 462 mNetworkDownloadBytes = in.readLong(); 463 mNetworkUploadBytes = in.readLong(); 464 mMinimumChunkBytes = in.readLong(); 465 mDeliveryCount = in.readInt(); 466 mWorkId = in.readInt(); 467 } 468 } 469