WorkManager.kt

/*
 * 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.app.PendingIntent
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.work.impl.WorkManagerImpl
import com.google.common.util.concurrent.ListenableFuture
import java.util.UUID
import kotlinx.coroutines.flow.Flow

/**
 * WorkManager is the recommended library for persistent work.
 * Scheduled work is guaranteed to execute sometime after its [Constraints] are met.
 * WorkManager allows observation of work status and the ability to create complex chains of work.
 *
 * WorkManager uses an underlying job dispatching service when available based on the following
 * criteria:
 *
 *  * Uses JobScheduler for API 23+
 *  * Uses a custom AlarmManager + BroadcastReceiver implementation for API 14-22
 *
 * All work must be done in a [ListenableWorker] class.  A simple implementation,
 * [Worker], is recommended as the starting point for most developers.  With the optional
 * dependencies, you can also use `CoroutineWorker` or `RxWorker`.  All background work
 * is given a maximum of ten minutes to finish its execution.  After this time has expired, the
 * worker will be signalled to stop.
 *
 * There are two types of work supported by WorkManager: [OneTimeWorkRequest] and
 * [PeriodicWorkRequest].  You can enqueue requests using WorkManager as follows:
 *
 * ```
 * WorkManager workManager = WorkManager.getInstance(Context);
 * workManager.enqueue(new OneTimeWorkRequest.Builder(FooWorker.class).build());
 * ```
 *
 * A [WorkRequest] has an associated id that can be used for lookups and observation as
 * follows:
 *
 * ```
 * WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
 * workManager.enqueue(request);
 * LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(request.getId());
 * status.observe(...);
 * ```
 *
 * You can also use the id for cancellation:
 *
 * ```
 * WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
 * workManager.enqueue(request);
 * workManager.cancelWorkById(request.getId());
 * ```
 *
 * You can chain work as follows:
 *
 * ```
 * WorkRequest request1 = new OneTimeWorkRequest.Builder(FooWorker.class).build();
 * WorkRequest request2 = new OneTimeWorkRequest.Builder(BarWorker.class).build();
 * WorkRequest request3 = new OneTimeWorkRequest.Builder(BazWorker.class).build();
 * workManager.beginWith(request1, request2).then(request3).enqueue();
 * ```
 *
 * Each call to [beginWith] returns a [WorkContinuation] upon which you can call
 * [WorkContinuation.then] with a single [OneTimeWorkRequest] or a list of [OneTimeWorkRequest] to
 * chain further work.  This allows for creation of complex chains of work.  For example, to create
 * a chain like this:
 *
 * ```
 *            A
 *            |
 *      +----------+
 *      |          |
 *      B          C
 *      |
 *   +----+
 *   |    |
 *   D    E
 * ```
 *
 * you would enqueue them as follows:
 *
 * ```
 * WorkContinuation continuation = workManager.beginWith(A);
 * continuation.then(B).then(D, E).enqueue();  // A is implicitly enqueued here
 * continuation.then(C).enqueue();
 * ```
 *
 * Work is eligible for execution when all of its prerequisites are complete.  If any of its
 * prerequisites fail or are cancelled, the work will never run.
 *
 * WorkRequests can accept [Constraints], inputs (see [Data]), and backoff criteria.
 * WorkRequests can be tagged with human-readable Strings
 * (see [WorkRequest.Builder.addTag]), and chains of work can be given a
 * uniquely-identifiable name (see [beginUniqueWork]).
 *
 * ### Initializing WorkManager
 *
 * By default, WorkManager auto-initializes itself using a built-in `ContentProvider`.
 * ContentProviders are created and run before the `Application` object, so this allows the
 * WorkManager singleton to be setup before your code can run in most cases.  This is suitable for
 * most developers.  However, you can provide a custom [Configuration] by using
 * [Configuration.Provider] or [WorkManager.initialize].
 *
 * ### Renaming and Removing ListenableWorker Classes
 *
 * Exercise caution in renaming classes derived from [ListenableWorker]s. WorkManager stores
 * the class name in its internal database when the [WorkRequest] is enqueued so it can later
 * create an instance of that worker when constraints are met. Unless otherwise specified in the
 * WorkManager [Configuration], this is done in the default [WorkerFactory] which tries
 * to reflectively create the ListenableWorker object. Therefore, renaming or removing these
 * classes is dangerous - if there is pending work with the given class, it will fail permanently
 * if the class cannot be found.  If you are using a custom WorkerFactory, make sure you properly
 * handle cases where the class is not found so that your code does not crash.
 *
 * In case it is desirable to rename a class, implement a custom WorkerFactory that instantiates the
 * right ListenableWorker for the old class name.
 */
