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 android.annotation.FlaggedApi; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.TestApi; 24 import android.app.ActivityManager; 25 import android.app.usage.UsageStatsManager; 26 import android.compat.Compatibility; 27 import android.compat.annotation.ChangeId; 28 import android.compat.annotation.Disabled; 29 import android.compat.annotation.Overridable; 30 import android.compat.annotation.UnsupportedAppUsage; 31 import android.content.ClipData; 32 import android.content.pm.PackageManager; 33 import android.net.Network; 34 import android.net.NetworkRequest; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.os.Parcel; 39 import android.os.Parcelable; 40 import android.os.PersistableBundle; 41 import android.os.Process; 42 import android.os.RemoteException; 43 import android.system.SystemCleaner; 44 import android.util.Log; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 48 import java.lang.annotation.Retention; 49 import java.lang.annotation.RetentionPolicy; 50 import java.lang.ref.Cleaner; 51 52 /** 53 * Contains the parameters used to configure/identify your job. You do not create this object 54 * yourself, instead it is handed in to your application by the System. 55 */ 56 public class JobParameters implements Parcelable { 57 private static final String TAG = "JobParameters"; 58 59 /** @hide */ 60 public static final int INTERNAL_STOP_REASON_UNKNOWN = -1; 61 62 /** @hide */ 63 public static final int INTERNAL_STOP_REASON_CANCELED = 64 JobProtoEnums.INTERNAL_STOP_REASON_CANCELLED; // 0. 65 /** @hide */ 66 public static final int INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED = 67 JobProtoEnums.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED; // 1. 68 /** @hide */ 69 public static final int INTERNAL_STOP_REASON_PREEMPT = 70 JobProtoEnums.INTERNAL_STOP_REASON_PREEMPT; // 2. 71 /** 72 * The job ran for at least its minimum execution limit. 73 * @hide 74 */ 75 public static final int INTERNAL_STOP_REASON_TIMEOUT = 76 JobProtoEnums.INTERNAL_STOP_REASON_TIMEOUT; // 3. 77 /** @hide */ 78 public static final int INTERNAL_STOP_REASON_DEVICE_IDLE = 79 JobProtoEnums.INTERNAL_STOP_REASON_DEVICE_IDLE; // 4. 80 /** @hide */ 81 public static final int INTERNAL_STOP_REASON_DEVICE_THERMAL = 82 JobProtoEnums.INTERNAL_STOP_REASON_DEVICE_THERMAL; // 5. 83 /** 84 * The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} 85 * bucket. 86 * 87 * @hide 88 */ 89 public static final int INTERNAL_STOP_REASON_RESTRICTED_BUCKET = 90 JobProtoEnums.INTERNAL_STOP_REASON_RESTRICTED_BUCKET; // 6. 91 /** 92 * The app was uninstalled. 93 * @hide 94 */ 95 public static final int INTERNAL_STOP_REASON_UNINSTALL = 96 JobProtoEnums.INTERNAL_STOP_REASON_UNINSTALL; // 7. 97 /** 98 * The app's data was cleared. 99 * @hide 100 */ 101 public static final int INTERNAL_STOP_REASON_DATA_CLEARED = 102 JobProtoEnums.INTERNAL_STOP_REASON_DATA_CLEARED; // 8. 103 /** 104 * @hide 105 */ 106 public static final int INTERNAL_STOP_REASON_RTC_UPDATED = 107 JobProtoEnums.INTERNAL_STOP_REASON_RTC_UPDATED; // 9. 108 /** 109 * The app called jobFinished() on its own. 110 * @hide 111 */ 112 public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH = 113 JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10. 114 /** 115 * The user stopped the job via some UI (eg. Task Manager). 116 * @hide 117 */ 118 @TestApi 119 public static final int INTERNAL_STOP_REASON_USER_UI_STOP = 120 JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11. 121 /** 122 * The app didn't respond quickly enough from JobScheduler's perspective. 123 * @hide 124 */ 125 public static final int INTERNAL_STOP_REASON_ANR = 126 JobProtoEnums.INTERNAL_STOP_REASON_ANR; // 12. 127 128 /** 129 * The job ran for at least its minimum execution limit and the app lost the strong reference 130 * to the {@link JobParameters}. This could indicate that the job is empty because the app 131 * can no longer call {@link JobService#jobFinished(JobParameters, boolean)}. 132 * @hide 133 */ 134 public static final int INTERNAL_STOP_REASON_TIMEOUT_ABANDONED = 135 JobProtoEnums.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED; // 13. 136 137 /** 138 * All the stop reason codes. This should be regarded as an immutable array at runtime. 139 * 140 * Note the order of these values will affect "dumpsys batterystats", and we do not want to 141 * change the order of existing fields, so adding new fields is okay but do not remove or 142 * change existing fields. When deprecating a field, just replace that with "-1" in this array. 143 * 144 * @hide 145 */ 146 public static final int[] JOB_STOP_REASON_CODES = { 147 INTERNAL_STOP_REASON_UNKNOWN, 148 INTERNAL_STOP_REASON_CANCELED, 149 INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, 150 INTERNAL_STOP_REASON_PREEMPT, 151 INTERNAL_STOP_REASON_TIMEOUT, 152 INTERNAL_STOP_REASON_DEVICE_IDLE, 153 INTERNAL_STOP_REASON_DEVICE_THERMAL, 154 INTERNAL_STOP_REASON_RESTRICTED_BUCKET, 155 INTERNAL_STOP_REASON_UNINSTALL, 156 INTERNAL_STOP_REASON_DATA_CLEARED, 157 INTERNAL_STOP_REASON_RTC_UPDATED, 158 INTERNAL_STOP_REASON_SUCCESSFUL_FINISH, 159 INTERNAL_STOP_REASON_USER_UI_STOP, 160 INTERNAL_STOP_REASON_ANR, 161 INTERNAL_STOP_REASON_TIMEOUT_ABANDONED, 162 }; 163 164 /** 165 * @hide 166 */ 167 // TODO(142420609): make it @SystemApi for mainline 168 @NonNull getInternalReasonCodeDescription(int reasonCode)169 public static String getInternalReasonCodeDescription(int reasonCode) { 170 switch (reasonCode) { 171 case INTERNAL_STOP_REASON_CANCELED: return "canceled"; 172 case INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints"; 173 case INTERNAL_STOP_REASON_PREEMPT: return "preempt"; 174 case INTERNAL_STOP_REASON_TIMEOUT: return "timeout"; 175 case INTERNAL_STOP_REASON_DEVICE_IDLE: return "device_idle"; 176 case INTERNAL_STOP_REASON_DEVICE_THERMAL: return "thermal"; 177 case INTERNAL_STOP_REASON_RESTRICTED_BUCKET: return "restricted_bucket"; 178 case INTERNAL_STOP_REASON_UNINSTALL: return "uninstall"; 179 case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared"; 180 case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated"; 181 case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish"; 182 case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop"; 183 case INTERNAL_STOP_REASON_ANR: return "anr"; 184 case INTERNAL_STOP_REASON_TIMEOUT_ABANDONED: return "timeout_abandoned"; 185 default: return "unknown:" + reasonCode; 186 } 187 } 188 189 /** @hide */ 190 @NonNull getJobStopReasonCodes()191 public static int[] getJobStopReasonCodes() { 192 return JOB_STOP_REASON_CODES; 193 } 194 195 /** 196 * There is no reason the job is stopped. This is the value returned from the JobParameters 197 * object passed to {@link JobService#onStartJob(JobParameters)}. 198 */ 199 public static final int STOP_REASON_UNDEFINED = 0; 200 /** 201 * The job was cancelled directly by the app, either by calling 202 * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a 203 * new job with the same job ID. 204 */ 205 public static final int STOP_REASON_CANCELLED_BY_APP = 1; 206 /** The job was stopped to run a higher priority job of the app. */ 207 public static final int STOP_REASON_PREEMPT = 2; 208 /** 209 * The job used up its maximum execution time and timed out. Each individual job has a maximum 210 * execution time limit, regardless of how much total quota the app has. See the note on 211 * {@link JobScheduler} and {@link JobInfo} for the execution time limits. 212 */ 213 public static final int STOP_REASON_TIMEOUT = 3; 214 /** 215 * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this 216 * job. 217 */ 218 public static final int STOP_REASON_DEVICE_STATE = 4; 219 /** 220 * The requested battery-not-low constraint is no longer satisfied. 221 * 222 * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean) 223 */ 224 public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; 225 /** 226 * The requested charging constraint is no longer satisfied. 227 * 228 * @see JobInfo.Builder#setRequiresCharging(boolean) 229 */ 230 public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; 231 /** 232 * The requested connectivity constraint is no longer satisfied. 233 * 234 * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest) 235 * @see JobInfo.Builder#setRequiredNetworkType(int) 236 */ 237 public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; 238 /** 239 * The requested idle constraint is no longer satisfied. 240 * 241 * @see JobInfo.Builder#setRequiresDeviceIdle(boolean) 242 */ 243 public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; 244 /** 245 * The requested storage-not-low constraint is no longer satisfied. 246 * 247 * @see JobInfo.Builder#setRequiresStorageNotLow(boolean) 248 */ 249 public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; 250 /** 251 * The app has consumed all of its current quota. Each app is assigned a quota of how much 252 * it can run jobs within a certain time frame. The quota is informed, in part, by app standby 253 * buckets. Once an app has used up all of its quota, it won't be able to start jobs until 254 * quota is replenished, is changed, or is temporarily not applied. 255 * 256 * @see UsageStatsManager#getAppStandbyBucket() 257 */ 258 public static final int STOP_REASON_QUOTA = 10; 259 /** 260 * The app is restricted from running in the background. 261 * 262 * @see ActivityManager#isBackgroundRestricted() 263 * @see PackageManager#isInstantApp() 264 */ 265 public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; 266 /** 267 * The current standby bucket requires that the job stop now. 268 * 269 * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED 270 */ 271 public static final int STOP_REASON_APP_STANDBY = 12; 272 /** 273 * The user stopped the job. This can happen either through force-stop, adb shell commands, 274 * uninstalling, or some other UI. 275 */ 276 public static final int STOP_REASON_USER = 13; 277 /** The system is doing some processing that requires stopping this job. */ 278 public static final int STOP_REASON_SYSTEM_PROCESSING = 14; 279 /** 280 * The system's estimate of when the app will be launched changed significantly enough to 281 * decide this job shouldn't be running right now. This will mostly apply to prefetch jobs. 282 * 283 * @see JobInfo#isPrefetch() 284 * @see JobInfo.Builder#setPrefetch(boolean) 285 */ 286 public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15; 287 288 /** 289 * The job used up its maximum execution time and timed out. The system also detected that the 290 * app can no longer call {@link JobService#jobFinished(JobParameters, boolean)} for this job, 291 * likely because the strong reference to the job handle ({@link JobParameters}) passed to it 292 * via {@link JobService#onStartJob(JobParameters)} was lost. This can occur even if the app 293 * called {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or 294 * {@link JobScheduler#cancelInAllNamespaces()} to stop an active job while losing strong 295 * references to the job handle. In this case, the job is not necessarily abandoned. However, 296 * the system cannot distinguish such cases from truly abandoned jobs. 297 * <p> 298 * It is recommended that you use {@link JobService#jobFinished(JobParameters, boolean)} or 299 * return false from {@link JobService#onStartJob(JobParameters)} to stop an active job. This 300 * will prevent the occurrence of this stop reason and the need to handle it. The primary use 301 * case for this stop reason is to report a probable case of a job being abandoned. 302 * <p> 303 */ 304 @FlaggedApi(Flags.FLAG_HANDLE_ABANDONED_JOBS) 305 public static final int STOP_REASON_TIMEOUT_ABANDONED = 16; 306 307 /** @hide */ 308 @IntDef(prefix = {"STOP_REASON_"}, value = { 309 STOP_REASON_UNDEFINED, 310 STOP_REASON_CANCELLED_BY_APP, 311 STOP_REASON_PREEMPT, 312 STOP_REASON_TIMEOUT, 313 STOP_REASON_DEVICE_STATE, 314 STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW, 315 STOP_REASON_CONSTRAINT_CHARGING, 316 STOP_REASON_CONSTRAINT_CONNECTIVITY, 317 STOP_REASON_CONSTRAINT_DEVICE_IDLE, 318 STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW, 319 STOP_REASON_QUOTA, 320 STOP_REASON_BACKGROUND_RESTRICTION, 321 STOP_REASON_APP_STANDBY, 322 STOP_REASON_USER, 323 STOP_REASON_SYSTEM_PROCESSING, 324 STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED, 325 STOP_REASON_TIMEOUT_ABANDONED, 326 }) 327 @Retention(RetentionPolicy.SOURCE) 328 public @interface StopReason { 329 } 330 331 @UnsupportedAppUsage 332 private final int jobId; 333 @Nullable 334 private final String mJobNamespace; 335 private final PersistableBundle extras; 336 private final Bundle transientExtras; 337 private final ClipData clipData; 338 private final int clipGrantFlags; 339 @UnsupportedAppUsage 340 private final IBinder callback; 341 private final boolean overrideDeadlineExpired; 342 private final boolean mIsExpedited; 343 private final boolean mIsUserInitiated; 344 private final Uri[] mTriggeredContentUris; 345 private final String[] mTriggeredContentAuthorities; 346 @Nullable 347 private Network mNetwork; 348 349 private int mStopReason = STOP_REASON_UNDEFINED; 350 private int mInternalStopReason = INTERNAL_STOP_REASON_UNKNOWN; 351 private String debugStopReason; // Human readable stop reason for debugging. 352 @Nullable 353 private JobCleanupCallback mJobCleanupCallback; 354 @Nullable 355 private Cleaner.Cleanable mCleanable; 356 /** 357 * Override handling of abandoned jobs in the system. Overriding this change 358 * will prevent the system to handle abandoned jobs and report it as a new 359 * stop reason STOP_REASON_TIMEOUT_ABANDONED. 360 * @hide 361 */ 362 @ChangeId 363 @Disabled 364 @Overridable 365 public static final long OVERRIDE_HANDLE_ABANDONED_JOBS = 372529068L; 366 367 /** @hide */ JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras, Bundle transientExtras, ClipData clipData, int clipGrantFlags, boolean overrideDeadlineExpired, boolean isExpedited, boolean isUserInitiated, Uri[] triggeredContentUris, String[] triggeredContentAuthorities, Network network)368 public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras, 369 Bundle transientExtras, ClipData clipData, int clipGrantFlags, 370 boolean overrideDeadlineExpired, boolean isExpedited, 371 boolean isUserInitiated, Uri[] triggeredContentUris, 372 String[] triggeredContentAuthorities, Network network) { 373 this.jobId = jobId; 374 this.extras = extras; 375 this.transientExtras = transientExtras; 376 this.clipData = clipData; 377 this.clipGrantFlags = clipGrantFlags; 378 this.callback = callback; 379 this.overrideDeadlineExpired = overrideDeadlineExpired; 380 this.mIsExpedited = isExpedited; 381 this.mIsUserInitiated = isUserInitiated; 382 this.mTriggeredContentUris = triggeredContentUris; 383 this.mTriggeredContentAuthorities = triggeredContentAuthorities; 384 this.mNetwork = network; 385 this.mJobNamespace = namespace; 386 this.mJobCleanupCallback = null; 387 this.mCleanable = null; 388 } 389 390 /** 391 * @return The unique id of this job, specified at creation time. 392 */ getJobId()393 public int getJobId() { 394 return jobId; 395 } 396 397 /** 398 * Get the namespace this job was placed in. 399 * 400 * @see JobScheduler#forNamespace(String) 401 * @return The namespace this job was scheduled in. Will be {@code null} if there was no 402 * explicit namespace set and this job is therefore in the default namespace. 403 */ 404 @Nullable getJobNamespace()405 public String getJobNamespace() { 406 return mJobNamespace; 407 } 408 409 /** 410 * Returns the reason {@link JobService#onStopJob(JobParameters)} was called on this job. 411 * <p> 412 * Apps should not rely on the stop reason for critical decision-making, as additional stop 413 * reasons may be added in subsequent Android releases. The primary intended use of this method 414 * is for logging and diagnostic purposes to gain insights into the causes of job termination. 415 * <p> 416 * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will 417 * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not 418 * yet been called. 419 */ 420 @StopReason getStopReason()421 public int getStopReason() { 422 return mStopReason; 423 } 424 425 /** @hide */ getInternalStopReasonCode()426 public int getInternalStopReasonCode() { 427 return mInternalStopReason; 428 } 429 430 /** 431 * Reason onStopJob() was called on this job. 432 * 433 * @hide 434 */ getDebugStopReason()435 public String getDebugStopReason() { 436 return debugStopReason; 437 } 438 439 /** 440 * @return The extras you passed in when constructing this job with 441 * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will 442 * never be null. If you did not set any extras this will be an empty bundle. 443 */ getExtras()444 public @NonNull PersistableBundle getExtras() { 445 return extras; 446 } 447 448 /** 449 * @return The transient extras you passed in when constructing this job with 450 * {@link android.app.job.JobInfo.Builder#setTransientExtras(android.os.Bundle)}. This will 451 * never be null. If you did not set any extras this will be an empty bundle. 452 */ getTransientExtras()453 public @NonNull Bundle getTransientExtras() { 454 return transientExtras; 455 } 456 457 /** 458 * @return The clip you passed in when constructing this job with 459 * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be null 460 * if it was not set. 461 */ getClipData()462 public @Nullable ClipData getClipData() { 463 return clipData; 464 } 465 466 /** 467 * @return The clip grant flags you passed in when constructing this job with 468 * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be 0 469 * if it was not set. 470 */ getClipGrantFlags()471 public int getClipGrantFlags() { 472 return clipGrantFlags; 473 } 474 475 /** 476 * @return Whether this job is running as an expedited job or not. A job is guaranteed to have 477 * all expedited job guarantees for the duration of the job execution if this returns 478 * {@code true}. This will return {@code false} if the job that wasn't requested to run as a 479 * expedited job, or if it was requested to run as an expedited job but the app didn't have 480 * any remaining expedited job quota at the time of execution. 481 * 482 * @see JobInfo.Builder#setExpedited(boolean) 483 */ isExpeditedJob()484 public boolean isExpeditedJob() { 485 return mIsExpedited; 486 } 487 488 /** 489 * @return Whether this job is running as a user-initiated job or not. A job is guaranteed to 490 * have all user-initiated job guarantees for the duration of the job execution if this returns 491 * {@code true}. This will return {@code false} if the job wasn't requested to run as a 492 * user-initiated job, or if it was requested to run as a user-initiated job but the app didn't 493 * meet any of the requirements at the time of execution, such as having the 494 * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission. 495 * 496 * @see JobInfo.Builder#setUserInitiated(boolean) 497 */ isUserInitiatedJob()498 public boolean isUserInitiatedJob() { 499 return mIsUserInitiated; 500 } 501 502 /** 503 * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this 504 * provides an easy way to tell whether the job is being executed due to the deadline 505 * expiring. Note: If the job is running because its deadline expired, it implies that its 506 * constraints will not be met. However, 507 * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever 508 * run when their constraints are satisfied, therefore, the constraints will still be satisfied 509 * for a periodic job even if the deadline has expired. 510 */ isOverrideDeadlineExpired()511 public boolean isOverrideDeadlineExpired() { 512 return overrideDeadlineExpired; 513 } 514 515 /** 516 * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this 517 * reports which URIs have triggered the job. This will be null if either no URIs have 518 * triggered it (it went off due to a deadline or other reason), or the number of changed 519 * URIs is too large to report. Whether or not the number of URIs is too large, you can 520 * always use {@link #getTriggeredContentAuthorities()} to determine whether the job was 521 * triggered due to any content changes and the authorities they are associated with. 522 */ getTriggeredContentUris()523 public @Nullable Uri[] getTriggeredContentUris() { 524 return mTriggeredContentUris; 525 } 526 527 /** 528 * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this 529 * reports which content authorities have triggered the job. It will only be null if no 530 * authorities have triggered it -- that is, the job executed for some other reason, such 531 * as a deadline expiring. If this is non-null, you can use {@link #getTriggeredContentUris()} 532 * to retrieve the details of which URIs changed (as long as that has not exceeded the maximum 533 * number it can reported). 534 */ getTriggeredContentAuthorities()535 public @Nullable String[] getTriggeredContentAuthorities() { 536 return mTriggeredContentAuthorities; 537 } 538 539 /** 540 * Return the network that should be used to perform any network requests 541 * for this job. 542 * <p> 543 * Devices may have multiple active network connections simultaneously, or 544 * they may not have a default network route at all. To correctly handle all 545 * situations like this, your job should always use the network returned by 546 * this method instead of implicitly using the default network route. 547 * <p> 548 * Note that the system may relax the constraints you originally requested, 549 * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over 550 * a metered network when there is a surplus of metered data available. 551 * 552 * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, 553 * this will return {@code null} if the app does not hold the permissions specified in 554 * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}. 555 * 556 * @return the network that should be used to perform any network requests 557 * for this job, or {@code null} if this job didn't set any required 558 * network type or if the job executed when there was no available network to use. 559 * @see JobInfo.Builder#setRequiredNetworkType(int) 560 * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest) 561 */ getNetwork()562 public @Nullable Network getNetwork() { 563 return mNetwork; 564 } 565 566 /** 567 * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their 568 * currently running job. Calling this method when there is no more work available and all 569 * previously dequeued work has been completed will result in the system taking care of 570 * stopping the job for you -- 571 * you should not call {@link JobService#jobFinished(JobParameters, boolean)} yourself 572 * (otherwise you risk losing an upcoming JobWorkItem that is being enqueued at the same time). 573 * 574 * <p>Once you are done with the {@link JobWorkItem} returned by this method, you must call 575 * {@link #completeWork(JobWorkItem)} with it to inform the system that you are done 576 * executing the work. The job will not be finished until all dequeued work has been 577 * completed. You do not, however, have to complete each returned work item before deqeueing 578 * the next one -- you can use {@link #dequeueWork()} multiple times before completing 579 * previous work if you want to process work in parallel, and you can complete the work 580 * in whatever order you want.</p> 581 * 582 * <p>If the job runs to the end of its available time period before all work has been 583 * completed, it will stop as normal. You should return true from 584 * {@link JobService#onStopJob(JobParameters)} in order to have the job rescheduled, and by 585 * doing so any pending as well as remaining uncompleted work will be re-queued 586 * for the next time the job runs.</p> 587 * 588 * <p>This example shows how to construct a JobService that will serially dequeue and 589 * process work that is available for it:</p> 590 * 591 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java 592 * service} 593 * 594 * @return Returns a new {@link JobWorkItem} if there is one pending, otherwise null. 595 * If null is returned, the system will also stop the job if all work has also been completed. 596 * (This means that for correct operation, you must always call dequeueWork() after you have 597 * completed other work, to check either for more work or allow the system to stop the job.) 598 */ dequeueWork()599 public @Nullable JobWorkItem dequeueWork() { 600 try { 601 return getCallback().dequeueWork(getJobId()); 602 } catch (RemoteException e) { 603 throw e.rethrowFromSystemServer(); 604 } 605 } 606 607 /** 608 * Report the completion of executing a {@link JobWorkItem} previously returned by 609 * {@link #dequeueWork()}. This tells the system you are done with the 610 * work associated with that item, so it will not be returned again. Note that if this 611 * is the last work in the queue, completing it here will <em>not</em> finish the overall 612 * job -- for that to happen, you still need to call {@link #dequeueWork()} 613 * again. 614 * 615 * <p>If you are enqueueing work into a job, you must call this method for each piece 616 * of work you process. Do <em>not</em> call 617 * {@link JobService#jobFinished(JobParameters, boolean)} 618 * or else you can lose work in your queue.</p> 619 * 620 * @param work The work you have completed processing, as previously returned by 621 * {@link #dequeueWork()} 622 */ completeWork(@onNull JobWorkItem work)623 public void completeWork(@NonNull JobWorkItem work) { 624 try { 625 if (!getCallback().completeWork(getJobId(), work.getWorkId())) { 626 throw new IllegalArgumentException("Given work is not active: " + work); 627 } 628 } catch (RemoteException e) { 629 throw e.rethrowFromSystemServer(); 630 } 631 } 632 633 /** @hide */ 634 @UnsupportedAppUsage getCallback()635 public IJobCallback getCallback() { 636 return IJobCallback.Stub.asInterface(callback); 637 } 638 JobParameters(Parcel in)639 private JobParameters(Parcel in) { 640 jobId = in.readInt(); 641 mJobNamespace = in.readString(); 642 extras = in.readPersistableBundle(); 643 transientExtras = in.readBundle(); 644 if (in.readInt() != 0) { 645 clipData = ClipData.CREATOR.createFromParcel(in); 646 clipGrantFlags = in.readInt(); 647 } else { 648 clipData = null; 649 clipGrantFlags = 0; 650 } 651 callback = in.readStrongBinder(); 652 overrideDeadlineExpired = in.readInt() == 1; 653 mIsExpedited = in.readBoolean(); 654 mIsUserInitiated = in.readBoolean(); 655 mTriggeredContentUris = in.createTypedArray(Uri.CREATOR); 656 mTriggeredContentAuthorities = in.createStringArray(); 657 if (in.readInt() != 0) { 658 mNetwork = Network.CREATOR.createFromParcel(in); 659 } else { 660 mNetwork = null; 661 } 662 mStopReason = in.readInt(); 663 mInternalStopReason = in.readInt(); 664 debugStopReason = in.readString(); 665 mJobCleanupCallback = null; 666 mCleanable = null; 667 } 668 669 /** @hide */ setNetwork(@ullable Network network)670 public void setNetwork(@Nullable Network network) { 671 mNetwork = network; 672 } 673 674 /** @hide */ setStopReason(@topReason int reason, int internalStopReason, String debugStopReason)675 public void setStopReason(@StopReason int reason, int internalStopReason, 676 String debugStopReason) { 677 mStopReason = reason; 678 mInternalStopReason = internalStopReason; 679 this.debugStopReason = debugStopReason; 680 } 681 682 /** @hide */ initCleaner(JobCleanupCallback jobCleanupCallback)683 public void initCleaner(JobCleanupCallback jobCleanupCallback) { 684 mJobCleanupCallback = jobCleanupCallback; 685 mCleanable = SystemCleaner.cleaner().register(this, mJobCleanupCallback); 686 } 687 688 /** 689 * Lazy initialize the cleaner and enable it 690 * 691 * @hide 692 */ enableCleaner()693 public void enableCleaner() { 694 if (!Flags.handleAbandonedJobs() 695 || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) { 696 return; 697 } 698 // JobParameters objects are passed by reference in local Binder 699 // transactions for clients running as SYSTEM. The life cycle of the 700 // JobParameters objects are no longer controlled by the client. 701 if (Process.myUid() == Process.SYSTEM_UID) { 702 return; 703 } 704 if (mJobCleanupCallback == null) { 705 initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId)); 706 } 707 mJobCleanupCallback.enableCleaner(); 708 } 709 710 /** 711 * Disable the cleaner from running and unregister it 712 * 713 * @hide 714 */ disableCleaner()715 public void disableCleaner() { 716 if (!Flags.handleAbandonedJobs() 717 || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) { 718 return; 719 } 720 if (mJobCleanupCallback != null) { 721 mJobCleanupCallback.disableCleaner(); 722 if (mCleanable != null) { 723 mCleanable.clean(); 724 mCleanable = null; 725 } 726 mJobCleanupCallback = null; 727 } 728 } 729 730 /** @hide */ 731 @VisibleForTesting 732 @Nullable getCleanable()733 public Cleaner.Cleanable getCleanable() { 734 return mCleanable; 735 } 736 737 /** @hide */ 738 @VisibleForTesting 739 @Nullable getJobCleanupCallback()740 public JobCleanupCallback getJobCleanupCallback() { 741 return mJobCleanupCallback; 742 } 743 744 @Override describeContents()745 public int describeContents() { 746 return 0; 747 } 748 749 @Override writeToParcel(Parcel dest, int flags)750 public void writeToParcel(Parcel dest, int flags) { 751 dest.writeInt(jobId); 752 dest.writeString(mJobNamespace); 753 dest.writePersistableBundle(extras); 754 dest.writeBundle(transientExtras); 755 if (clipData != null) { 756 dest.writeInt(1); 757 clipData.writeToParcel(dest, flags); 758 dest.writeInt(clipGrantFlags); 759 } else { 760 dest.writeInt(0); 761 } 762 dest.writeStrongBinder(callback); 763 dest.writeInt(overrideDeadlineExpired ? 1 : 0); 764 dest.writeBoolean(mIsExpedited); 765 dest.writeBoolean(mIsUserInitiated); 766 dest.writeTypedArray(mTriggeredContentUris, flags); 767 dest.writeStringArray(mTriggeredContentAuthorities); 768 if (mNetwork != null) { 769 dest.writeInt(1); 770 mNetwork.writeToParcel(dest, flags); 771 } else { 772 dest.writeInt(0); 773 } 774 dest.writeInt(mStopReason); 775 dest.writeInt(mInternalStopReason); 776 dest.writeString(debugStopReason); 777 } 778 779 /** 780 * JobCleanupCallback is used track JobParameters leak. If the job is started 781 * and jobFinish is not called at the time of garbage collection of JobParameters 782 * instance, it is considered a job leak. Force finish the job. 783 * 784 * @hide 785 */ 786 public static class JobCleanupCallback implements Runnable { 787 private final IJobCallback mCallback; 788 private final int mJobId; 789 private boolean mIsCleanerEnabled; 790 JobCleanupCallback( IJobCallback callback, int jobId)791 public JobCleanupCallback( 792 IJobCallback callback, 793 int jobId) { 794 mCallback = callback; 795 mJobId = jobId; 796 mIsCleanerEnabled = false; 797 } 798 799 /** 800 * Check if the cleaner is enabled 801 * 802 * @hide 803 */ isCleanerEnabled()804 public boolean isCleanerEnabled() { 805 return mIsCleanerEnabled; 806 } 807 808 /** 809 * Enable the cleaner to detect JobParameter leak 810 * 811 * @hide 812 */ enableCleaner()813 public void enableCleaner() { 814 mIsCleanerEnabled = true; 815 } 816 817 /** 818 * Disable the cleaner from running. 819 * 820 * @hide 821 */ disableCleaner()822 public void disableCleaner() { 823 mIsCleanerEnabled = false; 824 } 825 826 /** @hide */ 827 @Override run()828 public void run() { 829 if (!isCleanerEnabled()) { 830 return; 831 } 832 try { 833 mCallback.handleAbandonedJob(mJobId); 834 } catch (Exception e) { 835 Log.wtf(TAG, "Could not destroy running job", e); 836 } 837 } 838 } 839 840 public static final @android.annotation.NonNull Creator<JobParameters> CREATOR = new Creator<JobParameters>() { 841 @Override 842 public JobParameters createFromParcel(Parcel in) { 843 return new JobParameters(in); 844 } 845 846 @Override 847 public JobParameters[] newArray(int size) { 848 return new JobParameters[size]; 849 } 850 }; 851 } 852