Add `Configuration.workerCoroutineContext` to control CoroutineContext for CoroutineWorker

Relnote: "`Configiration.workerCoroutineContext` was added to for control of dispatcher
where CoroutineWorker is executed. It helps to completely avoid usage of
`Dispatchers.Default` in WorkManager."
bug: 245353737
Test: ConfigurationExecutorsTest

Change-Id: Icd1b7755fc90ea71ac2e8509c0f21d3e60ca8d53
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
index 3fdbecb..147d2a9 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
@@ -239,6 +239,7 @@
             0,
             0,
             mConfiguration.executor,
+            mConfiguration.workerCoroutineContext,
             mTaskExecutor,
             mConfiguration.workerFactory,
             progressUpdater,
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java
index cdfda32f..5aa72cc 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkerParameters.java
@@ -185,6 +185,7 @@
                 mRunAttemptCount,
                 mGeneration,
                 configuration.getExecutor(),
+                configuration.getWorkerCoroutineContext(),
                 taskExecutor,
                 configuration.getWorkerFactory(),
                 progressUpdater,
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index 6d1f114..452cc68 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -29,6 +29,7 @@
     method public androidx.work.RunnableScheduler getRunnableScheduler();
     method public androidx.core.util.Consumer<java.lang.Throwable>? getSchedulingExceptionHandler();
     method public java.util.concurrent.Executor getTaskExecutor();
+    method public kotlin.coroutines.CoroutineContext getWorkerCoroutineContext();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerExecutionExceptionHandler();
     method public androidx.work.WorkerFactory getWorkerFactory();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerInitializationExceptionHandler();
@@ -43,6 +44,7 @@
     property public final androidx.work.RunnableScheduler runnableScheduler;
     property public final androidx.core.util.Consumer<java.lang.Throwable>? schedulingExceptionHandler;
     property public final java.util.concurrent.Executor taskExecutor;
+    property public final kotlin.coroutines.CoroutineContext workerCoroutineContext;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerExecutionExceptionHandler;
     property public final androidx.work.WorkerFactory workerFactory;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerInitializationExceptionHandler;
@@ -65,6 +67,7 @@
     method public androidx.work.Configuration.Builder setRunnableScheduler(androidx.work.RunnableScheduler runnableScheduler);
     method public androidx.work.Configuration.Builder setSchedulingExceptionHandler(androidx.core.util.Consumer<java.lang.Throwable> schedulingExceptionHandler);
     method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor taskExecutor);
+    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlinx.coroutines.CoroutineDispatcher dispatcher);
     method public androidx.work.Configuration.Builder setWorkerExecutionExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
     method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory workerFactory);
     method public androidx.work.Configuration.Builder setWorkerInitializationExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index 6d1f114..452cc68 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -29,6 +29,7 @@
     method public androidx.work.RunnableScheduler getRunnableScheduler();
     method public androidx.core.util.Consumer<java.lang.Throwable>? getSchedulingExceptionHandler();
     method public java.util.concurrent.Executor getTaskExecutor();
+    method public kotlin.coroutines.CoroutineContext getWorkerCoroutineContext();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerExecutionExceptionHandler();
     method public androidx.work.WorkerFactory getWorkerFactory();
     method public androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? getWorkerInitializationExceptionHandler();
@@ -43,6 +44,7 @@
     property public final androidx.work.RunnableScheduler runnableScheduler;
     property public final androidx.core.util.Consumer<java.lang.Throwable>? schedulingExceptionHandler;
     property public final java.util.concurrent.Executor taskExecutor;
+    property public final kotlin.coroutines.CoroutineContext workerCoroutineContext;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerExecutionExceptionHandler;
     property public final androidx.work.WorkerFactory workerFactory;
     property public final androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo>? workerInitializationExceptionHandler;
@@ -65,6 +67,7 @@
     method public androidx.work.Configuration.Builder setRunnableScheduler(androidx.work.RunnableScheduler runnableScheduler);
     method public androidx.work.Configuration.Builder setSchedulingExceptionHandler(androidx.core.util.Consumer<java.lang.Throwable> schedulingExceptionHandler);
     method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor taskExecutor);