// Suppressing Metalava checks for added abstract methods in WorkManager.
// WorkManager cannot be extended, because the constructor is marked @Restricted
@SuppressLint("AddedAbstractMethod")
abstract class WorkManager internal constructor() {

    companion object {
        /**
         * Retrieves the `default` singleton instance of [WorkManager].
         *
         * @return The singleton instance of [WorkManager]; this may be `null` in unusual
         * circumstances where you have disabled automatic initialization and have failed to
         * manually call [initialize].
         * @throws IllegalStateException If WorkManager is not initialized properly as per
         * the exception message.
         */
        // `open` modifier was added to avoid errors in WorkManagerImpl:
        // "WorkManagerImpl cannot override <X> in WorkManager", even though methods are static
        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
        @Deprecated(
            message = "Use the overload receiving Context",
            replaceWith = ReplaceWith("WorkManager.getContext(context)"),
        )
        @JvmStatic
        open fun getInstance(): WorkManager {
            @Suppress("DEPRECATION")
            val workManager: WorkManager? = WorkManagerImpl.getInstance()
            checkNotNull(workManager) {
                "WorkManager is not initialized properly.  The most " +
                    "likely cause is that you disabled WorkManagerInitializer in your manifest " +
                    "but forgot to call WorkManager#initialize in your Application#onCreate or a " +
                    "ContentProvider."
            }
            return workManager
        }

        /**
         * Retrieves the `default` singleton instance of [WorkManager].
         *
         * @param context A [Context] for on-demand initialization.
         * @return The singleton instance of [WorkManager]; this may be `null` in unusual
         * circumstances where you have disabled automatic initialization and have failed to
         * manually call [initialize].
         * @throws IllegalStateException If WorkManager is not initialized properly
         */
        // `open` modifier was added to avoid errors in WorkManagerImpl:
        // "WorkManagerImpl cannot override <X> in WorkManager", even though methods are static
        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
        @JvmStatic
        open fun getInstance(context: Context): WorkManager {
            return WorkManagerImpl.getInstance(context)
        }

        /**
         * Used to do a one-time initialization of the [WorkManager] singleton with a custom
         * [Configuration]. By default, this method should not be called because WorkManager is
         * automatically initialized. To initialize WorkManager yourself, please follow these steps:
         *
         *  * Disable `androidx.work.WorkManagerInitializer` in your manifest.
         *  * Invoke this method in `Application#onCreate` or a `ContentProvider`. Note
         * that this method **must** be invoked in one of these two places or you risk getting a
         * `NullPointerException` in [getInstance].
         *
         * This method throws an [IllegalStateException] when attempting to initialize in
         * direct boot mode.
         *
         * This method throws an exception if it is called multiple times.
         *
         * @param context A [Context] object for configuration purposes. Internally, this class
         * will call [Context.getApplicationContext], so you may safely pass in
         * any Context without risking a memory leak.
         * @param configuration The [Configuration] for used to set up WorkManager.
         * @see Configuration.Provider for on-demand initialization.
         */
        // `open` modifier was added to avoid errors in WorkManagerImpl:
        // "WorkManagerImpl cannot override <X> in WorkManager", even though methods are static
        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
        @JvmStatic
        open fun initialize(context: Context, configuration: Configuration) {
            WorkManagerImpl.initialize(context, configuration)
        }

        /**
         * Provides a way to check if [WorkManager] is initialized in this process.
         *
         * @return `true` if [WorkManager] has been initialized in this process.
         */
        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
        @JvmStatic
        open fun isInitialized(): Boolean = WorkManagerImpl.isInitialized()
    }

