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 android.content.Context
20 import androidx.concurrent.futures.await
21 import com.google.common.util.concurrent.ListenableFuture
22 import kotlin.coroutines.CoroutineContext
23 import kotlinx.coroutines.CoroutineDispatcher
24 import kotlinx.coroutines.Dispatchers
25 import kotlinx.coroutines.Job
26 import kotlinx.coroutines.Runnable
27 
28 /**
29  * A [ListenableWorker] implementation that provides interop with Kotlin Coroutines. Override the
30  * [doWork] function to do your suspending work.
31  *
32  * By default, CoroutineWorker runs on [Dispatchers.Default] if neither
33  * [Configuration.Builder.setExecutor] or [Configuration.Builder.setWorkerCoroutineContext] were
34  * set.
35  *
36  * <p>
37  * A CoroutineWorker is given a maximum of ten minutes to finish its execution and return a
38  * [ListenableWorker.Result]. After this time has expired, the worker will be signalled to stop.
39  */
40 public abstract class CoroutineWorker(appContext: Context, private val params: WorkerParameters) :
41     ListenableWorker(appContext, params) {
42 
43     /**
44      * The coroutine context on which [doWork] will run.
45      *
46      * If this property is overridden then it takes precedent over [Configuration.executor] or
47      * [Configuration.workerCoroutineContext].
48      *
49      * By default, this is a dispatcher delegating to [Dispatchers.Default]
50      */
51     @Deprecated(message = "use withContext(...) inside doWork() instead.")
52     public open val coroutineContext: CoroutineDispatcher = DeprecatedDispatcher
53 
54     @Suppress("DEPRECATION")
startWorknull55     public final override fun startWork(): ListenableFuture<Result> {
56         // if a developer didn't override coroutineContext property, then
57         // we use Dispatchers.Default directly.
58         // We can't fully implement delegating CoroutineDispatcher, because CoroutineDispatcher
59         // has experimental and internal apis.
60         val coroutineContext =
61             if (coroutineContext != DeprecatedDispatcher) {
62                 coroutineContext
63             } else {
64                 params.workerContext
65             }
66 
67         return launchFuture(coroutineContext + Job()) { doWork() }
68     }
69 
70     /**
71      * A suspending method to do your work.
72      *
73      * <p>
74      * To specify which [CoroutineDispatcher] your work should run on, use `withContext()` within
75      * `doWork()`. If there is no other dispatcher declared, [Dispatchers.Default] will be used.
76      *
77      * <p>
78      * A CoroutineWorker is given a maximum of ten minutes to finish its execution and return a
79      * [ListenableWorker.Result]. After this time has expired, the worker will be signalled to stop.
80      *
81      * @return The [ListenableWorker.Result] of the result of the background work; note that
82      *   dependent work will not execute if you return [ListenableWorker.Result.failure]
83      */
doWorknull84     public abstract suspend fun doWork(): Result
85 
86     /**
87      * @return The [ForegroundInfo] instance if the [WorkRequest] is marked as expedited.
88      * @throws [IllegalStateException] when not overridden. Override this method when the
89      *   corresponding [WorkRequest] is marked expedited.
90      */
91     public open suspend fun getForegroundInfo(): ForegroundInfo {
92         throw IllegalStateException("Not implemented")
93     }
94 
95     /**
96      * Updates the progress for the [CoroutineWorker]. This is a suspending function unlike the
97      * [setProgressAsync] API which returns a [ListenableFuture].
98      *
99      * @param data The progress [Data]
100      */
setProgressnull101     public suspend fun setProgress(data: Data) {
102         setProgressAsync(data).await()
103     }
104 
105     /**
106      * Makes the [CoroutineWorker] run in the context of a foreground [android.app.Service]. This is
107      * a suspending function unlike the [setForegroundAsync] API which returns a [ListenableFuture].
108      *
109      * Calling [setForeground] will throw an [IllegalStateException] if the process is subject to
110      * foreground service restrictions. Consider using [WorkRequest.Builder.setExpedited] and
111      * [getForegroundInfo] instead.
112      *
113      * @param foregroundInfo The [ForegroundInfo]
114      */
setForegroundnull115     public suspend fun setForeground(foregroundInfo: ForegroundInfo) {
116         setForegroundAsync(foregroundInfo).await()
117     }
118 
119     @Suppress("DEPRECATION")
getForegroundInfoAsyncnull120     public final override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
121         return launchFuture(coroutineContext + Job()) { getForegroundInfo() }
122     }
123 
onStoppednull124     public final override fun onStopped() {
125         super.onStopped()
126     }
127 
128     private object DeprecatedDispatcher : CoroutineDispatcher() {
129         val dispatcher = Dispatchers.Default
130 
dispatchnull131         override fun dispatch(context: CoroutineContext, block: Runnable) {
132             dispatcher.dispatch(context, block)
133         }
134 
isDispatchNeedednull135         override fun isDispatchNeeded(context: CoroutineContext): Boolean {
136             return dispatcher.isDispatchNeeded(context)
137         }
138     }
139 }
140