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