    /**
     * The [Configuration] instance that [WorkManager] was initialized with.
     */
    abstract val configuration: Configuration

    /**
     * Enqueues one item for background processing.
     *
     * @param request The [WorkRequest] to enqueue
     * @return An [Operation] that can be used to determine when the enqueue has completed
     */
    fun enqueue(request: WorkRequest): Operation {
        return enqueue(listOf(request))
    }

    /**
     * Enqueues one or more items for background processing.
     *
     * @param requests One or more [WorkRequest] to enqueue
     * @return An [Operation] that can be used to determine when the enqueue has completed
     */
    abstract fun enqueue(requests: List<WorkRequest>): Operation

    /**
     * Begins a chain with one or more [OneTimeWorkRequest]s, which can be enqueued together
     * in the future using [WorkContinuation.enqueue].
     *
     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
     * and will never run.
     *
     * @param request One or more [OneTimeWorkRequest] to start a chain of work
     * @return A [WorkContinuation] that allows for further chaining of dependent
     * [OneTimeWorkRequest]
     */
    fun beginWith(request: OneTimeWorkRequest): WorkContinuation {
        return beginWith(listOf(request))
    }

    /**
     * Begins a chain with one or more [OneTimeWorkRequest]s, which can be enqueued together
     * in the future using [WorkContinuation.enqueue].
     *
     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
     * and will never run.
     *
     * @param requests One or more [OneTimeWorkRequest] to start a chain of work
     * @return A [WorkContinuation] that allows for further chaining of dependent
     * [OneTimeWorkRequest]
     */
    abstract fun beginWith(requests: List<OneTimeWorkRequest>): WorkContinuation

    /**
     * This method allows you to begin unique chains of work for situations where you only want one
     * chain with a given name to be active at a time.  For example, you may only want one sync
     * operation to be active.  If there is one pending, you can choose to let it run or replace it
     * with your new work.
     *
     * The `uniqueWorkName` uniquely identifies this set of work.
     *
     * If this method determines that new work should be enqueued and run, all records of previous
     * work with `uniqueWorkName` will be pruned.  If this method determines that new work
     * should NOT be run, then the entire chain will be considered a no-op.
     *
     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
     * and will never run.  This is particularly important if you are using `APPEND` as your
     * [ExistingWorkPolicy].
     *
     * @param uniqueWorkName A unique name which for this chain of work
     * @param existingWorkPolicy An [ExistingWorkPolicy]
     * @param request The [OneTimeWorkRequest] to enqueue. `REPLACE` ensures that if there
     * is pending work labelled with `uniqueWorkName`, it will be cancelled and
     * the new work will run. `KEEP` will run the new sequence of work only if
     * there is no pending work labelled with `uniqueWorkName`.  `APPEND`
     * will create a new sequence of work if there is no existing work with
     * `uniqueWorkName`; otherwise, `work` will be added as a child of all
     * leaf nodes labelled with `uniqueWorkName`.
     * @return A [WorkContinuation] that allows further chaining
     */
    fun beginUniqueWork(
        uniqueWorkName: String,
        existingWorkPolicy: ExistingWorkPolicy,
        request: OneTimeWorkRequest
    ): WorkContinuation {
        return beginUniqueWork(uniqueWorkName, existingWorkPolicy, listOf(request))
    }

