/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.work; import android.annotation.SuppressLint; import android.content.Context; import android.net.Network; import android.net.Uri; import androidx.annotation.IntRange; import androidx.annotation.Keep; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import androidx.work.impl.utils.taskexecutor.TaskExecutor; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * A class that can perform work asynchronously in {@link WorkManager}. For most cases, we * recommend using {@link Worker}, which offers a simple synchronous API that is executed on a * pre-specified background thread. *

* ListenableWorker classes are instantiated at runtime by the {@link WorkerFactory} specified in * the {@link Configuration}. The {@link #startWork()} method is called on the main thread. *

* In case the work is preempted and later restarted for any reason, a new instance of * ListenableWorker is created. This means that {@code startWork} is called exactly once per * ListenableWorker instance. A new ListenableWorker is created if a unit of work needs to be * rerun. *

* A ListenableWorker is given a maximum of ten minutes to finish its execution and return a * {@link Result}. After this time has expired, the worker will be signalled to stop and its * {@link ListenableFuture} will be cancelled. *

* Exercise caution when renaming or removing * ListenableWorkers from your codebase. */ public abstract class ListenableWorker { private @NonNull Context mAppContext; private @NonNull WorkerParameters mWorkerParams; private volatile boolean mStopped; private boolean mUsed; private boolean mRunInForeground; /** * @param appContext The application {@link Context} * @param workerParams Parameters to setup the internal state of this worker */ @Keep @SuppressLint("BanKeepAnnotation") public ListenableWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) { // Actually make sure we don't get nulls. if (appContext == null) { throw new IllegalArgumentException("Application Context is null"); } if (workerParams == null) { throw new IllegalArgumentException("WorkerParameters is null"); } mAppContext = appContext; mWorkerParams = workerParams; } /** * Gets the application {@link android.content.Context}. * * @return The application {@link android.content.Context} */ public final @NonNull Context getApplicationContext() { return mAppContext; } /** * Gets the ID of the {@link WorkRequest} that created this Worker. * * @return The ID of the creating {@link WorkRequest} */ public final @NonNull UUID getId() { return mWorkerParams.getId(); } /** * Gets the input data. Note that in the case that there are multiple prerequisites for this * Worker, the input data has been run through an {@link InputMerger}. * * @return The input data for this work * @see OneTimeWorkRequest.Builder#setInputMerger(Class) */ public final @NonNull Data getInputData() { return mWorkerParams.getInputData(); } /** * Gets a {@link java.util.Set} of tags associated with this Worker's {@link WorkRequest}. * * @return The {@link java.util.Set} of tags associated with this Worker's {@link WorkRequest} * @see WorkRequest.Builder#addTag(String) */ public final @NonNull Set getTags() { return mWorkerParams.getTags(); } /** * Gets the list of content {@link android.net.Uri}s that caused this Worker to execute. See * {@code JobParameters#getTriggeredContentUris()} for relevant {@code JobScheduler} code. * * @return The list of content {@link android.net.Uri}s that caused this Worker to execute * @see Constraints.Builder#addContentUriTrigger(android.net.Uri, boolean) */ @RequiresApi(24) public final @NonNull List getTriggeredContentUris() { return mWorkerParams.getTriggeredContentUris(); } /** * Gets the list of content authorities that caused this Worker to execute. See * {@code JobParameters#getTriggeredContentAuthorities()} for relevant {@code JobScheduler} * code. * * @return The list of content authorities that caused this Worker to execute */ @RequiresApi(24) public final @NonNull List getTriggeredContentAuthorities() { return mWorkerParams.getTriggeredContentAuthorities(); } /** * Gets the {@link android.net.Network} to use for this Worker. This method returns * {@code null} if there is no network needed for this work request. * * @return The {@link android.net.Network} specified by the OS to be used with this Worker */ @RequiresApi(28) public final @Nullable Network getNetwork() { return mWorkerParams.getNetwork(); } /** * Gets the current run attempt count for this work. Note that for periodic work, this value * gets reset between periods. * * @return The current run attempt count for this work. */ @IntRange(from = 0) public final int getRunAttemptCount() { return mWorkerParams.getRunAttemptCount(); } /** * Override this method to start your actual background processing. This method is called on * the main thread. *

* A ListenableWorker has a well defined * execution window * to to finish its execution and return a {@link Result}. After this time has expired, the * worker will be signalled to stop and its {@link ListenableFuture} will be cancelled. *

* The future will also be cancelled if this worker is stopped for any reason * (see {@link #onStopped()}). * * @return A {@link ListenableFuture} with the {@link Result} of the computation. If you * cancel this Future, WorkManager will treat this unit of work as failed. */ @MainThread public abstract @NonNull ListenableFuture startWork(); /** * Updates {@link ListenableWorker} progress. * * @param data The progress {@link Data} * @return A {@link ListenableFuture} which resolves after progress is persisted. * Cancelling this future is a no-op. */ @NonNull public ListenableFuture setProgressAsync(@NonNull Data data) { return mWorkerParams.getProgressUpdater() .updateProgress(getApplicationContext(), getId(), data); } /** * This specifies that the {@link WorkRequest} is long-running or otherwise important. In * this case, WorkManager provides a signal to the OS that the process should be kept alive * if possible while this work is executing. *

* Calls to {@code setForegroundAsync} *must* complete before a {@link ListenableWorker} * signals completion by returning a {@link Result}. *

* Under the hood, WorkManager manages and runs a foreground service on your behalf to * execute this WorkRequest, showing the notification provided in * {@link ForegroundInfo}. * * @param foregroundInfo The {@link ForegroundInfo} * @return A {@link ListenableFuture} which resolves after the {@link ListenableWorker} * transitions to running in the context of a foreground {@link android.app.Service}. */ @NonNull public final ListenableFuture setForegroundAsync(@NonNull ForegroundInfo foregroundInfo) { mRunInForeground = true; return mWorkerParams.getForegroundUpdater() .setForegroundAsync(getApplicationContext(), getId(), foregroundInfo); } /** * Returns {@code true} if this Worker has been told to stop. This could be because of an * explicit cancellation signal by the user, or because the system has decided to preempt the * task. In these cases, the results of the work will be ignored by WorkManager and it is safe * to stop the computation. WorkManager will retry the work at a later time if necessary. * * @return {@code true} if the work operation has been interrupted */ public final boolean isStopped() { return mStopped; } /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public final void stop() { mStopped = true; onStopped(); } /** * This method is invoked when this Worker has been told to stop. At this point, the * {@link ListenableFuture} returned by the instance of {@link #startWork()} is * also cancelled. This could happen due to an explicit cancellation signal by the user, or * because the system has decided to preempt the task. In these cases, the results of the * work will be ignored by WorkManager. All processing in this method should be lightweight * - there are no contractual guarantees about which thread will invoke this call, so this * should not be a long-running or blocking operation. */ public void onStopped() { // Do nothing by default. } /** * @return {@code true} if this worker has already been marked as used * @see #setUsed() * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public final boolean isUsed() { return mUsed; } /** * Marks this worker as used to make sure we enforce the policy that workers can only be used * once and that WorkerFactories return a new instance each time. * @see #isUsed() * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public final void setUsed() { mUsed = true; } /** * @return {@code true} if the {@link ListenableWorker} is running in the context of a * foreground {@link android.app.Service}. * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public boolean isRunInForeground() { return mRunInForeground; } /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public @NonNull Executor getBackgroundExecutor() { return mWorkerParams.getBackgroundExecutor(); } /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public @NonNull TaskExecutor getTaskExecutor() { return mWorkerParams.getTaskExecutor(); } /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public @NonNull WorkerFactory getWorkerFactory() { return mWorkerParams.getWorkerFactory(); } /** * The result of a {@link ListenableWorker}'s computation. Call {@link #success()}, * {@link #failure()}, or {@link #retry()} or one of their variants to generate an object * indicating what happened in your background work. */ public abstract static class Result { /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed successfully. Any work that depends on this can be executed as long as all of * its other dependencies and constraints are met. * * @return An instance of {@link Result} indicating successful execution of work */ @NonNull public static Result success() { return new Success(); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed successfully. Any work that depends on this can be executed as long as all of * its other dependencies and constraints are met. * * @param outputData A {@link Data} object that will be merged into the input Data of any * OneTimeWorkRequest that is dependent on this work * @return An instance of {@link Result} indicating successful execution of work */ @NonNull public static Result success(@NonNull Data outputData) { return new Success(outputData); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * encountered a transient failure and should be retried with backoff specified in * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}. * * @return An instance of {@link Result} indicating that the work needs to be retried */ @NonNull public static Result retry() { return new Retry(); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed with a permanent failure. Any work that depends on this will also be marked as * failed and will not be run. If you need child workers to run, you need to use * {@link #success()} or {@link #success(Data)}; failure indicates a permanent stoppage * of the chain of work. * * @return An instance of {@link Result} indicating failure when executing work */ @NonNull public static Result failure() { return new Failure(); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed with a permanent failure. Any work that depends on this will also be marked as * failed and will not be run. If you need child workers to run, you need to use * {@link #success()} or {@link #success(Data)}; failure indicates a permanent stoppage * of the chain of work. * * @param outputData A {@link Data} object that can be used to keep track of why the work * failed * @return An instance of {@link Result} indicating failure when executing work */ @NonNull public static Result failure(@NonNull Data outputData) { return new Failure(outputData); } /** * @return The output {@link Data} which will be merged into the input {@link Data} of * any {@link OneTimeWorkRequest} that is dependent on this work request. */ @NonNull public abstract Data getOutputData(); /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) Result() { // Restricting access to the constructor, to give Result a sealed class // like behavior. } /** * Used to indicate that the work completed successfully. Any work that depends on this * can be executed as long as all of its other dependencies and constraints are met. * * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static final class Success extends Result { private final Data mOutputData; public Success() { this(Data.EMPTY); } /** * @param outputData A {@link Data} object that will be merged into the input Data of * any OneTimeWorkRequest that is dependent on this work */ public Success(@NonNull Data outputData) { super(); mOutputData = outputData; } @Override public @NonNull Data getOutputData() { return mOutputData; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Success success = (Success) o; return mOutputData.equals(success.mOutputData); } @Override public int hashCode() { String name = Success.class.getName(); return 31 * name.hashCode() + mOutputData.hashCode(); } @Override public String toString() { return "Success {" + "mOutputData=" + mOutputData + '}'; } } /** * Used to indicate that the work completed with a permanent failure. Any work that depends * on this will also be marked as failed and will not be run. If you need child workers * to run, you need to return {@link Result.Success}; failure indicates a permanent * stoppage of the chain of work. * * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static final class Failure extends Result { private final Data mOutputData; public Failure() { this(Data.EMPTY); } /** * @param outputData A {@link Data} object that can be used to keep track of why the * work failed */ public Failure(@NonNull Data outputData) { super(); mOutputData = outputData; } @Override public @NonNull Data getOutputData() { return mOutputData; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Failure failure = (Failure) o; return mOutputData.equals(failure.mOutputData); } @Override public int hashCode() { String name = Failure.class.getName(); return 31 * name.hashCode() + mOutputData.hashCode(); } @Override public String toString() { return "Failure {" + "mOutputData=" + mOutputData + '}'; } } /** * Used to indicate that the work encountered a transient failure and should be retried with * backoff specified in * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}. * * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static final class Retry extends Result { public Retry() { super(); } @Override public boolean equals(Object o) { if (this == o) return true; // We are treating all instances of Retry as equivalent. return o != null && getClass() == o.getClass(); } @Override public int hashCode() { String name = Retry.class.getName(); return name.hashCode(); } @NonNull @Override public Data getOutputData() { return Data.EMPTY; } @Override public String toString() { return "Retry"; } } } }