/* * 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 static androidx.work.WorkInfo.STOP_REASON_NOT_STOPPED; import android.content.Context; import android.net.Network; import android.net.Uri; import androidx.annotation.IntRange; 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.futures.SettableFuture; 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 * {@code com.google.common.util.concurrent.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 int mStopReason = STOP_REASON_NOT_STOPPED;
private boolean mUsed;
/**
* @param appContext The application {@link Context}
* @param workerParams Parameters to setup the internal state of this worker
*/
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
* 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
* {@code com.google.common.util.concurrent.ListenableFuture} will be cancelled.
*
* The future will also be cancelled if this worker is stopped for any reason
* (see {@link #onStopped()}).
*
* @return A {@code com.google.common.util.concurrent.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
* 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}.
*
* Calling {@code setForegroundAsync} will fail with an
* {@link IllegalStateException} when the process is subject to foreground
* service restrictions. Consider using
* {@link WorkRequest.Builder#setExpedited(OutOfQuotaPolicy)} and
* {@link ListenableWorker#getForegroundInfoAsync()} instead.
*
* @param foregroundInfo The {@link ForegroundInfo}
* @return A {@code com.google.common.util.concurrent.ListenableFuture} which resolves after
* the {@link ListenableWorker} transitions to running in the context of a foreground
* {@link android.app.Service}.
*/
@NonNull
public final ListenableFuture
* Prior to Android S, WorkManager manages and runs a foreground service on your behalf to
* execute the WorkRequest, showing the notification provided in the {@link ForegroundInfo}.
* To update this notification subsequently, the application can use
* {@link android.app.NotificationManager}.
*
* Starting in Android S and above, WorkManager manages this WorkRequest using an immediate job.
*
* @return A {@code com.google.common.util.concurrent.ListenableFuture} of
* {@link ForegroundInfo} instance if the WorkRequest is marked immediate. For more
* information look at {@link WorkRequest.Builder#setExpedited(OutOfQuotaPolicy)}.
*/
@NonNull
public ListenableFuture
* If a worker hasn't been stopped, {@link WorkInfo#STOP_REASON_NOT_STOPPED} is returned.
*/
@StopReason
@RequiresApi(31)
public final int getStopReason() {
return mStopReason;
}
/**
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public final void stop(int reason) {
mStopReason = reason;
onStopped();
}
/**
* This method is invoked when this Worker has been told to stop. At this point, the
* {@code com.google.common.util.concurrent.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()
*/
@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()
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public final void setUsed() {
mUsed = true;
}
/**
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public @NonNull Executor getBackgroundExecutor() {
return mWorkerParams.getBackgroundExecutor();
}
/**
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public @NonNull TaskExecutor getTaskExecutor() {
return mWorkerParams.getTaskExecutor();
}
/**
*/
@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();
/**
*/
@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.
*
*/
@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();
}
@NonNull
@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.
*
*/
@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();
}
@NonNull
@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)}.
*
*/
@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;
}
@NonNull
@Override
public String toString() {
return "Retry";
}
}
}
}