    /**
     * This method allows you to begin unique chains of work for situations where you only want one
     * chain with a given name to be active at a time.  For example, you may only want one sync
     * operation to be active.  If there is one pending, you can choose to let it run or replace it
     * with your new work.
     *
     * The `uniqueWorkName` uniquely identifies this set of work.
     *
     * If this method determines that new work should be enqueued and run, all records of previous
     * work with `uniqueWorkName` will be pruned.  If this method determines that new work
     * should NOT be run, then the entire chain will be considered a no-op.
     *
     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
     * and will never run.  This is particularly important if you are using `APPEND` as your
     * [ExistingWorkPolicy].
     *
     * @param uniqueWorkName A unique name which for this chain of work
     * @param existingWorkPolicy An [ExistingWorkPolicy]; see below for more information
     * @param requests One or more [OneTimeWorkRequest] to enqueue. `REPLACE` ensures that
     * if there is pending work labelled with `uniqueWorkName`, it will be
     * cancelled and the new work will run. `KEEP` will run the new sequence of
     * work only if there is no pending work labelled with `uniqueWorkName`.
     * `APPEND` will create a new sequence of work if there is no
     * existing work with `uniqueWorkName`; otherwise, `work` will be added
     * as a child of all leaf nodes labelled with `uniqueWorkName`.
     * @return A [WorkContinuation] that allows further chaining
     */
    abstract fun beginUniqueWork(
        uniqueWorkName: String,
        existingWorkPolicy: ExistingWorkPolicy,
        requests: List<OneTimeWorkRequest>
    ): WorkContinuation

    /**
     * This method allows you to enqueue `work` requests to a uniquely-named
     * [WorkContinuation], where only one continuation of a particular name can be active at
     * a time. For example, you may only want one sync operation to be active. If there is one
     * pending, you can choose to let it run or replace it with your new work.
     *
     * The `uniqueWorkName` uniquely identifies this [WorkContinuation].
     *
     * @param uniqueWorkName A unique name which for this operation
     * @param existingWorkPolicy An [ExistingWorkPolicy]; see below for more information
     * @param request The [OneTimeWorkRequest]s to enqueue. `REPLACE` ensures that if there
     * is pending work labelled with `uniqueWorkName`, it will be cancelled and
     * the new work will run. `KEEP` will run the new OneTimeWorkRequests only if
     * there is no pending work labelled with `uniqueWorkName`.  `APPEND`
     * will append the OneTimeWorkRequests as leaf nodes labelled with
     * `uniqueWorkName`.
     * @return An [Operation] that can be used to determine when the enqueue has completed
     */
    open fun enqueueUniqueWork(
        uniqueWorkName: String,
        existingWorkPolicy: ExistingWorkPolicy,
        request: OneTimeWorkRequest
    ): Operation {
        return enqueueUniqueWork(uniqueWorkName, existingWorkPolicy, listOf(request))
    }

    /**
     * This method allows you to enqueue `work` requests to a uniquely-named
     * [WorkContinuation], where only one continuation of a particular name can be active at
     * a time. For example, you may only want one sync operation to be active. If there is one
     * pending, you can choose to let it run or replace it with your new work.
     *
     * The `uniqueWorkName` uniquely identifies this [WorkContinuation].
     *
     * @param uniqueWorkName A unique name which for this operation
     * @param existingWorkPolicy An [ExistingWorkPolicy]
     * @param requests [OneTimeWorkRequest]s to enqueue. `REPLACE` ensures
     * that if there is pending work labelled with `uniqueWorkName`, it
     * will be cancelled and the new work will run. `KEEP` will run the
     * new OneTimeWorkRequests only if there is no pending work labelled with
     * `uniqueWorkName`. `APPEND` will append the
     * OneTimeWorkRequests as leaf nodes labelled with `uniqueWorkName`.
     * @return An [Operation] that can be used to determine when the enqueue has completed
     */
    abstract fun enqueueUniqueWork(
        uniqueWorkName: String,
        existingWorkPolicy: ExistingWorkPolicy,
        requests: List<OneTimeWorkRequest>
    ): Operation

    /**
     * This method allows you to enqueue a uniquely-named [PeriodicWorkRequest], where only
     * one PeriodicWorkRequest of a particular name can be active at a time.  For example, you may
     * only want one sync operation to be active.  If there is one pending, you can choose to let it
     * run or replace it with your new work.
     *
     * The `uniqueWorkName` uniquely identifies this PeriodicWorkRequest.
     *
     * @param uniqueWorkName A unique name which for this operation
     * @param existingPeriodicWorkPolicy An [ExistingPeriodicWorkPolicy]
     * @param request A [PeriodicWorkRequest] to enqueue. `REPLACE` ensures that if
     * there is pending work labelled with `uniqueWorkName`, it will be
     * cancelled and the new work will run. `KEEP` will run the new
     * PeriodicWorkRequest only if there is no pending work labelled with
     * `uniqueWorkName`.
     * @return An [Operation] that can be used to determine when the enqueue has completed
     */
    abstract fun enqueueUniquePeriodicWork(
        uniqueWorkName: String,
        existingPeriodicWorkPolicy: ExistingPeriodicWorkPolicy,
        request: PeriodicWorkRequest
    ): Operation

