blob: 2203618d479335206d1dd03df816f4235e145fa4 [file] [log] [blame]
Rahul Ravikumar83f13f32018-07-31 16:50:28 -07001/*
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
17package androidx.work;
18
Oussama Ben Abdelbakif825eb52018-12-04 16:17:00 -050019import android.annotation.SuppressLint;
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070020import android.content.Context;
21import android.net.Network;
22import android.net.Uri;
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070023
Sumir Kataria2cdf00b2019-05-14 16:08:41 -070024import androidx.annotation.IntRange;
Rahul Ravikumare5356662019-03-04 16:22:14 -080025import androidx.annotation.Keep;
26import androidx.annotation.MainThread;
27import androidx.annotation.NonNull;
28import androidx.annotation.Nullable;
29import androidx.annotation.RequiresApi;
30import androidx.annotation.RestrictTo;
Sumir Kataria61856612018-11-04 21:08:35 -080031import androidx.work.impl.utils.taskexecutor.TaskExecutor;
32
Sergey Vasilinetsd0ac2cc2018-09-12 11:23:16 -070033import com.google.common.util.concurrent.ListenableFuture;
34
Rahul Ravikumarb3a76ba2018-10-12 14:28:06 -070035import java.util.List;
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070036import java.util.Set;
37import java.util.UUID;
Sumir Katariaf82a3d62018-09-12 14:18:23 -070038import java.util.concurrent.Executor;
Sumir Katariab18241b2018-12-10 12:34:45 -080039import java.util.concurrent.TimeUnit;
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070040
41/**
Sumir Kataria74a5a902018-11-19 16:47:24 -080042 * A class that can perform work asynchronously in {@link WorkManager}. For most cases, we
43 * recommend using {@link Worker}, which offers a simple synchronous API that is executed on a
44 * pre-specified background thread.
Sumir Kataria6dae2942018-11-19 10:16:51 -080045 * <p>
46 * ListenableWorker classes are instantiated at runtime by the {@link WorkerFactory} specified in
47 * the {@link Configuration}. The {@link #startWork()} method is called on the main thread.
Sumir Kataria74a5a902018-11-19 16:47:24 -080048 * <p>
49 * In case the work is preempted and later restarted for any reason, a new instance of
Sumir Kataria6dae2942018-11-19 10:16:51 -080050 * ListenableWorker is created. This means that {@code startWork} is called exactly once per
51 * ListenableWorker instance. A new ListenableWorker is created if a unit of work needs to be
52 * rerun.
Sumir Katariab4db1752018-12-06 15:15:51 -080053 * <p>
54 * A ListenableWorker is given a maximum of ten minutes to finish its execution and return a
55 * {@link Result}. After this time has expired, the worker will be signalled to stop and its
56 * {@link ListenableFuture} will be cancelled.
Sumir Kataria4bef2b92019-04-29 16:35:56 -070057 * <p>
58 * Exercise caution when <a href="WorkManager.html#worker_class_names">renaming or removing
59 * ListenableWorkers</a> from your codebase.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070060 */
Sumir Kataria92712792018-11-21 13:42:58 -080061
Sumir Kataria32f10f672018-09-28 15:00:45 -070062public abstract class ListenableWorker {
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070063
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070064 private @NonNull Context mAppContext;
Sumir Katariae3338d82018-09-13 13:16:25 -070065 private @NonNull WorkerParameters mWorkerParams;
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070066
67 private volatile boolean mStopped;
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070068
Sumir Kataria768d1602018-09-17 14:09:06 -070069 private boolean mUsed;
70
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070071 /**
Sumir Katariae3338d82018-09-13 13:16:25 -070072 * @param appContext The application {@link Context}
73 * @param workerParams Parameters to setup the internal state of this worker
74 */
Rahul Ravikumarb3909252018-09-25 10:21:22 -070075 @Keep
Oussama Ben Abdelbakif825eb52018-12-04 16:17:00 -050076 @SuppressLint("BanKeepAnnotation")
Sumir Kataria32f10f672018-09-28 15:00:45 -070077 public ListenableWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {
Sumir Katariacda13652018-09-28 14:17:48 -070078 // Actually make sure we don't get nulls.
79 if (appContext == null) {
Sumir Kataria40394d92018-09-28 15:29:16 -070080 throw new IllegalArgumentException("Application Context is null");
Sumir Katariacda13652018-09-28 14:17:48 -070081 }
82
83 if (workerParams == null) {
Sumir Kataria40394d92018-09-28 15:29:16 -070084 throw new IllegalArgumentException("WorkerParameters is null");
Sumir Katariacda13652018-09-28 14:17:48 -070085 }
86
Sumir Katariae3338d82018-09-13 13:16:25 -070087 mAppContext = appContext;
88 mWorkerParams = workerParams;
89 }
90
91 /**
Rahul Ravikumar83f13f32018-07-31 16:50:28 -070092 * Gets the application {@link android.content.Context}.
93 *
94 * @return The application {@link android.content.Context}
95 */
96 public final @NonNull Context getApplicationContext() {
97 return mAppContext;
98 }
99
100 /**
101 * Gets the ID of the {@link WorkRequest} that created this Worker.
102 *
103 * @return The ID of the creating {@link WorkRequest}
104 */
105 public final @NonNull UUID getId() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700106 return mWorkerParams.getId();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700107 }
108
109 /**
110 * Gets the input data. Note that in the case that there are multiple prerequisites for this
111 * Worker, the input data has been run through an {@link InputMerger}.
112 *
113 * @return The input data for this work
114 * @see OneTimeWorkRequest.Builder#setInputMerger(Class)
115 */
116 public final @NonNull Data getInputData() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700117 return mWorkerParams.getInputData();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700118 }
119
120 /**
121 * Gets a {@link java.util.Set} of tags associated with this Worker's {@link WorkRequest}.
122 *
123 * @return The {@link java.util.Set} of tags associated with this Worker's {@link WorkRequest}
124 * @see WorkRequest.Builder#addTag(String)
125 */
126 public final @NonNull Set<String> getTags() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700127 return mWorkerParams.getTags();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700128 }
129
130 /**
Sumir Kataria74a5a902018-11-19 16:47:24 -0800131 * Gets the list of content {@link android.net.Uri}s that caused this Worker to execute. See
132 * {@code JobParameters#getTriggeredContentUris()} for relevant {@code JobScheduler} code.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700133 *
Rahul Ravikumarb3a76ba2018-10-12 14:28:06 -0700134 * @return The list of content {@link android.net.Uri}s that caused this Worker to execute
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700135 * @see Constraints.Builder#addContentUriTrigger(android.net.Uri, boolean)
136 */
137 @RequiresApi(24)
Sumir Kataria3e65fa82018-11-13 14:51:07 -0800138 public final @NonNull List<Uri> getTriggeredContentUris() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700139 return mWorkerParams.getTriggeredContentUris();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700140 }
141
142 /**
Sumir Kataria74a5a902018-11-19 16:47:24 -0800143 * Gets the list of content authorities that caused this Worker to execute. See
144 * {@code JobParameters#getTriggeredContentAuthorities()} for relevant {@code JobScheduler}
145 * code.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700146 *
Rahul Ravikumarb3a76ba2018-10-12 14:28:06 -0700147 * @return The list of content authorities that caused this Worker to execute
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700148 */
149 @RequiresApi(24)
Sumir Kataria3e65fa82018-11-13 14:51:07 -0800150 public final @NonNull List<String> getTriggeredContentAuthorities() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700151 return mWorkerParams.getTriggeredContentAuthorities();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700152 }
153
154 /**
Sumir Kataria74a5a902018-11-19 16:47:24 -0800155 * Gets the {@link android.net.Network} to use for this Worker. This method returns
156 * {@code null} if there is no network needed for this work request.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700157 *
158 * @return The {@link android.net.Network} specified by the OS to be used with this Worker
159 */
160 @RequiresApi(28)
161 public final @Nullable Network getNetwork() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700162 return mWorkerParams.getNetwork();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700163 }
164
165 /**
Sumir Kataria74a5a902018-11-19 16:47:24 -0800166 * Gets the current run attempt count for this work. Note that for periodic work, this value
167 * gets reset between periods.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700168 *
169 * @return The current run attempt count for this work.
170 */
Sumir Kataria2cdf00b2019-05-14 16:08:41 -0700171 @IntRange(from = 0)
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700172 public final int getRunAttemptCount() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700173 return mWorkerParams.getRunAttemptCount();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700174 }
175
176 /**
Rahul Ravikumarfe31ad72018-09-27 13:57:39 -0700177 * Override this method to start your actual background processing. This method is called on
178 * the main thread.
Sumir Katariab4db1752018-12-06 15:15:51 -0800179 * <p>
180 * A ListenableWorker is given a maximum of ten minutes to finish its execution and return a
181 * {@link Result}. After this time has expired, the worker will be signalled to stop and its
182 * {@link ListenableFuture} will be cancelled.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700183 *
Rahul Ravikumardc4cfe72018-11-28 16:18:59 -0800184 * @return A {@link ListenableFuture} with the {@link Result} of the computation. If you
Sumir Kataria328453cb2018-10-09 11:31:33 -0700185 * cancel this Future, WorkManager will treat this unit of work as failed.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700186 */
Rahul Ravikumarfe31ad72018-09-27 13:57:39 -0700187 @MainThread
Rahul Ravikumardc4cfe72018-11-28 16:18:59 -0800188 public abstract @NonNull ListenableFuture<Result> startWork();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700189
190 /**
Rahul Ravikumar05ee9f82019-07-15 10:31:14 -0700191 * Updates {@link ListenableWorker} progress.
192 *
193 * @param data The progress {@link Data}
194 * @return A {@link ListenableFuture} which resolves after progress is persisted.
195 * Cancelling this future is a no-op.
196 */
197 @NonNull
Rahul Ravikumarf9b0e2a2019-07-22 15:22:24 -0700198 public final ListenableFuture<Void> setProgressAsync(@NonNull Data data) {
Rahul Ravikumar05ee9f82019-07-15 10:31:14 -0700199 return mWorkerParams.getProgressUpdater()
200 .updateProgress(getApplicationContext(), getId(), data);
201 }
202
203 /**
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700204 * Returns {@code true} if this Worker has been told to stop. This could be because of an
205 * explicit cancellation signal by the user, or because the system has decided to preempt the
206 * task. In these cases, the results of the work will be ignored by WorkManager and it is safe
Sumir Kataria74a5a902018-11-19 16:47:24 -0800207 * to stop the computation. WorkManager will retry the work at a later time if necessary.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700208 *
209 * @return {@code true} if the work operation has been interrupted
210 */
211 public final boolean isStopped() {
212 return mStopped;
213 }
214
215 /**
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700216 * @hide
217 */
218 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Rahul Ravikumar22988d32018-11-06 12:42:03 -0800219 public final void stop() {
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700220 mStopped = true;
Rahul Ravikumar22988d32018-11-06 12:42:03 -0800221 onStopped();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700222 }
223
224 /**
225 * This method is invoked when this Worker has been told to stop. This could happen due
226 * to an explicit cancellation signal by the user, or because the system has decided to preempt
227 * the task. In these cases, the results of the work will be ignored by WorkManager. All
228 * processing in this method should be lightweight - there are no contractual guarantees about
229 * which thread will invoke this call, so this should not be a long-running or blocking
230 * operation.
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700231 */
Rahul Ravikumar22988d32018-11-06 12:42:03 -0800232 public void onStopped() {
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700233 // Do nothing by default.
234 }
235
236 /**
Sumir Kataria768d1602018-09-17 14:09:06 -0700237 * @return {@code true} if this worker has already been marked as used
238 * @see #setUsed()
239 * @hide
240 */
241 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
242 public final boolean isUsed() {
243 return mUsed;
244 }
245
246 /**
247 * Marks this worker as used to make sure we enforce the policy that workers can only be used
248 * once and that WorkerFactories return a new instance each time.
249 * @see #isUsed()
250 * @hide
251 */
252 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
253 public final void setUsed() {
254 mUsed = true;
255 }
256
257 /**
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700258 * @hide
259 */
260 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Sumir Katariaf82a3d62018-09-12 14:18:23 -0700261 public @NonNull Executor getBackgroundExecutor() {
Sumir Katariae3338d82018-09-13 13:16:25 -0700262 return mWorkerParams.getBackgroundExecutor();
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700263 }
Sumir Katariaf7554d2b2018-09-14 13:47:29 -0700264
265 /**
266 * @hide
267 */
268 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Sumir Kataria61856612018-11-04 21:08:35 -0800269 public @NonNull TaskExecutor getTaskExecutor() {
270 return mWorkerParams.getTaskExecutor();
271 }
272
273 /**
274 * @hide
275 */
276 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Sumir Katariaf7554d2b2018-09-14 13:47:29 -0700277 public @NonNull WorkerFactory getWorkerFactory() {
278 return mWorkerParams.getWorkerFactory();
279 }
Sumir Katariab18241b2018-12-10 12:34:45 -0800280
281 /**
282 * The result of a {@link ListenableWorker}'s computation. Call {@link #success()},
283 * {@link #failure()}, or {@link #retry()} or one of their variants to generate an object
284 * indicating what happened in your background work.
285 */
286 public abstract static class Result {
287 /**
288 * Returns an instance of {@link Result} that can be used to indicate that the work
289 * completed successfully. Any work that depends on this can be executed as long as all of
290 * its other dependencies and constraints are met.
291 *
292 * @return An instance of {@link Result} indicating successful execution of work
293 */
294 @NonNull
295 public static Result success() {
296 return new Success();
297 }
298
299 /**
300 * Returns an instance of {@link Result} that can be used to indicate that the work
301 * completed successfully. Any work that depends on this can be executed as long as all of
302 * its other dependencies and constraints are met.
303 *
304 * @param outputData A {@link Data} object that will be merged into the input Data of any
305 * OneTimeWorkRequest that is dependent on this work
306 * @return An instance of {@link Result} indicating successful execution of work
307 */
308 @NonNull
309 public static Result success(@NonNull Data outputData) {
310 return new Success(outputData);
311 }
312
313 /**
314 * Returns an instance of {@link Result} that can be used to indicate that the work
315 * encountered a transient failure and should be retried with backoff specified in
316 * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}.
317 *
318 * @return An instance of {@link Result} indicating that the work needs to be retried
319 */
320 @NonNull
321 public static Result retry() {
322 return new Retry();
323 }
324
325 /**
326 * Returns an instance of {@link Result} that can be used to indicate that the work
327 * completed with a permanent failure. Any work that depends on this will also be marked as
328 * failed and will not be run. <b>If you need child workers to run, you need to use
329 * {@link #success()} or {@link #success(Data)}</b>; failure indicates a permanent stoppage
330 * of the chain of work.
331 *
332 * @return An instance of {@link Result} indicating failure when executing work
333 */
334 @NonNull
335 public static Result failure() {
336 return new Failure();
337 }
338
339 /**
340 * Returns an instance of {@link Result} that can be used to indicate that the work
341 * completed with a permanent failure. Any work that depends on this will also be marked as
342 * failed and will not be run. <b>If you need child workers to run, you need to use
343 * {@link #success()} or {@link #success(Data)}</b>; failure indicates a permanent stoppage
344 * of the chain of work.
345 *
346 * @param outputData A {@link Data} object that can be used to keep track of why the work
347 * failed
348 * @return An instance of {@link Result} indicating failure when executing work
349 */
350 @NonNull
351 public static Result failure(@NonNull Data outputData) {
352 return new Failure(outputData);
353 }
354
355 /**
356 * @hide
357 */
358 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
359 Result() {
360 // Restricting access to the constructor, to give Result a sealed class
361 // like behavior.
362 }
363
364 /**
365 * Used to indicate that the work completed successfully. Any work that depends on this
366 * can be executed as long as all of its other dependencies and constraints are met.
367 *
368 * @hide
369 */
370 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
371 public static final class Success extends Result {
372 private final Data mOutputData;
373
374 public Success() {
375 this(Data.EMPTY);
376 }
377
378 /**
379 * @param outputData A {@link Data} object that will be merged into the input Data of
380 * any OneTimeWorkRequest that is dependent on this work
381 */
382 public Success(@NonNull Data outputData) {
383 super();
384 mOutputData = outputData;
385 }
386
387 /**
388 * @hide
389 */
390 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
391 public Data getOutputData() {
392 return mOutputData;
393 }
Rahul Ravikumare20d9442018-12-20 11:14:49 -0800394
395 @Override
396 public boolean equals(Object o) {
397 if (this == o) return true;
398 if (o == null || getClass() != o.getClass()) return false;
399
400 Success success = (Success) o;
401
402 return mOutputData.equals(success.mOutputData);
403 }
404
405 @Override
406 public int hashCode() {
407 String name = Success.class.getName();
408 return 31 * name.hashCode() + mOutputData.hashCode();
409 }
Rahul Ravikumar7f149de2019-01-29 16:19:29 -0800410
411 @Override
412 public String toString() {
413 return "Success {" + "mOutputData=" + mOutputData + '}';
414 }
Sumir Katariab18241b2018-12-10 12:34:45 -0800415 }
416
417 /**
418 * Used to indicate that the work completed with a permanent failure. Any work that depends
419 * on this will also be marked as failed and will not be run. <b>If you need child workers
420 * to run, you need to return {@link Result.Success}</b>; failure indicates a permanent
421 * stoppage of the chain of work.
422 *
423 * @hide
424 */
425 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
426 public static final class Failure extends Result {
427 private final Data mOutputData;
428
429 public Failure() {
430 this(Data.EMPTY);
431 }
432
433 /**
434 * @param outputData A {@link Data} object that can be used to keep track of why the
435 * work failed
436 */
437 public Failure(@NonNull Data outputData) {
438 super();
439 mOutputData = outputData;
440 }
441
442 /**
443 * @hide
444 */
445 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
446 public Data getOutputData() {
447 return mOutputData;
448 }
Rahul Ravikumare20d9442018-12-20 11:14:49 -0800449
450 @Override
451 public boolean equals(Object o) {
452 if (this == o) return true;
453 if (o == null || getClass() != o.getClass()) return false;
454
455 Failure failure = (Failure) o;
456
457 return mOutputData.equals(failure.mOutputData);
458 }
459
460 @Override
461 public int hashCode() {
462 String name = Failure.class.getName();
463 return 31 * name.hashCode() + mOutputData.hashCode();
464 }
Rahul Ravikumar7f149de2019-01-29 16:19:29 -0800465
466 @Override
467 public String toString() {
468 return "Failure {" + "mOutputData=" + mOutputData + '}';
469 }
Sumir Katariab18241b2018-12-10 12:34:45 -0800470 }
471
472 /**
473 * Used to indicate that the work encountered a transient failure and should be retried with
474 * backoff specified in
475 * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}.
476 *
477 * @hide
478 */
479 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
480 public static final class Retry extends Result {
481 public Retry() {
482 super();
483 }
Rahul Ravikumare20d9442018-12-20 11:14:49 -0800484
485 @Override
486 public boolean equals(Object o) {
487 if (this == o) return true;
488 // We are treating all instances of Retry as equivalent.
489 return o != null && getClass() == o.getClass();
490 }
491
492 @Override
493 public int hashCode() {
494 String name = Retry.class.getName();
495 return name.hashCode();
496 }
Rahul Ravikumar7f149de2019-01-29 16:19:29 -0800497
498 @Override
499 public String toString() {
500 return "Retry";
501 }
Sumir Katariab18241b2018-12-10 12:34:45 -0800502 }
503 }
Rahul Ravikumar83f13f32018-07-31 16:50:28 -0700504}