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