    /**
     * Cancels work with the given id if it isn't finished.  Note that cancellation is a best-effort
     * policy and work that is already executing may continue to run. Upon cancellation,
     * [ListenableFuture] returned by [ListenableWorker.startWork] will be cancelled. Also
     * [ListenableWorker.onStopped] will be invoked for any affected workers.
     *
     * @param id The id of the work
     * @return An [Operation] that can be used to determine when the cancelWorkById has
     * completed
     */
    abstract fun cancelWorkById(id: UUID): Operation

    /**
     * Cancels all unfinished work with the given tag.  Note that cancellation is a best-effort
     * policy and work that is already executing may continue to run. Upon cancellation,
     * [ListenableFuture] returned by [ListenableWorker.startWork] will be cancelled. Also
     * [ListenableWorker.onStopped] will be invoked for any affected workers.
     *
     * @param tag The tag used to identify the work
     * @return An [Operation] that can be used to determine when the cancelAllWorkByTag has
     * completed
     */
    abstract fun cancelAllWorkByTag(tag: String): Operation

    /**
     * Cancels all unfinished work in the work chain with the given name.  Note that cancellation is
     * a best-effort policy and work that is already executing may continue to run. Upon
     * cancellation, [ListenableFuture] returned by [ListenableWorker.startWork] will be cancelled.
     * Also [ListenableWorker.onStopped] will be invoked for any affected workers.
     *
     * @param uniqueWorkName The unique name used to identify the chain of work
     * @return An [Operation] that can be used to determine when the cancelUniqueWork has
     * completed
     */
    abstract fun cancelUniqueWork(uniqueWorkName: String): Operation

    /**
     * Cancels all unfinished work.  **Use this method with extreme caution!**  By invoking it,
     * you will potentially affect other modules or libraries in your codebase.  It is strongly
     * recommended that you use one of the other cancellation methods at your disposal.
     *
     * Upon cancellation, [ListenableFuture] returned by [ListenableWorker.startWork] will be
     * cancelled. Also [ListenableWorker.onStopped] will be invoked for any affected workers.
     *
     * @return An [Operation] that can be used to determine when the cancelAllWork has
     * completed
     */
    abstract fun cancelAllWork(): Operation

    /**
     * Creates a [PendingIntent] which can be used to cancel a [WorkRequest] with the
     * given `id`.
     *
     * @param id      The [WorkRequest] id.
     * @return The [PendingIntent] that can be used to cancel the [WorkRequest].
     */
    abstract fun createCancelPendingIntent(id: UUID): PendingIntent

    /**
     * Prunes all eligible finished work from the internal database.  Eligible work must be finished
     * ([WorkInfo.State.SUCCEEDED], [WorkInfo.State.FAILED], or
     * [WorkInfo.State.CANCELLED]), with zero unfinished dependents.
     *
     * **Use this method with caution**; by invoking it, you (and any modules and libraries in
     * your codebase) will no longer be able to observe the [WorkInfo] of the pruned work.
     * You do not normally need to call this method - WorkManager takes care to auto-prune its work
     * after a sane period of time.  This method also ignores the
     * [OneTimeWorkRequest.Builder.keepResultsForAtLeast] policy.
     *
     * @return An [Operation] that can be used to determine when the pruneWork has
     * completed
     */
    abstract fun pruneWork(): Operation

    /**
     * Gets a [LiveData] of the last time all work was cancelled.  This method is intended for
     * use by library and module developers who have dependent data in their own repository that
     * must be updated or deleted in case someone cancels their work without their prior knowledge.
     *
     * @return A [LiveData] of the timestamp (`System#getCurrentTimeMillis()`) when
     * [cancelAllWork] was last invoked; this timestamp may be `0L` if this
     * never occurred
     */
    abstract fun getLastCancelAllTimeMillisLiveData(): LiveData<Long>

