Worker.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.content.Context
import androidx.annotation.WorkerThread
import androidx.concurrent.futures.CallbackToFutureAdapter
import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean

/**
 * A class that performs work synchronously on a background thread provided by [WorkManager].
 *
 * Worker classes are instantiated at runtime by WorkManager and the [.doWork] method is
 * called on a pre-specified background thread (see [Configuration.executor]).  This
 * method is for **synchronous** processing of your work, meaning that once you return from that
 * method, the Worker is considered to be finished and will be destroyed.  If you need to do your
 * work asynchronously or call asynchronous APIs, you should use [ListenableWorker].
 *
 *
 * In case the work is preempted for any reason, the same instance of Worker is not reused.  This
 * means that [.doWork] is called exactly once per Worker instance.  A new Worker is created
 * if a unit of work needs to be rerun.
 *
 *
 * A Worker is given a maximum of ten minutes to finish its execution and return a
 * [androidx.work.ListenableWorker.Result].  After this time has expired, the Worker will be
 * signalled to stop.
 */
abstract class Worker(
    context: Context,
    workerParams: WorkerParameters
) : ListenableWorker(context, workerParams) {

    /**
     * Override this method to do your actual background processing.  This method is called on a
     * background thread - you are required to **synchronously** do your work and return the
     * [androidx.work.ListenableWorker.Result] from this method.  Once you return from this
     * method, the Worker is considered to have finished what its doing and will be destroyed.  If
     * you need to do your work asynchronously on a thread of your own choice, see
     * [ListenableWorker].
     *
     *
     * A Worker has a well defined
     * [execution window](https://d.android.com/reference/android/app/job/JobScheduler)
     * to finish its execution and return a [androidx.work.ListenableWorker.Result].  After
     * this time has expired, the Worker will be signalled to stop.
     *
     * @return The [androidx.work.ListenableWorker.Result] of the computation; note that
     * dependent work will not execute if you use
     * [androidx.work.ListenableWorker.Result.failure] or
     * [androidx.work.ListenableWorker.Result.failure]
     */
    @WorkerThread
    abstract fun doWork(): Result

    final override fun startWork(): ListenableFuture<Result> = backgroundExecutor.future {
        doWork()
    }

    override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> =
        backgroundExecutor.future { getForegroundInfo() }

    /**
     * An instance of [ForegroundInfo] if the [WorkRequest] is important to
     * the user.  In this case, WorkManager provides a signal to the OS that the process should
     * be kept alive while this work is executing.
     *
     *
     * Prior to Android S, WorkManager manages and runs a foreground service on your behalf to
     * execute the WorkRequest, showing the notification provided in the [ForegroundInfo].
     * To update this notification subsequently, the application can use
     * [android.app.NotificationManager].
     *
     *
     * Starting in Android S and above, WorkManager manages this WorkRequest using an immediate job.
     *
     * @return A [ForegroundInfo] instance if the WorkRequest is marked immediate.
     * For more information look at [WorkRequest.Builder.setExpedited].
     * @throws IllegalStateException if it is not overridden and worker tries to go to foreground
     */
    @WorkerThread
    open fun getForegroundInfo(): ForegroundInfo {
        throw IllegalStateException(
            "Expedited WorkRequests require a Worker to provide an implementation for " +
                "`getForegroundInfo()`"
        )
    }
}

private fun <T> Executor.future(
    block: () -> T
): ListenableFuture<T> = CallbackToFutureAdapter.getFuture {
    val cancelled = AtomicBoolean(false)
    it.addCancellationListener({ cancelled.set(true) }, DirectExecutor.INSTANCE)
    execute {
        if (cancelled.get()) return@execute
        try {
            it.set(block())
        } catch (t: Throwable) {
            it.setException(t)
        }
    }
}