+    method public androidx.work.Configuration.Builder setWorkerCoroutineContext(kotlinx.coroutines.CoroutineDispatcher dispatcher);
     method public androidx.work.Configuration.Builder setWorkerExecutionExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
     method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory workerFactory);
     method public androidx.work.Configuration.Builder setWorkerInitializationExceptionHandler(androidx.core.util.Consumer<androidx.work.WorkerExceptionInfo> workerExceptionHandler);
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/ConfigurationExecutorsTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/ConfigurationExecutorsTest.kt
new file mode 100644
index 0000000..40e6d54
--- /dev/null
+++ b/work/work-runtime/src/androidTest/java/androidx/work/ConfigurationExecutorsTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2024 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.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.testutils.TrackingWorkerFactory
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
+import androidx.work.testutils.WorkManager
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executors
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class ConfigurationExecutorsTest {
+    val workerFactory = TrackingWorkerFactory()
+    val executor = Executors.newSingleThreadExecutor {
+        Thread(it).also { thread -> thread.name = threadTestName }
+    }
+
+    @Test
+    fun testSetExecutor() = runBlocking {
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setExecutor(executor)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val blockingRequest = OneTimeWorkRequest.from(ThreadNameWorker::class.java)
+        workManager.enqueue(blockingRequest)
+        val blockingWorker = workerFactory.await(blockingRequest.id) as ThreadNameWorker
+        assertThat(blockingWorker.threadNameDeferred.await()).isEqualTo(threadTestName)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher?.asExecutor()).isEqualTo(executor)
+    }
+
+    @Test
+    fun testSetWorkerCoroutineDispatcher() = runBlocking {
+        val dispatcher = executor.asCoroutineDispatcher()
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setWorkerCoroutineContext(dispatcher)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val blockingRequest = OneTimeWorkRequest.from(ThreadNameWorker::class.java)
+        workManager.enqueue(blockingRequest)
+        val blockingWorker = workerFactory.await(blockingRequest.id) as ThreadNameWorker
+        assertThat(blockingWorker.threadNameDeferred.await()).isEqualTo(threadTestName)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher).isEqualTo(dispatcher)
+    }
+
+    @Test
+    fun testSetBoth() = runBlocking {
+        val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setExecutor(executor)
+            .setWorkerCoroutineContext(dispatcher)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val blockingRequest = OneTimeWorkRequest.from(ThreadNameWorker::class.java)
+        workManager.enqueue(blockingRequest)
+        val blockingWorker = workerFactory.await(blockingRequest.id) as ThreadNameWorker
+        assertThat(blockingWorker.threadNameDeferred.await()).isEqualTo(threadTestName)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher).isEqualTo(dispatcher)
+    }
+
+    @Test
+    fun testSetNeither() = runBlocking {
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineDispatcherWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id) as CoroutineDispatcherWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        assertThat(coroutineDispatcher).isEqualTo(Dispatchers.Default)
+    }
+
+    @Test
+    fun testSetCoroutineContextOverride() = runBlocking {
+        val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+        val configuration = Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .setExecutor(executor)
+            .setWorkerCoroutineContext(dispatcher)
+            .build()
+        val env = TestEnv(configuration)
+        val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+        val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+        WorkManagerImpl.setDelegate(workManager)
+
+        val coroutineRequest = OneTimeWorkRequest.from(CoroutineContextOverridingWorker::class.java)
+        workManager.enqueue(coroutineRequest)
+        val coroutineWorker = workerFactory.await(coroutineRequest.id)
+            as CoroutineContextOverridingWorker
+
+        val coroutineDispatcher = coroutineWorker.coroutineDispatcherDeferred.await()
+        @Suppress("DEPRECATION")
+        assertThat(coroutineDispatcher).isEqualTo(coroutineWorker.coroutineContext)
+    }
+}
+
+private const val threadTestName = "configuration_test"
+
+class ThreadNameWorker(
+    context: Context,
+    workerParams: WorkerParameters
+) : Worker(context, workerParams) {
+    val threadNameDeferred = CompletableDeferred<String>()
+
+    override fun doWork(): Result {
+        threadNameDeferred.complete(Thread.currentThread().name)
+        return Result.success()
+    }
+}
+
+class CoroutineDispatcherWorker(
+    appContext: Context,
+    params: WorkerParameters
+) : CoroutineWorker(appContext, params) {
+    val coroutineDispatcherDeferred = CompletableDeferred<CoroutineDispatcher?>()
+
+    @OptIn(ExperimentalStdlibApi::class)
+    override suspend fun doWork(): Result {
+        coroutineDispatcherDeferred.complete(currentCoroutineContext()[CoroutineDispatcher])
+        return Result.success()
+    }
+}
+
+class CoroutineContextOverridingWorker(
+    appContext: Context,
+    params: WorkerParameters,
+) : CoroutineWorker(appContext, params) {
+    private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+
+    @Suppress("OVERRIDE_DEPRECATION")
+    override val coroutineContext: CoroutineDispatcher
+        get() = dispatcher
+
+    val coroutineDispatcherDeferred = CompletableDeferred<CoroutineDispatcher?>()
+
+    @OptIn(ExperimentalStdlibApi::class)
+    override suspend fun doWork(): Result {
+        coroutineDispatcherDeferred.complete(currentCoroutineContext()[CoroutineDispatcher])
+        return Result.success()
+    }
+}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java b/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
index 20c26d7..cd18321 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
@@ -39,6 +39,8 @@
 
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.Dispatchers;
+
 @RunWith(AndroidJUnit4.class)
 public class DefaultWorkerFactoryTest extends DatabaseTest {
 
@@ -73,6 +75,7 @@
                         1,
                         0,
                         executor,
+                        Dispatchers.getDefault(),
                         new WorkManagerTaskExecutor(executor),
                         mDefaultWorkerFactory,
                         mProgressUpdater,
@@ -101,6 +104,7 @@
                             1,
                             0,
                             executor,
+                            Dispatchers.getDefault(),
                             new WorkManagerTaskExecutor(executor),
                             mDefaultWorkerFactory,
                             mProgressUpdater,
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
index 29ce8c5..bc03195 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
@@ -25,6 +25,7 @@
 import androidx.work.worker.FailureWorker
 import androidx.work.worker.TestWorker
 import java.util.UUID
+import kotlinx.coroutines.Dispatchers
 import org.hamcrest.CoreMatchers.instanceOf
 import org.hamcrest.CoreMatchers.notNullValue
 import org.hamcrest.MatcherAssert.assertThat
@@ -98,6 +99,7 @@
         1,
         0,
         SynchronousExecutor(),
+        Dispatchers.Default,
         WorkManagerTaskExecutor(SynchronousExecutor()),
         factory,
         progressUpdater,
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt
index 3794ddb..9a680b7 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkForegroundRunnableTest.kt
@@ -34,6 +34,7 @@
 import com.google.common.util.concurrent.ListenableFuture
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.fail
 import org.junit.Before
@@ -168,6 +169,7 @@
         1,
         0,
         executor,
+        Dispatchers.Default,
         taskExecutor,
         configuration.workerFactory,
         progressUpdater,
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index ddb9e31..8510b51 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -114,6 +114,8 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
+import kotlinx.coroutines.Dispatchers;
+
 @RunWith(AndroidJUnit4.class)
 public class WorkerWrapperTest extends DatabaseTest {
 
@@ -230,6 +232,7 @@
                                 1,
                                 0,
                                 mSynchronousExecutor,
+                                Dispatchers.getDefault(),
                                 mWorkTaskExecutor,
                                 mConfiguration.getWorkerFactory(),
                                 mMockProgressUpdater,
@@ -1007,6 +1010,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1036,6 +1040,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1056,6 +1061,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1085,6 +1091,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1115,6 +1122,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1149,6 +1157,7 @@
                         1,
                         0,
                         mSynchronousExecutor,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
@@ -1397,6 +1406,7 @@
                         1,
                         0,
                         executorService,
+                        Dispatchers.getDefault(),
                         mWorkTaskExecutor,
                         mConfiguration.getWorkerFactory(),
                         mMockProgressUpdater,
diff --git a/work/work-runtime/src/main/java/androidx/work/Configuration.kt b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
index ddcbc4d..8f33b1d 100644
--- a/work/work-runtime/src/main/java/androidx/work/Configuration.kt
+++ b/work/work-runtime/src/main/java/androidx/work/Configuration.kt
@@ -27,8 +27,14 @@
 import java.util.concurrent.Executors
 import java.util.concurrent.ThreadFactory
 import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
 import kotlin.math.max
 import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.asExecutor
 
 /**
  * The Configuration object used to customize [WorkManager] upon initialization.
@@ -44,6 +50,11 @@
     val executor: Executor
 
     /**
+     * The [CoroutineContext] used by [WorkManager] to execute [CoroutineWorker]s.
+     */
+    val workerCoroutineContext: CoroutineContext
+
+    /**
      * The [Executor] used by [WorkManager] for all its internal business logic
      */
     val taskExecutor: Executor
@@ -150,7 +161,19 @@
     val isUsingDefaultTaskExecutor: Boolean
 
     init {
-        executor = builder.executor ?: createDefaultExecutor(isTaskExecutor = false)
+        val builderWorkerDispatcher = builder.workerContext
+
+        executor = builder.executor ?: builderWorkerDispatcher?.asExecutor()
+            ?: createDefaultExecutor(isTaskExecutor = false)
+
+        workerCoroutineContext = when {
+            builderWorkerDispatcher != null -> builderWorkerDispatcher
+            // we don't want simply always use executor.asCoroutineDispatcher()
+            // as compatibility measure
+            builder.executor != null -> executor.asCoroutineDispatcher()
+            else -> Dispatchers.Default
+        }
+
         isUsingDefaultTaskExecutor = builder.taskExecutor == null
         // This executor is used for *both* WorkManager's tasks and Room's query executor.
         // So this should not be a single threaded executor. Writes will still be serialized
@@ -182,6 +205,7 @@
      */
     class Builder {
         internal var executor: Executor? = null
+        internal var workerContext: CoroutineContext? = null
         internal var workerFactory: WorkerFactory? = null
         internal var inputMergerFactory: InputMergerFactory? = null
         internal var taskExecutor: Executor? = null
@@ -226,7 +250,7 @@
             initializationExceptionHandler = configuration.initializationExceptionHandler
             schedulingExceptionHandler = configuration.schedulingExceptionHandler
             workerInitializationExceptionHandler =
-                    configuration.workerInitializationExceptionHandler
+                configuration.workerInitializationExceptionHandler
             workerExecutionExceptionHandler = configuration.workerExecutionExceptionHandler
             defaultProcessName = configuration.defaultProcessName
         }
@@ -254,7 +278,10 @@
         }
 
         /**
-         * Specifies a custom [Executor] for WorkManager.
+         * Specifies a custom [Executor] to run [Worker.doWork].
+         *
+         * If [setWorkerCoroutineContext] wasn't called then the [executor] will be used as
+         * [CoroutineDispatcher] to run [CoroutineWorker] as well.
          *
          * @param executor An [Executor] for running [Worker]s
          * @return This [Builder] instance
@@ -265,6 +292,20 @@
         }
 
         /**
+         * Specifies a custom [CoroutineDispatcher] to run [CoroutineWorker.doWork].
+         *
+         * If [setExecutor] wasn't called then [dispatcher] will be used as [Executor]
+         * to run [Worker] as well.
+         *
+         * @param dispatcher A [CoroutineDispatcher] for running [CoroutineWorker]s
+         * @return This [Builder] instance
+         */
+        fun setWorkerCoroutineContext(dispatcher: CoroutineDispatcher): Builder {
+            this.workerContext = dispatcher
+            return this
+        }
+
+        /**
          * Specifies a [Executor] which will be used by WorkManager for all its
          * internal book-keeping.
          *
@@ -512,7 +553,7 @@
     }
 }
 
-internal val DEFAULT_CONTENT_URI_TRIGGERS_WORKERS_LIMIT = 8
+internal const val DEFAULT_CONTENT_URI_TRIGGERS_WORKERS_LIMIT = 8
 
 private fun createDefaultExecutor(isTaskExecutor: Boolean): Executor {
     val factory = object : ThreadFactory {
@@ -529,3 +570,6 @@
         factory
     )
 }
+
+private fun CoroutineContext?.asExecutor(): Executor? =
+    (this?.get(ContinuationInterceptor) as? CoroutineDispatcher)?.asExecutor()
diff --git a/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt b/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt
index d56aec9..b546f1c 100644
--- a/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt
+++ b/work/work-runtime/src/main/java/androidx/work/CoroutineWorker.kt
@@ -18,33 +18,52 @@
 
 import android.content.Context
 import com.google.common.util.concurrent.ListenableFuture
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.Runnable
 
 /**
- * A [ListenableWorker] implementation that provides interop with Kotlin Coroutines.  Override
+ * A [ListenableWorker] implementation that provides interop with Kotlin Coroutines. Override
  * the [doWork] function to do your suspending work.
- * <p>
- * By default, CoroutineWorker runs on [Dispatchers.Default]; this can be modified by
- * overriding [coroutineContext].
+ *
+ * By default, CoroutineWorker runs on [Dispatchers.Default] if neither
+ * [Configuration.Builder.setExecutor] or [Configuration.Builder.setWorkerCoroutineContext]
+ * were set.
+ *
  * <p>
  * A CoroutineWorker is given a maximum of ten minutes to finish its execution and return a
- * [ListenableWorker.Result].  After this time has expired, the worker will be signalled to stop.
+ * [ListenableWorker.Result]. After this time has expired, the worker will be signalled to stop.
  */
 public abstract class CoroutineWorker(
     appContext: Context,
-    params: WorkerParameters
+    private val params: WorkerParameters
 ) : ListenableWorker(appContext, params) {
 
     /**
-     * The coroutine context on which [doWork] will run. By default, this is [Dispatchers.Default].
+     * The coroutine context on which [doWork] will run.
+     *
+     * If this property is overridden then it takes precedent over [Configuration.executor] or
+     * [Configuration.workerCoroutineContext].
+     *
+     * By default, this is a dispatcher delegating to [Dispatchers.Default]
      */
     @Deprecated(message = "use withContext(...) inside doWork() instead.")
-    public open val coroutineContext: CoroutineDispatcher = Dispatchers.Default
+    public open val coroutineContext: CoroutineDispatcher = DeprecatedDispatcher
 
     @Suppress("DEPRECATION")
     public final override fun startWork(): ListenableFuture<Result> {
+        // if a developer didn't override coroutineContext property, then
+        // we use Dispatchers.Default directly.
+        // We can't fully implement delegating CoroutineDispatcher, because CoroutineDispatcher
+        // has experimental and internal apis.
+        val coroutineContext = if (coroutineContext != DeprecatedDispatcher) {
+            coroutineContext
+        } else {
+            params.workerContext
+        }
+
         return launchFuture(coroutineContext + Job()) { doWork() }
     }
 
@@ -107,4 +126,15 @@
     public final override fun onStopped() {
         super.onStopped()
     }
+
+    private object DeprecatedDispatcher : CoroutineDispatcher() {
+        val dispatcher = Dispatchers.Default
+        override fun dispatch(context: CoroutineContext, block: Runnable) {
+            dispatcher.dispatch(context, block)
+        }
+
+        override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+            return dispatcher.isDispatchNeeded(context)
+        }
+    }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java b/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java
index fa1d5dd..fa8f5a2 100644
--- a/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java
+++ b/work/work-runtime/src/main/java/androidx/work/WorkerParameters.java
@@ -26,6 +26,8 @@
 import androidx.annotation.RestrictTo;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
 
+import kotlin.coroutines.CoroutineContext;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -46,6 +48,7 @@
     private @NonNull RuntimeExtras mRuntimeExtras;
     private int mRunAttemptCount;
     private @NonNull Executor mBackgroundExecutor;
+    private @NonNull CoroutineContext mWorkerContext;
     private @NonNull TaskExecutor mWorkTaskExecutor;
     private @NonNull WorkerFactory mWorkerFactory;
     private @NonNull ProgressUpdater mProgressUpdater;
@@ -63,6 +66,7 @@
             @IntRange(from = 0) int runAttemptCount,
             @IntRange(from = 0) int generation,
             @NonNull Executor backgroundExecutor,
+            @NonNull CoroutineContext workerContext,
             @NonNull TaskExecutor workTaskExecutor,
             @NonNull WorkerFactory workerFactory,
             @NonNull ProgressUpdater progressUpdater,
@@ -74,6 +78,7 @@
         mRunAttemptCount = runAttemptCount;
         mGeneration = generation;
         mBackgroundExecutor = backgroundExecutor;
+        mWorkerContext = workerContext;
         mWorkTaskExecutor = workTaskExecutor;
         mWorkerFactory = workerFactory;
         mProgressUpdater = progressUpdater;
@@ -184,6 +189,13 @@
     /**
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public @NonNull CoroutineContext getWorkerContext() {
+        return mWorkerContext;
+    }
+
+    /**
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public @NonNull TaskExecutor getTaskExecutor() {
         return mWorkTaskExecutor;
     }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
index fd7b5f6..9e1e1b47 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
@@ -173,6 +173,7 @@
             workSpec.runAttemptCount,
             workSpec.generation,
             configuration.executor,
+            configuration.workerCoroutineContext,
             workTaskExecutor,
             configuration.workerFactory,
             WorkProgressUpdater(workDatabase, workTaskExecutor),
diff --git a/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt b/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt
index 99ae5f7..cd6c543 100644
--- a/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt
+++ b/work/work-rxjava2/src/test/java/androidx/work/RxForegroundInfoTest.kt
@@ -25,6 +25,7 @@
 import io.reactivex.Single
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -94,6 +95,7 @@
     1,
     0,
     executor,
+    EmptyCoroutineContext,
     RxWorkerTest.InstantWorkTaskExecutor(),
     DefaultWorkerFactory,
     progressUpdater,
diff --git a/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt b/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
index 25e93b7..ada365e 100644
--- a/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
+++ b/work/work-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
@@ -26,6 +26,7 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
+import kotlin.coroutines.EmptyCoroutineContext
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Assert
@@ -127,6 +128,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt b/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
index fec7eec..e6a60c4 100644
--- a/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
+++ b/work/work-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
@@ -22,6 +22,7 @@
 import androidx.work.impl.utils.futures.SettableFuture
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -64,6 +65,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         RxWorkerTest.InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt
index 51c70f6..35a6b31 100644
--- a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt
+++ b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxForegroundInfoTest.kt
@@ -31,6 +31,7 @@
 import io.reactivex.rxjava3.core.Single
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -100,6 +101,7 @@
     1,
     0,
     executor,
+    EmptyCoroutineContext,
     RxWorkerTest.InstantWorkTaskExecutor(),
     DefaultWorkerFactory,
     progressUpdater,
diff --git a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt
index 9edd2de..4d497f3 100644
--- a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt
+++ b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/RxWorkerTest.kt
@@ -32,6 +32,7 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
+import kotlin.coroutines.EmptyCoroutineContext
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Assert
@@ -133,6 +134,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt
index 5bc2ab1..b528b562 100644
--- a/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt
+++ b/work/work-rxjava3/src/test/java/androidx/work/rxjava3/SetCompletableProgressTest.kt
@@ -27,6 +27,7 @@
 import androidx.work.impl.utils.futures.SettableFuture
 import java.util.UUID
 import java.util.concurrent.Executor
+import kotlin.coroutines.EmptyCoroutineContext
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -69,6 +70,7 @@
         1,
         0,
         executor,
+        EmptyCoroutineContext,
         RxWorkerTest.InstantWorkTaskExecutor(),
         DefaultWorkerFactory,
         progressUpdater,
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java b/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
index 8a65620..c06abc9 100644
--- a/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
+++ b/work/work-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
@@ -42,6 +42,8 @@
 import java.util.UUID;
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.Dispatchers;
+
 /**
  * Builds instances of {@link androidx.work.ListenableWorker} which can be used for testing.
  *
@@ -321,6 +323,7 @@
                         mGeneration,
                         // This is unused for ListenableWorker
                         getExecutor(),
+                        Dispatchers.getDefault(),
                         getTaskExecutor(),
                         mWorkerFactory,
                         getProgressUpdater(),