    /**
     * Gets a [ListenableFuture] of the last time all work was cancelled.  This method is
     * intended for use by library and module developers who have dependent data in their own
     * repository that must be updated or deleted in case someone cancels their work without
     * their prior knowledge.
     *
     * @return A [ListenableFuture] of the timestamp (`System#getCurrentTimeMillis()`)
     * when [cancelAllWork] was last invoked; this timestamp may be `0L` if
     * this never occurred
     */
    abstract fun getLastCancelAllTimeMillis(): ListenableFuture<Long>

    /**
     * Gets a [LiveData] of the [WorkInfo] for a given work id.
     *
     * @param id The id of the work
     * @return A [LiveData] of the [WorkInfo] associated with `id`; note that
     * this [WorkInfo] may be `null` if `id` is not known to WorkManager.
     */
    abstract fun getWorkInfoByIdLiveData(id: UUID): LiveData<WorkInfo?>

    /**
     * Gets a [Flow] of the [WorkInfo] for a given work id.
     *
     * @param id The id of the work
     * @return A [Flow] of the [WorkInfo] associated with `id`; note that
     * this [WorkInfo] may be `null` if `id` is not known to WorkManager.
     */
    abstract fun getWorkInfoByIdFlow(id: UUID): Flow<WorkInfo?>

    /**
     * Gets a [ListenableFuture] of the [WorkInfo] for a given work id.
     *
     * @param id The id of the work
     * @return A [ListenableFuture] of the [WorkInfo] associated with `id`;
     * note that this [WorkInfo] may be `null` if `id` is not known to WorkManager
     */
    abstract fun getWorkInfoById(id: UUID): ListenableFuture<WorkInfo?>

    /**
     * Gets a [LiveData] of the [WorkInfo] for all work for a given tag.
     *
     * @param tag The tag of the work
     * @return A [LiveData] list of [WorkInfo] for work tagged with `tag`
     */
    abstract fun getWorkInfosByTagLiveData(tag: String): LiveData<List<WorkInfo>>

    /**
     * Gets a [Flow] of the [WorkInfo] for all work for a given tag.
     *
     * @param tag The tag of the work
     * @return A [Flow] list of [WorkInfo] for work tagged with `tag`
     */
    abstract fun getWorkInfosByTagFlow(tag: String): Flow<List<WorkInfo>>

    /**
     * Gets a [ListenableFuture] of the [WorkInfo] for all work for a given tag.
     *
     * @param tag The tag of the work
     * @return A [ListenableFuture] list of [WorkInfo] for work tagged with `tag`
     */
    abstract fun getWorkInfosByTag(tag: String): ListenableFuture<List<WorkInfo>>

    /**
     * Gets a [LiveData] of the [WorkInfo] for all work in a work chain with a given
     * unique name.
     *
     * @param uniqueWorkName The unique name used to identify the chain of work
     * @return A [LiveData] of the [WorkInfo] for work in the chain named `uniqueWorkName`
     */
    abstract fun getWorkInfosForUniqueWorkLiveData(uniqueWorkName: String): LiveData<List<WorkInfo>>

    /**
     * Gets a [Flow] of the [WorkInfo] for all work in a work chain with a given
     * unique name.
     *
     * @param uniqueWorkName The unique name used to identify the chain of work
     * @return A [Flow] of the [WorkInfo] for work in the chain named `uniqueWorkName`
     */
    abstract fun getWorkInfosForUniqueWorkFlow(uniqueWorkName: String): Flow<List<WorkInfo>>

    /**
     * Gets a [ListenableFuture] of the [WorkInfo] for all work in a work chain
     * with a given unique name.
     *
     * @param uniqueWorkName The unique name used to identify the chain of work
     * @return A [ListenableFuture] of the [WorkInfo] for work in the chain named `uniqueWorkName`
     */
    abstract fun getWorkInfosForUniqueWork(uniqueWorkName: String): ListenableFuture<List<WorkInfo>>

