1 /* 2 * Copyright 2018 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 androidx.work; 18 19 import static androidx.work.WorkInfo.STOP_REASON_NOT_STOPPED; 20 21 import android.content.Context; 22 import android.net.Network; 23 import android.net.Uri; 24 25 import androidx.annotation.IntRange; 26 import androidx.annotation.MainThread; 27 import androidx.annotation.RequiresApi; 28 import androidx.annotation.RestrictTo; 29 import androidx.concurrent.futures.CallbackToFutureAdapter; 30 import androidx.work.impl.utils.taskexecutor.TaskExecutor; 31 32 import com.google.common.util.concurrent.ListenableFuture; 33 34 import org.jspecify.annotations.NonNull; 35 import org.jspecify.annotations.Nullable; 36 37 import java.util.List; 38 import java.util.Set; 39 import java.util.UUID; 40 import java.util.concurrent.Executor; 41 import java.util.concurrent.TimeUnit; 42 import java.util.concurrent.atomic.AtomicInteger; 43 44 /** 45 * A class that can perform work asynchronously in {@link WorkManager}. For most cases, we 46 * recommend using {@link Worker}, which offers a simple synchronous API that is executed on a 47 * pre-specified background thread. 48 * <p> 49 * ListenableWorker classes are instantiated at runtime by the {@link WorkerFactory} specified in 50 * the {@link Configuration}. The {@link #startWork()} method is called on the main thread. 51 * <p> 52 * In case the work is preempted and later restarted for any reason, a new instance of 53 * ListenableWorker is created. This means that {@code startWork} is called exactly once per 54 * ListenableWorker instance. A new ListenableWorker is created if a unit of work needs to be 55 * rerun. 56 * <p> 57 * A ListenableWorker is given a maximum of ten minutes to finish its execution and return a 58 * {@link Result}. After this time has expired, the worker will be signalled to stop and its 59 * {@code com.google.common.util.concurrent.ListenableFuture} will be cancelled. 60 * <p> 61 * Exercise caution when <a href="WorkManager.html#worker_class_names">renaming or removing 62 * ListenableWorkers</a> from your codebase. 63 */ 64 65 public abstract class ListenableWorker { 66 67 private @NonNull Context mAppContext; 68 private @NonNull WorkerParameters mWorkerParams; 69 70 private final AtomicInteger mStopReason = new AtomicInteger(STOP_REASON_NOT_STOPPED); 71 72 private boolean mUsed; 73 74 /** 75 * @param appContext The application {@link Context} 76 * @param workerParams Parameters to setup the internal state of this worker 77 */ ListenableWorker(@onNull Context appContext, @NonNull WorkerParameters workerParams)78 public ListenableWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) { 79 // Actually make sure we don't get nulls. 80 if (appContext == null) { 81 throw new IllegalArgumentException("Application Context is null"); 82 } 83 84 if (workerParams == null) { 85 throw new IllegalArgumentException("WorkerParameters is null"); 86 } 87 88 mAppContext = appContext; 89 mWorkerParams = workerParams; 90 } 91 92 /** 93 * Gets the application {@link android.content.Context}. 94 * 95 * @return The application {@link android.content.Context} 96 */ getApplicationContext()97 public final @NonNull Context getApplicationContext() { 98 return mAppContext; 99 } 100 101 /** 102 * Gets the ID of the {@link WorkRequest} that created this Worker. 103 * 104 * @return The ID of the creating {@link WorkRequest} 105 */ getId()106 public final @NonNull UUID getId() { 107 return mWorkerParams.getId(); 108 } 109 110 /** 111 * Gets the input data. Note that in the case that there are multiple prerequisites for this 112 * Worker, the input data has been run through an {@link InputMerger}. 113 * 114 * @return The input data for this work 115 * @see OneTimeWorkRequest.Builder#setInputMerger(Class) 116 */ getInputData()117 public final @NonNull Data getInputData() { 118 return mWorkerParams.getInputData(); 119 } 120 121 /** 122 * Gets a {@link java.util.Set} of tags associated with this Worker's {@link WorkRequest}. 123 * 124 * @return The {@link java.util.Set} of tags associated with this Worker's {@link WorkRequest} 125 * @see WorkRequest.Builder#addTag(String) 126 */ getTags()127 public final @NonNull Set<String> getTags() { 128 return mWorkerParams.getTags(); 129 } 130 131 /** 132 * Gets the list of content {@link android.net.Uri}s that caused this Worker to execute. See 133 * {@code JobParameters#getTriggeredContentUris()} for relevant {@code JobScheduler} code. 134 * 135 * @return The list of content {@link android.net.Uri}s that caused this Worker to execute 136 * @see Constraints.Builder#addContentUriTrigger(android.net.Uri, boolean) 137 */ 138 @RequiresApi(24) getTriggeredContentUris()139 public final @NonNull List<Uri> getTriggeredContentUris() { 140 return mWorkerParams.getTriggeredContentUris(); 141 } 142 143 /** 144 * Gets the list of content authorities that caused this Worker to execute. See 145 * {@code JobParameters#getTriggeredContentAuthorities()} for relevant {@code JobScheduler} 146 * code. 147 * 148 * @return The list of content authorities that caused this Worker to execute 149 */ 150 @RequiresApi(24) getTriggeredContentAuthorities()151 public final @NonNull List<String> getTriggeredContentAuthorities() { 152 return mWorkerParams.getTriggeredContentAuthorities(); 153 } 154 155 /** 156 * Gets the {@link android.net.Network} to use for this Worker. This method returns 157 * {@code null} if there is no network needed for this work request. 158 * 159 * @return The {@link android.net.Network} specified by the OS to be used with this Worker 160 */ 161 @RequiresApi(28) getNetwork()162 public final @Nullable Network getNetwork() { 163 return mWorkerParams.getNetwork(); 164 } 165 166 /** 167 * Gets the current run attempt count for this work. Note that for periodic work, this value 168 * gets reset between periods. 169 * 170 * @return The current run attempt count for this work. 171 */ 172 @IntRange(from = 0) getRunAttemptCount()173 public final int getRunAttemptCount() { 174 return mWorkerParams.getRunAttemptCount(); 175 } 176 177 /** 178 * Override this method to start your actual background processing. This method is called on 179 * the main thread. 180 * <p> 181 * A ListenableWorker has a well defined 182 * <a href="https://d.android.com/reference/android/app/job/JobScheduler">execution window</a> 183 * to to finish its execution and return a {@link Result}. After this time has expired, the 184 * worker will be signalled to stop and its 185 * {@code com.google.common.util.concurrent.ListenableFuture} will be cancelled. 186 * <p> 187 * The future will also be cancelled if this worker is stopped for any reason 188 * (see {@link #onStopped()}). 189 * 190 * @return A {@code com.google.common.util.concurrent.ListenableFuture} with the 191 * {@link Result} of the computation. If you 192 * cancel this Future, WorkManager will treat this unit of work as failed. 193 */ 194 @MainThread startWork()195 public abstract @NonNull ListenableFuture<Result> startWork(); 196 197 /** 198 * Updates {@link ListenableWorker} progress. 199 * 200 * @param data The progress {@link Data} 201 * @return A {@code com.google.common.util.concurrent.ListenableFuture} which resolves 202 * after progress is persisted. Cancelling this future is a no-op. 203 */ setProgressAsync(@onNull Data data)204 public @NonNull ListenableFuture<Void> setProgressAsync(@NonNull Data data) { 205 return mWorkerParams.getProgressUpdater() 206 .updateProgress(getApplicationContext(), getId(), data); 207 } 208 209 /** 210 * This specifies that the {@link WorkRequest} is long-running or otherwise important. In 211 * this case, WorkManager provides a signal to the OS that the process should be kept alive 212 * if possible while this work is executing. 213 * <p> 214 * Calls to {@code setForegroundAsync} *must* complete before a {@link ListenableWorker} 215 * signals completion by returning a {@link Result}. 216 * <p> 217 * Under the hood, WorkManager manages and runs a foreground service on your behalf to 218 * execute this WorkRequest, showing the notification provided in 219 * {@link ForegroundInfo}. 220 * <p> 221 * Calling {@code setForegroundAsync} will fail with an 222 * {@link IllegalStateException} when the process is subject to foreground 223 * service restrictions. Consider using 224 * {@link WorkRequest.Builder#setExpedited(OutOfQuotaPolicy)} and 225 * {@link ListenableWorker#getForegroundInfoAsync()} instead. 226 * 227 * @param foregroundInfo The {@link ForegroundInfo} 228 * @return A {@code com.google.common.util.concurrent.ListenableFuture} which resolves after 229 * the {@link ListenableWorker} transitions to running in the context of a foreground 230 * {@link android.app.Service}. 231 */ setForegroundAsync( @onNull ForegroundInfo foregroundInfo)232 public final @NonNull ListenableFuture<Void> setForegroundAsync( 233 @NonNull ForegroundInfo foregroundInfo) { 234 return mWorkerParams.getForegroundUpdater() 235 .setForegroundAsync(getApplicationContext(), getId(), foregroundInfo); 236 } 237 238 /** 239 * Return an instance of {@link ForegroundInfo} if the {@link WorkRequest} is important to 240 * the user. In this case, WorkManager provides a signal to the OS that the process should 241 * be kept alive while this work is executing. 242 * <p> 243 * Prior to Android S, WorkManager manages and runs a foreground service on your behalf to 244 * execute the WorkRequest, showing the notification provided in the {@link ForegroundInfo}. 245 * To update this notification subsequently, the application can use 246 * {@link android.app.NotificationManager}. 247 * <p> 248 * Starting in Android S and above, WorkManager manages this WorkRequest using an immediate job. 249 * 250 * @return A {@code com.google.common.util.concurrent.ListenableFuture} of 251 * {@link ForegroundInfo} instance if the WorkRequest is marked immediate. For more 252 * information look at {@link WorkRequest.Builder#setExpedited(OutOfQuotaPolicy)}. 253 */ getForegroundInfoAsync()254 public @NonNull ListenableFuture<ForegroundInfo> getForegroundInfoAsync() { 255 return CallbackToFutureAdapter.getFuture((completer) -> { 256 String message = 257 "Expedited WorkRequests require a ListenableWorker to provide an implementation" 258 + " for`getForegroundInfoAsync()`"; 259 completer.setException(new IllegalStateException(message)); 260 return "default failing getForegroundInfoAsync"; 261 }); 262 } 263 264 /** 265 * Returns {@code true} if this Worker has been told to stop. This could be because of an 266 * explicit cancellation signal by the user, or because the system has decided to preempt the 267 * task. In these cases, the results of the work will be ignored by WorkManager and it is safe 268 * to stop the computation. WorkManager will retry the work at a later time if necessary. 269 * 270 * @return {@code true} if the work operation has been interrupted 271 */ isStopped()272 public final boolean isStopped() { 273 return mStopReason.get() != STOP_REASON_NOT_STOPPED; 274 } 275 276 /** 277 * Returns a reason why this worker has been stopped. Return values match values of 278 * {@code JobParameters.STOP_REASON_*} constants, e.g. 279 * {@link android.app.job.JobParameters#STOP_REASON_CONSTRAINT_CHARGING} or 280 * {@link WorkInfo#STOP_REASON_UNKNOWN} 281 * <p> 282 * If a worker hasn't been stopped, {@link WorkInfo#STOP_REASON_NOT_STOPPED} is returned. 283 */ 284 @StopReason 285 @RequiresApi(31) getStopReason()286 public final int getStopReason() { 287 return mStopReason.get(); 288 } 289 290 /** 291 */ 292 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) stop(int reason)293 public final void stop(int reason) { 294 if (mStopReason.compareAndSet(STOP_REASON_NOT_STOPPED, reason)) { 295 onStopped(); 296 } 297 } 298 299 /** 300 * This method is invoked when this Worker has been told to stop. At this point, the 301 * {@code com.google.common.util.concurrent.ListenableFuture} returned by the instance of 302 * {@link #startWork()} is also cancelled. This could happen due to an explicit cancellation 303 * signal by the user, or because the system has decided to preempt the task. In these 304 * cases, the results of the work will be ignored by WorkManager. All processing in this 305 * method should be lightweight - there are no contractual guarantees about which thread will 306 * invoke this call, so this should not be a long-running or blocking operation. 307 */ onStopped()308 public void onStopped() { 309 // Do nothing by default. 310 } 311 312 /** 313 * @return {@code true} if this worker has already been marked as used 314 * @see #setUsed() 315 */ 316 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) isUsed()317 public final boolean isUsed() { 318 return mUsed; 319 } 320 321 /** 322 * Marks this worker as used to make sure we enforce the policy that workers can only be used 323 * once and that WorkerFactories return a new instance each time. 324 * @see #isUsed() 325 */ 326 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) setUsed()327 public final void setUsed() { 328 mUsed = true; 329 } 330 331 /** 332 */ 333 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getBackgroundExecutor()334 public @NonNull Executor getBackgroundExecutor() { 335 return mWorkerParams.getBackgroundExecutor(); 336 } 337 338 /** 339 */ 340 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getTaskExecutor()341 public @NonNull TaskExecutor getTaskExecutor() { 342 return mWorkerParams.getTaskExecutor(); 343 } 344 345 /** 346 */ 347 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getWorkerFactory()348 public @NonNull WorkerFactory getWorkerFactory() { 349 return mWorkerParams.getWorkerFactory(); 350 } 351 352 /** 353 * The result of a {@link ListenableWorker}'s computation. Call {@link #success()}, 354 * {@link #failure()}, or {@link #retry()} or one of their variants to generate an object 355 * indicating what happened in your background work. 356 */ 357 public abstract static class Result { 358 /** 359 * Returns an instance of {@link Result} that can be used to indicate that the work 360 * completed successfully. Any work that depends on this can be executed as long as all of 361 * its other dependencies and constraints are met. 362 * 363 * @return An instance of {@link Result} indicating successful execution of work 364 */ success()365 public static @NonNull Result success() { 366 return new Success(); 367 } 368 369 /** 370 * Returns an instance of {@link Result} that can be used to indicate that the work 371 * completed successfully. Any work that depends on this can be executed as long as all of 372 * its other dependencies and constraints are met. 373 * 374 * @param outputData A {@link Data} object that will be merged into the input Data of any 375 * OneTimeWorkRequest that is dependent on this work 376 * @return An instance of {@link Result} indicating successful execution of work 377 */ success(@onNull Data outputData)378 public static @NonNull Result success(@NonNull Data outputData) { 379 return new Success(outputData); 380 } 381 382 /** 383 * Returns an instance of {@link Result} that can be used to indicate that the work 384 * encountered a transient failure and should be retried with backoff specified in 385 * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}. 386 * 387 * @return An instance of {@link Result} indicating that the work needs to be retried 388 */ retry()389 public static @NonNull Result retry() { 390 return new Retry(); 391 } 392 393 /** 394 * Returns an instance of {@link Result} that can be used to indicate that the work 395 * completed with a permanent failure. Any work that depends on this will also be marked as 396 * failed and will not be run. <b>If you need child workers to run, you need to use 397 * {@link #success()} or {@link #success(Data) success(Data)}</b>; failure indicates a 398 * permanent stoppage of the chain of work. 399 * 400 * @return An instance of {@link Result} indicating failure when executing work 401 */ failure()402 public static @NonNull Result failure() { 403 return new Failure(); 404 } 405 406 /** 407 * Returns an instance of {@link Result} that can be used to indicate that the work 408 * completed with a permanent failure. Any work that depends on this will also be marked as 409 * failed and will not be run. <b>If you need child workers to run, you need to use 410 * {@link #success()} or {@link #success(Data) success(Data)}</b>; failure indicates a 411 * permanent stoppage of the chain of work. 412 * 413 * @param outputData A {@link Data} object that can be used to keep track of why the work 414 * failed 415 * @return An instance of {@link Result} indicating failure when executing work 416 */ failure(@onNull Data outputData)417 public static @NonNull Result failure(@NonNull Data outputData) { 418 return new Failure(outputData); 419 } 420 421 /** 422 * @return The output {@link Data} which will be merged into the input {@link Data} of 423 * any {@link OneTimeWorkRequest} that is dependent on this work request. 424 */ getOutputData()425 public abstract @NonNull Data getOutputData(); 426 427 /** 428 */ 429 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) Result()430 Result() { 431 // Restricting access to the constructor, to give Result a sealed class 432 // like behavior. 433 } 434 435 /** 436 * Used to indicate that the work completed successfully. Any work that depends on this 437 * can be executed as long as all of its other dependencies and constraints are met. 438 * 439 */ 440 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 441 public static final class Success extends Result { 442 private final Data mOutputData; 443 Success()444 public Success() { 445 this(Data.EMPTY); 446 } 447 448 /** 449 * @param outputData A {@link Data} object that will be merged into the input Data of 450 * any OneTimeWorkRequest that is dependent on this work 451 */ Success(@onNull Data outputData)452 public Success(@NonNull Data outputData) { 453 super(); 454 mOutputData = outputData; 455 } 456 457 @Override getOutputData()458 public @NonNull Data getOutputData() { 459 return mOutputData; 460 } 461 462 @Override equals(Object o)463 public boolean equals(Object o) { 464 if (this == o) return true; 465 if (o == null || getClass() != o.getClass()) return false; 466 467 Success success = (Success) o; 468 469 return mOutputData.equals(success.mOutputData); 470 } 471 472 @Override hashCode()473 public int hashCode() { 474 String name = Success.class.getName(); 475 return 31 * name.hashCode() + mOutputData.hashCode(); 476 } 477 478 @Override toString()479 public @NonNull String toString() { 480 return "Success {" + "mOutputData=" + mOutputData + '}'; 481 } 482 } 483 484 /** 485 * Used to indicate that the work completed with a permanent failure. Any work that depends 486 * on this will also be marked as failed and will not be run. <b>If you need child workers 487 * to run, you need to return {@link Result.Success}</b>; failure indicates a permanent 488 * stoppage of the chain of work. 489 * 490 */ 491 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 492 public static final class Failure extends Result { 493 private final Data mOutputData; 494 Failure()495 public Failure() { 496 this(Data.EMPTY); 497 } 498 499 /** 500 * @param outputData A {@link Data} object that can be used to keep track of why the 501 * work failed 502 */ Failure(@onNull Data outputData)503 public Failure(@NonNull Data outputData) { 504 super(); 505 mOutputData = outputData; 506 } 507 508 @Override getOutputData()509 public @NonNull Data getOutputData() { 510 return mOutputData; 511 } 512 513 @Override equals(Object o)514 public boolean equals(Object o) { 515 if (this == o) return true; 516 if (o == null || getClass() != o.getClass()) return false; 517 518 Failure failure = (Failure) o; 519 520 return mOutputData.equals(failure.mOutputData); 521 } 522 523 @Override hashCode()524 public int hashCode() { 525 String name = Failure.class.getName(); 526 return 31 * name.hashCode() + mOutputData.hashCode(); 527 } 528 529 @Override toString()530 public @NonNull String toString() { 531 return "Failure {" + "mOutputData=" + mOutputData + '}'; 532 } 533 } 534 535 /** 536 * Used to indicate that the work encountered a transient failure and should be retried with 537 * backoff specified in 538 * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}. 539 * 540 */ 541 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 542 public static final class Retry extends Result { Retry()543 public Retry() { 544 super(); 545 } 546 547 @Override equals(Object o)548 public boolean equals(Object o) { 549 if (this == o) return true; 550 // We are treating all instances of Retry as equivalent. 551 return o != null && getClass() == o.getClass(); 552 } 553 554 @Override hashCode()555 public int hashCode() { 556 String name = Retry.class.getName(); 557 return name.hashCode(); 558 } 559 560 @Override getOutputData()561 public @NonNull Data getOutputData() { 562 return Data.EMPTY; 563 } 564 565 566 @Override toString()567 public @NonNull String toString() { 568 return "Retry"; 569 } 570 } 571 } 572 } 573