    /**
     * Gets the [LiveData] of the [List] of [WorkInfo] for all work
     * referenced by the [WorkQuery] specification.
     *
     * @param workQuery The work query specification
     * @return A [LiveData] of the [List] of [WorkInfo] for work
     * referenced by this [WorkQuery].
     */
    abstract fun getWorkInfosLiveData(workQuery: WorkQuery): LiveData<List<WorkInfo>>

    /**
     * Gets the [Flow] of the [List] of [WorkInfo] for all work
     * referenced by the [WorkQuery] specification.
     *
     * @param workQuery The work query specification
     * @return A [Flow] of the [List] of [WorkInfo] for work
     * referenced by this [WorkQuery].
     */
    abstract fun getWorkInfosFlow(workQuery: WorkQuery): Flow<List<WorkInfo>>

    /**
     * Gets the [ListenableFuture] of the [List] of [WorkInfo] for all work
     * referenced by the [WorkQuery] specification.
     *
     * @param workQuery The work query specification
     * @return A [ListenableFuture] of the [List] of [WorkInfo] for work
     * referenced by this [WorkQuery].
     */
    abstract fun getWorkInfos(workQuery: WorkQuery): ListenableFuture<List<WorkInfo>>

    /**
     * Updates the work with the new specification. A [WorkRequest] passed as parameter
     * must have an id set with [WorkRequest.Builder.setId] that matches an id of the
     * previously enqueued work.
     *
     * It preserves enqueue time, e.g. if a work was enqueued 3 hours ago and had 6 hours long
     * initial delay, after the update it would be still eligible for run in 3 hours, assuming
     * that initial delay wasn't updated.
     *
     * If the work being updated is currently running the returned ListenableFuture will be
     * completed with [UpdateResult.APPLIED_FOR_NEXT_RUN]. In this case the current run won't
     * be interrupted and will continue to rely on previous state of the request, e.g. using
     * old constraints, tags etc. However, on the next run, e.g. retry of one-time Worker or
     * another iteration of periodic worker, the new worker specification will be used.
     *
     * If the one time work that is updated is already finished the returned ListenableFuture
     * will be completed with [UpdateResult.NOT_APPLIED].
     *
     * If update can be applied immediately, e.g. the updated work isn't currently running,
     * the returned ListenableFuture will be completed with
     * [UpdateResult.APPLIED_IMMEDIATELY].
     *
     * If the work with the given id (`request.getId()`) doesn't exist the returned
     * ListenableFuture will be completed exceptionally with [IllegalArgumentException].
     *
     * Worker type can't be changed, [OneTimeWorkRequest] can't be updated to
     * [PeriodicWorkRequest] and otherwise, the returned ListenableFuture will be
     * completed with [IllegalArgumentException].
     *
     * @param request the new specification for the work.
     * @return a [ListenableFuture] that will be successfully completed if the update was
     * successful. The future will be completed with an exception if the work is already running
     * or finished.
     */
    // consistent with already existent method like getWorkInfos() in WorkManager
    @Suppress("AsyncSuffixFuture")
    abstract fun updateWork(request: WorkRequest): ListenableFuture<UpdateResult>

    /**
     * An enumeration of results for [WorkManager.updateWork] method.
     */
    enum class UpdateResult {
        /**
         * An update wasn't applied, because `Worker` has already finished.
         */
        NOT_APPLIED,

        /**
         * An update was successfully applied immediately, meaning
         * the updated work wasn't currently running in the moment of the request.
         * See [UpdateResult.APPLIED_FOR_NEXT_RUN] for the case of running worker.
         */
        APPLIED_IMMEDIATELY,

        /**
         * An update was successfully applied, but the worker being updated was running.
         * This run isn't interrupted and will continue to rely on previous state of the
         * request, e.g. using old constraints, tags etc. However, on the next run, e.g. retry of
         * one-time Worker or another iteration of periodic worker, the new worker specification.
         * will be used.
         */
        APPLIED_FOR_NEXT_RUN,
    }
}