Merge "Preserve backwards compatbility in how WorkManager task executors are configured in test mode." into androidx-master-dev
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index f0b9bf1..fd3836f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -35,9 +35,8 @@
     prebuilts(LibraryGroups.ARCH_CORE, "2.1.0-rc01")
     prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, "1.0.0")
     prebuilts(LibraryGroups.AUTOFILL, "1.0.0-alpha02")
-    prebuilts(LibraryGroups.BENCHMARK, "benchmark", "1.0.0-alpha03")
-    ignore(LibraryGroups.BENCHMARK.group, "benchmark-common")
     ignore(LibraryGroups.BENCHMARK.group, "benchmark-gradle-plugin")
+    prebuilts(LibraryGroups.BENCHMARK, "1.0.0-alpha04")
     prebuilts(LibraryGroups.BIOMETRIC, "biometric", "1.0.0-beta01")
     prebuilts(LibraryGroups.BROWSER, "1.2.0-alpha07")
     ignore(LibraryGroups.CAMERA.group, "camera-testing")
diff --git a/paging/common/api/3.0.0-alpha01.txt b/paging/common/api/3.0.0-alpha01.txt
index 684ff7e..cd5b0722 100644
--- a/paging/common/api/3.0.0-alpha01.txt
+++ b/paging/common/api/3.0.0-alpha01.txt
@@ -181,9 +181,11 @@
     method public suspend Object buildAsync(kotlin.coroutines.Continuation<? super androidx.paging.PagedList<Value>> p);
     method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
     method public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
-    method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
     method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
-    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
   }
 
   public abstract static class PagedList.Callback {
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index 684ff7e..cd5b0722 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -181,9 +181,11 @@
     method public suspend Object buildAsync(kotlin.coroutines.Continuation<? super androidx.paging.PagedList<Value>> p);
     method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
     method public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
-    method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
     method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
-    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
   }
 
   public abstract static class PagedList.Callback {
diff --git a/paging/common/api/restricted_3.0.0-alpha01.txt b/paging/common/api/restricted_3.0.0-alpha01.txt
index 9dab4f5..87c9156 100644
--- a/paging/common/api/restricted_3.0.0-alpha01.txt
+++ b/paging/common/api/restricted_3.0.0-alpha01.txt
@@ -140,9 +140,10 @@
   }
 
   public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public PagedList(kotlinx.coroutines.CoroutineScope coroutineScope, androidx.paging.PagedSource<?,T> pagedSource, androidx.paging.PagedStorage<T> storage, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher backgroundDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config);
     method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
     method public void addWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final suspend <K, T> Object create(androidx.paging.PagedSource<K,T> p, kotlinx.coroutines.CoroutineScope pagedSource, java.util.concurrent.Executor coroutineScope, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<T>? initialLoadExecutor, androidx.paging.PagedList.Config boundaryCallback, K? config, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> key);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final suspend <K, T> Object create(androidx.paging.PagedSource<K,T> p, kotlinx.coroutines.CoroutineScope pagedSource, kotlinx.coroutines.CoroutineDispatcher coroutineScope, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? initialFetchDispatcher, androidx.paging.PagedList.Config boundaryCallback, K? config, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> key);
     method public abstract void detach();
     method public T? get(int index);
     method public androidx.paging.PagedList.Config getConfig();
@@ -188,9 +189,11 @@
     method public suspend Object buildAsync(kotlin.coroutines.Continuation<? super androidx.paging.PagedList<Value>> p);
     method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
     method public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
-    method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
     method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
-    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
   }
 
   public abstract static class PagedList.Callback {
@@ -201,7 +204,7 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final class PagedList.Companion {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public suspend <K, T> Object create(androidx.paging.PagedSource<K,T> pagedSource, kotlinx.coroutines.CoroutineScope coroutineScope, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, java.util.concurrent.Executor initialLoadExecutor, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config, K? key, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> p);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public suspend <K, T> Object create(androidx.paging.PagedSource<K,T> pagedSource, kotlinx.coroutines.CoroutineScope coroutineScope, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher, kotlinx.coroutines.CoroutineDispatcher initialFetchDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config, K? key, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> p);
   }
 
   public static final class PagedList.Config {
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index 9dab4f5..87c9156 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -140,9 +140,10 @@
   }
 
   public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public PagedList(kotlinx.coroutines.CoroutineScope coroutineScope, androidx.paging.PagedSource<?,T> pagedSource, androidx.paging.PagedStorage<T> storage, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher backgroundDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config);
     method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
     method public void addWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final suspend <K, T> Object create(androidx.paging.PagedSource<K,T> p, kotlinx.coroutines.CoroutineScope pagedSource, java.util.concurrent.Executor coroutineScope, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<T>? initialLoadExecutor, androidx.paging.PagedList.Config boundaryCallback, K? config, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> key);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final suspend <K, T> Object create(androidx.paging.PagedSource<K,T> p, kotlinx.coroutines.CoroutineScope pagedSource, kotlinx.coroutines.CoroutineDispatcher coroutineScope, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? initialFetchDispatcher, androidx.paging.PagedList.Config boundaryCallback, K? config, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> key);
     method public abstract void detach();
     method public T? get(int index);
     method public androidx.paging.PagedList.Config getConfig();
@@ -188,9 +189,11 @@
     method public suspend Object buildAsync(kotlin.coroutines.Continuation<? super androidx.paging.PagedList<Value>> p);
     method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
     method public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
-    method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
     method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
-    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+    method public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
   }
 
   public abstract static class PagedList.Callback {
@@ -201,7 +204,7 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final class PagedList.Companion {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public suspend <K, T> Object create(androidx.paging.PagedSource<K,T> pagedSource, kotlinx.coroutines.CoroutineScope coroutineScope, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, java.util.concurrent.Executor initialLoadExecutor, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config, K? key, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> p);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public suspend <K, T> Object create(androidx.paging.PagedSource<K,T> pagedSource, kotlinx.coroutines.CoroutineScope coroutineScope, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher, kotlinx.coroutines.CoroutineDispatcher initialFetchDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config, K? key, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> p);
   }
 
   public static final class PagedList.Config {
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index 3920cb8..e6075be 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -32,7 +32,7 @@
     compile("androidx.annotation:annotation:1.1.0")
     compile(ARCH_CORE_COMMON)
     api(KOTLIN_STDLIB)
-    api(KOTLIN_COROUTINES)
+    api(KOTLIN_COROUTINES_CORE)
 
     testCompile(JUNIT)
     testCompile(MOCKITO_CORE)
@@ -40,6 +40,7 @@
         exclude group: 'org.mockito' // to keep control on the mockito version
     }
     testImplementation project(':internal-testutils-common')
+    testImplementation project(':internal-testutils-ktx')
     testImplementation(KOTLIN_TEST_COMMON)
     testImplementation(TRUTH)
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
index e583f30..223d698 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -20,9 +20,8 @@
 import androidx.annotation.RestrictTo
 import androidx.paging.PagedSource.KeyProvider
 import androidx.paging.PagedSource.LoadResult.Companion.COUNT_UNDEFINED
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asCoroutineDispatcher
-import java.util.concurrent.Executor
 
 /**
  * @hide
@@ -31,17 +30,18 @@
 open class ContiguousPagedList<K : Any, V : Any>(
     pagedSource: PagedSource<K, V>,
     coroutineScope: CoroutineScope,
-    mainThreadExecutor: Executor,
-    backgroundThreadExecutor: Executor,
+    notifyDispatcher: CoroutineDispatcher,
+    backgroundDispatcher: CoroutineDispatcher,
     boundaryCallback: BoundaryCallback<V>?,
     config: Config,
     initialResult: PagedSource.LoadResult<K, V>,
     lastLoad: Int
 ) : PagedList<V>(
+    coroutineScope,
     pagedSource,
     PagedStorage<V>(),
-    mainThreadExecutor,
-    backgroundThreadExecutor,
+    notifyDispatcher,
+    backgroundDispatcher,
     boundaryCallback,
     config
 ), PagedStorage.Callback, Pager.PageConsumer<V> {
@@ -75,8 +75,8 @@
         coroutineScope,
         config,
         pagedSource,
-        mainThreadExecutor.asCoroutineDispatcher(),
-        backgroundThreadExecutor.asCoroutineDispatcher(),
+        notifyDispatcher,
+        backgroundDispatcher,
         this,
         initialResult,
         storage
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
index 4aa8792..7407055 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -17,7 +17,7 @@
 package androidx.paging
 
 import androidx.annotation.RestrictTo
-import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.DirectDispatcher
 import kotlinx.coroutines.CoroutineScope
 
 /**
@@ -37,8 +37,8 @@
 ) : ContiguousPagedList<K, V>(
     pagedSource,
     coroutineScope,
-    DirectExecutor,
-    DirectExecutor,
+    DirectDispatcher,
+    DirectDispatcher,
     null,
     config,
     PagedSource.LoadResult.empty(),
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 6d56579..c0bde0e 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -28,10 +28,13 @@
 import androidx.paging.PagedList.LoadState
 import androidx.paging.PagedList.LoadType
 import androidx.paging.PagedSource.KeyProvider
-import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.DirectDispatcher
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import java.lang.ref.WeakReference
@@ -45,10 +48,11 @@
  *
  * Used to observe the [LoadState] of any [LoadType] (REFRESH/START/END). For UI purposes (swipe
  * refresh, loading spinner, retry button), this is typically done by registering a
- * [LoadStateListener] with the [PagedListAdapter] or [AsyncPagedListDiffer].
+ * [LoadStateListener] with the [androidx.paging.PagedListAdapter] or
+ * [androidx.paging.AsyncPagedListDiffer].
  *
- * These calls will be dispatched on the executor defined by [Builder.setNotifyExecutor], which is
- * generally the main/UI thread.
+ * These calls will be dispatched on the executor defined by [PagedList.Builder.setNotifyExecutor],
+ * which is generally the main/UI thread.
  *
  * Called when the LoadState has changed - whether the refresh, prepend, or append is idle, loading,
  * or has an error.
@@ -156,10 +160,10 @@
          * thread,posting updates to the main thread.
          *
          * @param pagedSource [PagedSource] providing data to the [PagedList]
-         * @param notifyExecutor Thread tat will use and consume data from the [PagedList].
-         * Generally, this is the UI/main thread.
-         * @param fetchExecutor Data loading will be done via this executor - should be a background
-         * thread.
+         * @param notifyDispatcher [CoroutineDispatcher] that will use and consume data from the
+         * [PagedList]. Generally, this is the UI/main thread.
+         * @param fetchDispatcher Data loading jobs will be dispatched to this
+         * [CoroutineDispatcher] - should be a background thread.
          * @param boundaryCallback Optional boundary callback to attach to the list.
          * @param config [PagedList.Config], which defines how the [PagedList] will load data.
          * @param K Key type that indicates to the [PagedSource] what data to load.
@@ -175,9 +179,9 @@
         suspend fun <K : Any, T : Any> create(
             pagedSource: PagedSource<K, T>,
             coroutineScope: CoroutineScope,
-            notifyExecutor: Executor,
-            fetchExecutor: Executor,
-            initialLoadExecutor: Executor,
+            notifyDispatcher: CoroutineDispatcher,
+            fetchDispatcher: CoroutineDispatcher,
+            initialFetchDispatcher: CoroutineDispatcher,
             boundaryCallback: BoundaryCallback<T>?,
             config: Config,
             key: K?
@@ -195,15 +199,15 @@
                 config.pageSize
             )
 
-            val initialResult = withContext(initialLoadExecutor.asCoroutineDispatcher()) {
+            val initialResult = withContext(initialFetchDispatcher) {
                 pagedSource.load(params)
             }
 
             return ContiguousPagedList(
                 pagedSource,
                 coroutineScope,
-                notifyExecutor,
-                fetchExecutor,
+                notifyDispatcher,
+                fetchDispatcher,
                 boundaryCallback,
                 config,
                 initialResult,
@@ -270,7 +274,7 @@
         /**
          * Loading hit a retryable error.
          *
-         * @see .retry
+         * @see retry
          */
         RETRYABLE_ERROR
     }
@@ -278,7 +282,7 @@
     /**
      * Builder class for [PagedList].
      *
-     * [PagedSource], [Config], main thread and background executor must all be provided.
+     * [pagedSource], [config], [notifyDispatcher] and [fetchDispatcher] must all be provided.
      *
      * A [PagedList] queries initial data from its [PagedSource] during construction, to avoid empty
      * PagedLists being presented to the UI when possible. It's preferred to present initial data,
@@ -295,8 +299,8 @@
         private val pagedSource: PagedSource<Key, Value>
         private val config: Config
         private var coroutineScope: CoroutineScope = GlobalScope
-        private var notifyExecutor: Executor? = null
-        private var fetchExecutor: Executor? = null
+        private var notifyDispatcher: CoroutineDispatcher? = null
+        private var fetchDispatcher: CoroutineDispatcher? = null
         private var boundaryCallback: BoundaryCallback<Value>? = null
         private var initialKey: Key? = null
 
@@ -378,18 +382,37 @@
         }
 
         /**
-         * The executor defining where page loading updates are dispatched.
+         * The [Executor] defining where page loading updates are dispatched.
          *
-         * @param notifyExecutor Executor that receives [PagedList] updates, and where [Callback]
+         * @param notifyExecutor [Executor] that receives [PagedList] updates, and where [Callback]
          * calls are dispatched. Generally, this is the ui/main thread.
          * @return this
          */
+        @Deprecated(
+            message = "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
+                    "consider passing a CoroutineDispatcher directly",
+            replaceWith = ReplaceWith(
+                "setNotifyDispatcher(fetchExecutor.asCoroutineDispatcher())",
+                "kotlinx.coroutines.asCoroutineDispatcher"
+            )
+        )
         fun setNotifyExecutor(notifyExecutor: Executor) = apply {
-            this.notifyExecutor = notifyExecutor
+            this.notifyDispatcher = notifyExecutor.asCoroutineDispatcher()
         }
 
         /**
-         * The executor used to fetch additional pages from the [PagedSource].
+         * The [CoroutineDispatcher] defining where page loading updates are dispatched.
+         *
+         * @param notifyDispatcher [CoroutineDispatcher] that receives [PagedList] updates, and where
+         * [Callback] calls are dispatched. Generally, this is the ui/main thread.
+         * @return this
+         */
+        fun setNotifyDispatcher(notifyDispatcher: CoroutineDispatcher) = apply {
+            this.notifyDispatcher = notifyDispatcher
+        }
+
+        /**
+         * The [Executor] used to fetch additional pages from the [PagedSource].
          *
          * Does not affect initial load, which will be done immediately on whichever thread the
          * [PagedList] is created on.
@@ -398,8 +421,30 @@
          * thread pool for e.g. I/O or network loading.
          * @return this
          */
+        @Deprecated(
+            message = "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
+                    "consider passing a CoroutineDispatcher directly",
+            replaceWith = ReplaceWith(
+                "setFetchDispatcher(fetchExecutor.asCoroutineDispatcher())",
+                "kotlinx.coroutines.asCoroutineDispatcher"
+            )
+        )
         fun setFetchExecutor(fetchExecutor: Executor) = apply {
-            this.fetchExecutor = fetchExecutor
+            this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher()
+        }
+
+        /**
+         * The [CoroutineDispatcher] used to fetch additional pages from the [PagedSource].
+         *
+         * Does not affect initial load, which will be done immediately on whichever thread the
+         * [PagedList] is created on.
+         *
+         * @param fetchDispatcher [CoroutineDispatcher] used to fetch from [PagedSource]s,
+         * generally a background thread pool for e.g. I/O or network loading.
+         * @return this
+         */
+        fun setFetchDispatcher(fetchDispatcher: CoroutineDispatcher) = apply {
+            this.fetchDispatcher = fetchDispatcher
         }
 
         /**
@@ -444,25 +489,18 @@
          * the [PagedList] will be immediately [detached][PagedList.isDetached], and you can retry
          * construction (including setting a new [PagedSource]).
          *
-         * @throws IllegalArgumentException if [notifyExecutor] or [fetchExecutor] are not set.
+         * @throws IllegalArgumentException if [notifyDispatcher] or [fetchDispatcher] are not set.
          *
          * @return The newly constructed [PagedList]
          */
         @WorkerThread
         @Deprecated(
-            "This method has no means of handling errors encountered during initial load, and" +
-                    " blocks on the initial load result. Use {@link #buildAsync()} instead."
+            message = "This method has no means of handling errors encountered during initial " +
+                    "load, and blocks on the initial load result.",
+            replaceWith = ReplaceWith("buildAsync()")
         )
-        fun build(): PagedList<Value> {
-            // TODO: define defaults, once they can be used in module without android dependency
-            if (notifyExecutor == null) {
-                throw IllegalArgumentException("MainThreadExecutor required")
-            }
-            if (fetchExecutor == null) {
-                throw IllegalArgumentException("BackgroundThreadExecutor required")
-            }
-
-            return runBlocking { create(DirectExecutor) }
+        fun build(): PagedList<Value> = runBlocking {
+            create(DirectDispatcher)
         }
 
         /**
@@ -472,41 +510,35 @@
          * [PagedSource.LoadType.INITIAL], and return a [PagedList] once it completes, triggering
          * [loadStateListeners].
          *
-         * @throws IllegalArgumentException if [notifyExecutor] or [fetchExecutor] are not set.
+         * @throws IllegalArgumentException if [notifyDispatcher] or [fetchDispatcher] are not set.
          *
          * @return The newly constructed [PagedList]
          */
-        @Suppress("unused")
+        @Suppress("unused") // Public API
         suspend fun buildAsync(): PagedList<Value> {
-            // TODO: define defaults, once they can be used in module without android dependency
-            if (notifyExecutor == null) {
-                throw IllegalArgumentException("MainThreadExecutor required")
-            }
-            if (fetchExecutor == null) {
-                throw IllegalArgumentException("BackgroundThreadExecutor required")
-            }
-
-            return create(fetchExecutor!!)
+            return create(fetchDispatcher ?: Dispatchers.IO)
         }
 
-        private suspend fun create(initialFetchExecutor: Executor): PagedList<Value> =
-            create(
+        private suspend fun create(initialFetchDispatcher: CoroutineDispatcher): PagedList<Value> {
+            return create(
                 pagedSource,
                 coroutineScope,
-                notifyExecutor!!,
-                fetchExecutor!!,
-                initialFetchExecutor,
+                notifyDispatcher ?: Dispatchers.Main,
+                fetchDispatcher ?: Dispatchers.IO,
+                initialFetchDispatcher,
                 boundaryCallback,
                 config,
                 initialKey
             )
+        }
     }
 
     /**
      * Callback signaling when content is loaded into the list.
      *
      * Can be used to listen to items being paged in and out. These calls will be dispatched on
-     * the executor defined by [Builder.setNotifyExecutor], which is generally the main/UI thread.
+     * the dispatcher defined by [Builder.setNotifyDispatcher], which is generally the main/UI
+     * thread.
      */
     abstract class Callback {
         /**
@@ -924,19 +956,21 @@
     /**
      * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     constructor(
+        coroutineScope: CoroutineScope,
         pagedSource: PagedSource<*, T>,
         storage: PagedStorage<T>,
-        mainThreadExecutor: Executor,
-        backgroundThreadExecutor: Executor,
+        notifyDispatcher: CoroutineDispatcher,
+        backgroundDispatcher: CoroutineDispatcher,
         boundaryCallback: BoundaryCallback<T>?,
         config: Config
     ) : super() {
+        this.coroutineScope = coroutineScope
         this.pagedSource = pagedSource
         this.storage = storage
-        this.mainThreadExecutor = mainThreadExecutor
-        this.backgroundThreadExecutor = backgroundThreadExecutor
+        this.notifyDispatcher = notifyDispatcher
+        this.backgroundDispatcher = backgroundDispatcher
         this.boundaryCallback = boundaryCallback
         this.config = config
         this.callbacks = ArrayList()
@@ -952,8 +986,9 @@
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise
     fun getStorage() = storage
 
-    internal val mainThreadExecutor: Executor
-    internal val backgroundThreadExecutor: Executor
+    internal val notifyDispatcher: CoroutineDispatcher
+    internal val backgroundDispatcher: CoroutineDispatcher
+
     internal val boundaryCallback: BoundaryCallback<T>?
 
     internal var refreshRetryCallback: Runnable? = null
@@ -978,6 +1013,8 @@
      */
     open val config: Config
 
+    internal val coroutineScope: CoroutineScope
+
     private val callbacks: MutableList<WeakReference<Callback>>
 
     private val loadStateListeners: MutableList<WeakReference<LoadStateListener>>
@@ -1012,7 +1049,10 @@
      * @throws IllegalStateException if this [PagedList] was instantiated without a
      * [PagedSourceWrapper] wrapping a backing [DataSource]
      */
-    @Deprecated("DataSource is deprecated and has been replaced by PagedSource")
+    @Deprecated(
+        message = "DataSource is deprecated and has been replaced by PagedSource",
+        replaceWith = ReplaceWith("pagedSource")
+    )
     val dataSource: DataSource<*, T>
         get() {
             if (pagedSource is PagedSourceWrapper) return pagedSource.dataSource
@@ -1219,7 +1259,7 @@
 
         if (deferEmpty || deferBegin || deferEnd) {
             // Post to the main thread, since we may be on creation thread currently
-            mainThreadExecutor.execute {
+            coroutineScope.launch(notifyDispatcher) {
                 // on is dispatched immediately, since items won't be accessed
 
                 if (deferEmpty) {
@@ -1258,7 +1298,9 @@
             boundaryCallbackEndDeferred = false
         }
         if (post) {
-            mainThreadExecutor.execute { dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd) }
+            coroutineScope.launch(notifyDispatcher) {
+                dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd)
+            }
         } else {
             dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd)
         }
@@ -1409,8 +1451,8 @@
  * @param Value Type of items held and loaded by the [PagedList].
  * @param dataSource [DataSource] the [PagedList] will load from.
  * @param config Config that defines how the [PagedList] loads data from its [DataSource].
- * @param notifyExecutor Executor that receives [PagedList] updates, and where [PagedList.Callback]
- * calls are dispatched. Generally, this is the UI/main thread.
+ * @param notifyExecutor [Executor] that receives [PagedList] updates, and where
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI/main thread.
  * @param fetchExecutor [Executor] used to fetch from [DataSource]s, generally a background thread
  * pool for e.g. I/O or network loading.
  * @param boundaryCallback [PagedList.BoundaryCallback] for listening to out-of-data events.
diff --git a/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
index 5688543..a0b556b 100644
--- a/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
@@ -17,10 +17,11 @@
 package androidx.paging
 
 internal class SnapshotPagedList<T : Any>(private val pagedList: PagedList<T>) : PagedList<T>(
+    pagedList.coroutineScope,
     pagedList.pagedSource,
     pagedList.storage.snapshot(),
-    pagedList.mainThreadExecutor,
-    pagedList.backgroundThreadExecutor,
+    pagedList.notifyDispatcher,
+    pagedList.backgroundDispatcher,
     null,
     pagedList.config
 ) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/futures/DirectDispatcher.kt b/paging/common/src/main/kotlin/androidx/paging/futures/DirectDispatcher.kt
new file mode 100644
index 0000000..436f49a
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/futures/DirectDispatcher.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 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.paging.futures
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * [CoroutineDispatcher] which immediately runs new jobs on the current thread.
+ */
+internal object DirectDispatcher : CoroutineDispatcher() {
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        block.run()
+    }
+}
diff --git a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
index a36c36f..292a305 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -20,8 +20,8 @@
 import androidx.paging.PagedList.LoadState.IDLE
 import androidx.paging.PagedList.LoadState.LOADING
 import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
-import androidx.paging.futures.DirectExecutor
-import androidx.testutils.TestExecutor
+import androidx.paging.futures.DirectDispatcher
+import androidx.testutils.TestDispatcher
 import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.reset
 import com.nhaarman.mockitokotlin2.verify
@@ -39,8 +39,8 @@
 
 @RunWith(Parameterized::class)
 class ContiguousPagedListTest(private val placeholdersEnabled: Boolean) {
-    private val mainThread = TestExecutor()
-    private val backgroundThread = TestExecutor()
+    private val mainThread = TestDispatcher()
+    private val backgroundThread = TestDispatcher()
 
     private class Item(position: Int) {
         val pos: Int = position
@@ -179,7 +179,7 @@
                 GlobalScope,
                 mainThread,
                 backgroundThread,
-                DirectExecutor,
+                DirectDispatcher,
                 boundaryCallback,
                 PagedList.Config.Builder()
                     .setPageSize(pageSize)
@@ -212,9 +212,9 @@
             PagedList.create(
                 PagedSourceWrapper(ItemDataSource()),
                 GlobalScope,
-                FailExecutor(),
-                DirectExecutor,
-                DirectExecutor,
+                FailDispatcher(),
+                DirectDispatcher,
+                DirectDispatcher,
                 null,
                 PagedList.Config.Builder().setPageSize(10).build(),
                 null
@@ -902,11 +902,10 @@
     }
 
     private fun drain() {
-        var executed: Boolean
-        do {
-            executed = backgroundThread.executeAll()
-            executed = mainThread.executeAll() || executed
-        } while (executed)
+        while (backgroundThread.queue.isNotEmpty() || mainThread.queue.isNotEmpty()) {
+            backgroundThread.executeAll()
+            mainThread.executeAll()
+        }
     }
 
     companion object {
diff --git a/paging/common/src/test/kotlin/androidx/paging/FailExecutor.kt b/paging/common/src/test/kotlin/androidx/paging/FailDispatcher.kt
similarity index 69%
rename from paging/common/src/test/kotlin/androidx/paging/FailExecutor.kt
rename to paging/common/src/test/kotlin/androidx/paging/FailDispatcher.kt
index 1c2b0d5..9e41203 100644
--- a/paging/common/src/test/kotlin/androidx/paging/FailExecutor.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/FailDispatcher.kt
@@ -16,11 +16,15 @@
 
 package androidx.paging
 
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Runnable
 import org.junit.Assert.fail
-import java.util.concurrent.Executor
+import kotlin.coroutines.CoroutineContext
 
-class FailExecutor(val string: String = "Executor expected to be unused") : Executor {
-    override fun execute(runnable: Runnable?) {
+class FailDispatcher(
+    val string: String = "Executor expected to be unused"
+) : CoroutineDispatcher() {
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
         fail(string)
     }
 }
diff --git a/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
index 4068366..36a6381 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -17,6 +17,7 @@
 package androidx.paging
 
 import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.DirectDispatcher
 import com.nhaarman.mockitokotlin2.capture
 import com.nhaarman.mockitokotlin2.mock
 import kotlinx.coroutines.GlobalScope
@@ -291,9 +292,9 @@
             PagedList.create(
                 PagedSourceWrapper(dataSource),
                 GlobalScope,
-                FailExecutor(),
-                DirectExecutor,
-                DirectExecutor,
+                FailDispatcher(),
+                DirectDispatcher,
+                DirectDispatcher,
                 null,
                 PagedList.Config.Builder()
                     .setPageSize(10)
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
index af58970..11b7cda 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
@@ -16,11 +16,10 @@
 
 package androidx.paging
 
-import androidx.paging.futures.DirectExecutor
-import androidx.testutils.TestExecutor
+import androidx.paging.futures.DirectDispatcher
+import androidx.testutils.TestDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.async
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
@@ -39,8 +38,8 @@
 
 @RunWith(JUnit4::class)
 class PageKeyedDataSourceTest {
-    private val mainThread = TestExecutor()
-    private val backgroundThread = TestExecutor()
+    private val mainThread = TestDispatcher()
+    private val backgroundThread = TestDispatcher()
 
     internal data class Item(val name: String)
 
@@ -94,7 +93,7 @@
     fun loadFullVerify() {
         // validate paging entire ItemDataSource results in full, correctly ordered data
         val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
-        val pagedListJob = testCoroutineScope.async(backgroundThread.asCoroutineDispatcher()) {
+        val pagedListJob = testCoroutineScope.async(backgroundThread) {
             PagedList.create(
                 PagedSourceWrapper(ItemDataSource()),
                 testCoroutineScope,
@@ -160,9 +159,9 @@
             PagedList.create(
                 PagedSourceWrapper(dataSource),
                 GlobalScope,
-                FailExecutor(),
-                DirectExecutor,
-                DirectExecutor,
+                FailDispatcher(),
+                DirectDispatcher,
+                DirectDispatcher,
                 null,
                 PagedList.Config.Builder()
                     .setPageSize(10)
@@ -260,16 +259,16 @@
         @Suppress("UNCHECKED_CAST")
         val boundaryCallback =
             mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
-        val executor = TestExecutor()
+        val dispatcher = TestDispatcher()
 
         val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
-        val pagedListJob = testCoroutineScope.async(executor.asCoroutineDispatcher()) {
+        val pagedListJob = testCoroutineScope.async(dispatcher) {
             PagedList.create(
                 PagedSourceWrapper(dataSource),
                 testCoroutineScope,
-                executor,
-                executor,
-                executor,
+                dispatcher,
+                dispatcher,
+                dispatcher,
                 boundaryCallback,
                 PagedList.Config.Builder()
                     .setPageSize(10)
@@ -278,14 +277,14 @@
             )
         }
 
-        executor.executeAll()
+        dispatcher.executeAll()
 
         val pagedList = runBlocking { pagedListJob.await() }
         pagedList.loadAround(0)
 
         verifyZeroInteractions(boundaryCallback)
 
-        executor.executeAll()
+        dispatcher.executeAll()
 
         // verify boundary callbacks are triggered
         verify(boundaryCallback).onItemAtFrontLoaded("A")
@@ -322,16 +321,16 @@
         @Suppress("UNCHECKED_CAST")
         val boundaryCallback =
             mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
-        val executor = TestExecutor()
+        val dispatcher = TestDispatcher()
 
         val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
-        val pagedListJob = testCoroutineScope.async(executor.asCoroutineDispatcher()) {
+        val pagedListJob = testCoroutineScope.async(dispatcher) {
             PagedList.create(
                 PagedSourceWrapper(dataSource),
                 testCoroutineScope,
-                executor,
-                executor,
-                executor,
+                dispatcher,
+                dispatcher,
+                dispatcher,
                 boundaryCallback,
                 PagedList.Config.Builder()
                     .setPageSize(10)
@@ -339,14 +338,14 @@
                 ""
             )
         }
-        executor.executeAll()
+        dispatcher.executeAll()
         val pagedList = runBlocking { pagedListJob.await() }
 
         pagedList.loadAround(0)
 
         verifyZeroInteractions(boundaryCallback)
 
-        executor.executeAll()
+        dispatcher.executeAll()
 
         // verify boundary callbacks are triggered
         verify(boundaryCallback).onItemAtFrontLoaded("B")
@@ -516,7 +515,6 @@
         private const val INIT_KEY: String = "key 2"
         private val PAGE_MAP: Map<String, Page>
         private val ITEM_LIST: List<Item>
-        private val EXCEPTION = Exception()
 
         init {
             val map = HashMap<String, Page>()
@@ -537,10 +535,9 @@
     }
 
     private fun drain() {
-        var executed: Boolean
-        do {
-            executed = backgroundThread.executeAll()
-            executed = mainThread.executeAll() || executed
-        } while (executed)
+        while (backgroundThread.queue.isNotEmpty() || mainThread.queue.isNotEmpty()) {
+            backgroundThread.executeAll()
+            mainThread.executeAll()
+        }
     }
 }
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
index f162572..05faea4 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
@@ -26,8 +26,8 @@
     @Test
     fun defaults() {
         val config = PagedList.Config.Builder()
-                .setPageSize(10)
-                .build()
+            .setPageSize(10)
+            .build()
         Assert.assertEquals(10, config.pageSize)
         Assert.assertEquals(30, config.initialLoadSizeHint)
         Assert.assertEquals(true, config.enablePlaceholders)
@@ -38,18 +38,18 @@
     @Test(expected = IllegalArgumentException::class)
     fun maxSizeTooSmall() {
         PagedList.Config.Builder()
-                .setPageSize(20)
-                .setPrefetchDistance(15)
-                .setMaxSize(49)
-                .build()
+            .setPageSize(20)
+            .setPrefetchDistance(15)
+            .setMaxSize(49)
+            .build()
     }
 
     @Test
     fun maxSizeAccepted() {
         PagedList.Config.Builder()
-                .setPageSize(20)
-                .setPrefetchDistance(15)
-                .setMaxSize(50)
-                .build()
+            .setPageSize(20)
+            .setPrefetchDistance(15)
+            .setMaxSize(50)
+            .build()
     }
 }
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
index e79ef38..3f4c9b9 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
@@ -16,10 +16,10 @@
 
 package androidx.paging
 
-import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.DirectDispatcher
+import androidx.testutils.TestDispatcher
 import androidx.testutils.TestExecutor
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.async
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
@@ -54,15 +54,15 @@
     }
 
     private val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
-    private val mainThread = TestExecutor()
-    private val backgroundThread = TestExecutor()
+    private val mainThread = TestDispatcher()
+    private val backgroundThread = TestDispatcher()
 
     @Test
-    fun createLegacy() = runBlocking {
+    fun createLegacy() {
         @Suppress("DEPRECATION")
         val pagedList = PagedList.Builder(ListDataSource(ITEMS), 100)
-            .setNotifyExecutor(mainThread)
-            .setFetchExecutor(backgroundThread)
+            .setNotifyExecutor(TestExecutor())
+            .setFetchExecutor(TestExecutor())
             .build()
         // if build succeeds without flushing an executor, success!
         assertEquals(ITEMS, pagedList)
@@ -76,7 +76,7 @@
             .build()
         var success = false
 
-        val job = testCoroutineScope.async(backgroundThread.asCoroutineDispatcher()) {
+        val job = testCoroutineScope.async(backgroundThread) {
             val pagedList = PagedList.create(
                 PagedSourceWrapper(ListDataSource(ITEMS)),
                 testCoroutineScope,
@@ -116,7 +116,7 @@
             .build()
         var success = false
         assertFails {
-            val job = testCoroutineScope.async(backgroundThread.asCoroutineDispatcher()) {
+            val job = testCoroutineScope.async(backgroundThread) {
                 PagedList.create(
                     pagedSource,
                     testCoroutineScope,
@@ -140,8 +140,8 @@
     @Test
     fun defaults() = runBlocking {
         val pagedList = PagedList.Builder(pagedSource, config)
-            .setNotifyExecutor(DirectExecutor)
-            .setFetchExecutor(DirectExecutor)
+            .setNotifyDispatcher(DirectDispatcher)
+            .setFetchDispatcher(DirectDispatcher)
             .buildAsync()
 
         assertEquals(pagedSource, pagedList.pagedSource)
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
index d175169..fce6cbd 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
@@ -22,10 +22,9 @@
 import androidx.paging.PagedList.LoadType.END
 import androidx.paging.PagedList.LoadType.START
 import androidx.paging.PagedSource.LoadResult
-import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.DirectDispatcher
 import androidx.testutils.TestExecutor
 import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.asCoroutineDispatcher
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Test
@@ -131,8 +130,8 @@
             GlobalScope,
             PagedList.Config(2, 2, true, 10, PagedList.Config.MAX_SIZE_UNBOUNDED),
             PagedSourceWrapper(ImmediateListDataSource(data)),
-            DirectExecutor.asCoroutineDispatcher(),
-            DirectExecutor.asCoroutineDispatcher(),
+            DirectDispatcher,
+            DirectDispatcher,
             consumer,
             initialResult
         )
@@ -254,7 +253,7 @@
         // Pager triggers an immediate empty response here, so we don't need to flush the executor
         assertEquals(
             listOf(
-                Result(END, PagedSource.LoadResult.empty<Int, String>())
+                Result(END, LoadResult.empty<Int, String>())
             ), consumer.takeResults()
         )
         assertEquals(
@@ -274,7 +273,7 @@
         // Pager triggers an immediate empty response here, so we don't need to flush the executor
         assertEquals(
             listOf(
-                Result(START, PagedSource.LoadResult.empty<Int, String>())
+                Result(START, LoadResult.empty<Int, String>())
             ), consumer.takeResults()
         )
         assertEquals(
diff --git a/paging/runtime/api/3.0.0-alpha01.txt b/paging/runtime/api/3.0.0-alpha01.txt
index f035ad8..6822061 100644
--- a/paging/runtime/api/3.0.0-alpha01.txt
+++ b/paging/runtime/api/3.0.0-alpha01.txt
@@ -43,8 +43,8 @@
     ctor public LivePagedListKt();
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
   }
 
   public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/api/current.txt b/paging/runtime/api/current.txt
index f035ad8..6822061 100644
--- a/paging/runtime/api/current.txt
+++ b/paging/runtime/api/current.txt
@@ -43,8 +43,8 @@
     ctor public LivePagedListKt();
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
   }
 
   public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/api/restricted_3.0.0-alpha01.txt b/paging/runtime/api/restricted_3.0.0-alpha01.txt
index f035ad8..6822061 100644
--- a/paging/runtime/api/restricted_3.0.0-alpha01.txt
+++ b/paging/runtime/api/restricted_3.0.0-alpha01.txt
@@ -43,8 +43,8 @@
     ctor public LivePagedListKt();
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
   }
 
   public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/api/restricted_current.txt b/paging/runtime/api/restricted_current.txt
index f035ad8..6822061 100644
--- a/paging/runtime/api/restricted_current.txt
+++ b/paging/runtime/api/restricted_current.txt
@@ -43,8 +43,8 @@
     ctor public LivePagedListKt();
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
     method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
-    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, kotlinx.coroutines.CoroutineScope coroutineScope = GlobalScope, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher = Dispatchers.IO);
   }
 
   public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/build.gradle b/paging/runtime/build.gradle
index 71033f7..e3719d7 100644
--- a/paging/runtime/build.gradle
+++ b/paging/runtime/build.gradle
@@ -18,7 +18,6 @@
 import androidx.build.AndroidXExtension
 import androidx.build.LibraryGroups
 import androidx.build.LibraryVersions
-import androidx.build.AndroidXExtension
 import androidx.build.Publish
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -42,16 +41,20 @@
     api(KOTLIN_COROUTINES)
 
     androidTestImplementation project(':internal-testutils-common')
-    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation project(':internal-testutils-ktx'), {
+        exclude group: 'com.google.truth'
+    }
     androidTestImplementation(ANDROIDX_TEST_CORE)
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
     androidTestImplementation(ARCH_CORE_TESTING)
-    androidTestImplementation(ESPRESSO_CORE)
+    androidTestImplementation(KOTLIN_COROUTINES_TEST)
     androidTestImplementation(JUNIT)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation MOCKITO_KOTLIN, {
         exclude group: 'org.mockito' // to keep control on the mockito version
+        exclude group: 'net.bytebuddy'
     }
 }
 
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index 67ada15..2b4de35b 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -27,7 +27,13 @@
 import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
 import androidx.paging.PagedList.LoadType.REFRESH
 import androidx.test.filters.SmallTest
+import androidx.testutils.TestDispatcher
 import androidx.testutils.TestExecutor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -43,6 +49,7 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class LivePagedListBuilderTest {
+    private val mainDispatcher = TestDispatcher()
     private val backgroundExecutor = TestExecutor()
     private val lifecycleOwner = object : LifecycleOwner {
         private val lifecycle = LifecycleRegistry(this)
@@ -56,8 +63,10 @@
         }
     }
 
+    @ExperimentalCoroutinesApi
     @Before
     fun setup() {
+        Dispatchers.setMain(mainDispatcher)
         ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
             override fun executeOnDiskIO(runnable: Runnable) {
                 fail("IO executor should be overwritten")
@@ -74,10 +83,12 @@
         lifecycleOwner.handleEvent(Lifecycle.Event.ON_START)
     }
 
+    @ExperimentalCoroutinesApi
     @After
     fun teardown() {
         lifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP)
         ArchTaskExecutor.getInstance().setDelegate(null)
+        Dispatchers.resetMain()
     }
 
     class MockDataSourceFactory {
@@ -126,8 +137,8 @@
 
     @Test
     fun executorBehavior() {
-        // specify a background executor via builder, and verify it gets used for all loads,
-        // overriding default arch IO executor
+        // specify a background dispatcher via builder, and verify it gets used for all loads,
+        // overriding default IO dispatcher
         val livePagedList = LivePagedListBuilder(MockDataSourceFactory()::create, 2)
             .setFetchExecutor(backgroundExecutor)
             .build()
@@ -143,7 +154,7 @@
         assertTrue(pagedListHolder[0] is InitialPagedList<*, *>)
 
         // flush loadInitial, done with passed executor
-        backgroundExecutor.executeAll()
+        drain()
 
         val pagedList = pagedListHolder[0]
         assertNotNull(pagedList)
@@ -151,7 +162,7 @@
 
         // flush loadRange
         pagedList!!.loadAround(2)
-        backgroundExecutor.executeAll()
+        drain()
 
         assertEquals(listOf("a", "b", "c", "d"), pagedList)
     }
@@ -191,8 +202,8 @@
         }
         initPagedList.addWeakLoadStateListener(loadStateChangedCallback)
 
-        // flush loadInitial, done with passed executor
-        backgroundExecutor.executeAll()
+        // flush loadInitial, done with passed dispatcher
+        drain()
 
         assertSame(initPagedList, pagedListHolder[0])
         // TODO: Investigate removing initial IDLE state from callback updates.
@@ -208,7 +219,8 @@
         assertSame(initPagedList, pagedListHolder[0])
 
         // flush loadInitial, should succeed now
-        backgroundExecutor.executeAll()
+        drain()
+
         assertNotSame(initPagedList, pagedListHolder[0])
         assertEquals(listOf("a", "b", null, null), pagedListHolder[0])
 
@@ -235,6 +247,14 @@
         )
     }
 
+    private fun drain() {
+        var executed: Boolean
+        do {
+            executed = backgroundExecutor.executeAll()
+            mainDispatcher.executeAll()
+        } while (executed || mainDispatcher.queue.isNotEmpty())
+    }
+
     companion object {
         val RETRYABLE_EXCEPTION = Exception("retryable")
     }
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
index 20c42d4..e9cdf9b 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
@@ -16,7 +16,8 @@
 
 package androidx.paging
 
-import androidx.testutils.TestExecutor
+import androidx.testutils.TestDispatcher
+import kotlinx.coroutines.GlobalScope
 
 class StringPagedList constructor(
     leadingNulls: Int,
@@ -24,10 +25,11 @@
     vararg items: String,
     list: List<String> = items.toList()
 ) : PagedList<String>(
+    GlobalScope,
     PagedSourceWrapper(ListDataSource(list)),
     PagedStorage(),
-    TestExecutor(),
-    TestExecutor(),
+    TestDispatcher(),
+    TestDispatcher(),
     null,
     Config.Builder().setPageSize(1).build()
 ), PagedStorage.Callback {
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
index ce6fcde6d..2a0a7e4 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
@@ -18,9 +18,11 @@
 
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.lifecycle.LiveData
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.util.concurrent.Executor
@@ -31,8 +33,8 @@
     private val config: PagedList.Config,
     private val boundaryCallback: PagedList.BoundaryCallback<Value>?,
     private val pagedSourceFactory: PagedSourceFactory<Key, Value>,
-    private val notifyExecutor: Executor,
-    private val fetchExecutor: Executor
+    private val notifyDispatcher: CoroutineDispatcher,
+    private val fetchDispatcher: CoroutineDispatcher
 ) : LiveData<PagedList<Value>>() {
     private var currentData: PagedList<Value>
     private var currentJob: Job? = null
@@ -77,14 +79,14 @@
         if (currentJob != null && !force) return
 
         currentJob?.cancel()
-        currentJob = coroutineScope.launch(fetchExecutor.asCoroutineDispatcher()) {
+        currentJob = coroutineScope.launch(fetchDispatcher) {
             try {
                 val pagedList = createPagedList()
-                withContext(notifyExecutor.asCoroutineDispatcher()) {
+                withContext(notifyDispatcher) {
                     onSuccess(pagedList)
                 }
             } catch (throwable: Throwable) {
-                withContext(notifyExecutor.asCoroutineDispatcher()) {
+                withContext(notifyDispatcher) {
                     onError(throwable)
                 }
             }
@@ -100,16 +102,19 @@
         val pagedSource = pagedSourceFactory()
         currentData.pagedSource.unregisterInvalidatedCallback(callback)
         pagedSource.registerInvalidatedCallback(callback)
-        currentData.setInitialLoadState(PagedList.LoadState.LOADING, null)
+
+        withContext(notifyDispatcher) {
+            currentData.setInitialLoadState(PagedList.LoadState.LOADING, null)
+        }
 
         @Suppress("UNCHECKED_CAST") // getLastKey guaranteed to be of 'Key' type
         val lastKey = currentData.lastKey as Key?
         return PagedList.create(
             pagedSource,
             coroutineScope,
-            notifyExecutor,
-            fetchExecutor,
-            fetchExecutor,
+            notifyDispatcher,
+            fetchDispatcher,
+            fetchDispatcher,
             boundaryCallback,
             config,
             lastKey
@@ -121,13 +126,13 @@
  * Constructs a `LiveData<PagedList>`, from this `DataSource.Factory`, convenience for
  * [LivePagedListBuilder].
  *
- * No work (such as loading) is done immediately, the creation of the first PagedList is is
- * deferred until the LiveData is observed.
+ * No work (such as loading) is done immediately, the creation of the first [PagedList] is deferred
+ * until the [LiveData] is observed.
  *
  * @param config Paging configuration.
- * @param initialLoadKey Initial load key passed to the first PagedList/DataSource.
- * @param boundaryCallback The boundary callback for listening to PagedList load state.
- * @param fetchExecutor Executor for fetching data from DataSources.
+ * @param initialLoadKey Initial load key passed to the first [PagedList] / [PagedSource].
+ * @param boundaryCallback The boundary callback for listening to [PagedList] load state.
+ * @param fetchExecutor [Executor] for fetching data from [PagedSource]s.
  *
  * @see LivePagedListBuilder
  */
@@ -150,12 +155,12 @@
  * Constructs a `LiveData<PagedList>`, from this `DataSource.Factory`, convenience for
  * [LivePagedListBuilder].
  *
- * No work (such as loading) is done immediately, the creation of the first PagedList is is
- * deferred until the LiveData is observed.
+ * No work (such as loading) is done immediately, the creation of the first [PagedList] is deferred
+ * until the [LiveData] is observed.
  *
  * @param pageSize Page size.
- * @param initialLoadKey Initial load key passed to the first PagedList/DataSource.
- * @param boundaryCallback The boundary callback for listening to PagedList load state.
+ * @param initialLoadKey Initial load key passed to the first [PagedList] / [PagedSource].
+ * @param boundaryCallback The boundary callback for listening to [PagedList] load state.
  * @param fetchExecutor Executor for fetching data from DataSources.
  *
  * @see LivePagedListBuilder
@@ -179,13 +184,18 @@
  * Constructs a `LiveData<PagedList>`, from this [PagedSourceFactory], convenience for
  * [LivePagedListBuilder].
  *
- * No work (such as loading) is done immediately, the creation of the first PagedList is is
- * deferred until the LiveData is observed.
+ * No work (such as loading) is done immediately, the creation of the first [PagedList] is deferred
+ * until the [LiveData] is observed.
  *
  * @param config Paging configuration.
- * @param initialLoadKey Initial load key passed to the first PagedList/PagedSource.
- * @param boundaryCallback The boundary callback for listening to PagedList load state.
- * @param fetchExecutor Executor for fetching data from PagedSources.
+ * @param initialLoadKey Initial load key passed to the first [PagedList] / [PagedSource].
+ * @param boundaryCallback The boundary callback for listening to [PagedList] load state.
+ * @param coroutineScope Set the [CoroutineScope] that page loads should be launched within. The
+ * set [coroutineScope] allows a [PagedSource] to cancel running load operations when the results
+ * are no longer needed - for example, when the containing activity is destroyed.
+ *
+ * Defaults to [GlobalScope].
+ * @param fetchDispatcher [CoroutineDispatcher] for fetching data from [PagedSource]s.
  *
  * @see LivePagedListBuilder
  */
@@ -193,26 +203,36 @@
     config: PagedList.Config,
     initialLoadKey: Key? = null,
     boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
-    fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
+    coroutineScope: CoroutineScope = GlobalScope,
+    fetchDispatcher: CoroutineDispatcher = Dispatchers.IO
 ): LiveData<PagedList<Value>> {
-    return LivePagedListBuilder(this, config)
-        .setInitialLoadKey(initialLoadKey)
-        .setBoundaryCallback(boundaryCallback)
-        .setFetchExecutor(fetchExecutor)
-        .build()
+    return LivePagedList(
+        coroutineScope,
+        initialLoadKey,
+        config,
+        boundaryCallback,
+        this,
+        Dispatchers.Main,
+        fetchDispatcher
+    )
 }
 
 /**
  * Constructs a `LiveData<PagedList>`, from this [PagedSourceFactory], convenience for
  * [LivePagedListBuilder].
  *
- * No work (such as loading) is done immediately, the creation of the first PagedList is is
- * deferred until the LiveData is observed.
+ * No work (such as loading) is done immediately, the creation of the first [PagedList] is deferred
+ * until the [LiveData] is observed.
  *
  * @param pageSize Page size.
- * @param initialLoadKey Initial load key passed to the first PagedList/PagedSource.
- * @param boundaryCallback The boundary callback for listening to PagedList load state.
- * @param fetchExecutor Executor for fetching data from PagedSources.
+ * @param initialLoadKey Initial load key passed to the first [PagedList] / [PagedSource].
+ * @param boundaryCallback The boundary callback for listening to [PagedList] load state.
+ * @param coroutineScope Set the [CoroutineScope] that page loads should be launched within. The
+ * set [coroutineScope] allows a [PagedSource] to cancel running load operations when the results
+ * are no longer needed - for example, when the containing activity is destroyed.
+ *
+ * Defaults to [GlobalScope].
+ * @param fetchDispatcher [CoroutineDispatcher] for fetching data from [PagedSource]s.
  *
  * @see LivePagedListBuilder
  */
@@ -220,11 +240,16 @@
     pageSize: Int,
     initialLoadKey: Key? = null,
     boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
-    fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
+    coroutineScope: CoroutineScope = GlobalScope,
+    fetchDispatcher: CoroutineDispatcher = Dispatchers.IO
 ): LiveData<PagedList<Value>> {
-    return LivePagedListBuilder(this, Config(pageSize))
-        .setInitialLoadKey(initialLoadKey)
-        .setBoundaryCallback(boundaryCallback)
-        .setFetchExecutor(fetchExecutor)
-        .build()
+    return LivePagedList(
+        coroutineScope,
+        initialLoadKey,
+        PagedList.Config.Builder().setPageSize(pageSize).build(),
+        boundaryCallback,
+        this,
+        Dispatchers.Main,
+        fetchDispatcher
+    )
 }
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
index ae1c147..a374d4a 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
@@ -16,15 +16,16 @@
 
 package androidx.paging
 
-import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.lifecycle.LiveData
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.asCoroutineDispatcher
 import java.util.concurrent.Executor
 
 /**
- * Builder for `LiveData<PagedList>`, given a [androidx.paging.DataSource.Factory] and a
- * [androidx.paging.PagedList.Config].
+ * Builder for `LiveData<PagedList>` for Java users, given a [androidx.paging.DataSource.Factory]
+ * and a [androidx.paging.PagedList.Config].
  *
  * The required parameters are in the constructor, so you can simply construct and build, or
  * optionally enable extra features (such as initial load key, or BoundaryCallback).
@@ -32,6 +33,8 @@
  * @param Key Type of input valued used to load data from the [DataSource]. Must be integer if
  * you're using [PositionalDataSource].
  * @param Value Item type being presented.
+ *
+ * @see toLiveData
  */
 class LivePagedListBuilder<Key : Any, Value : Any> {
     private val pagedSourceFactory: PagedSourceFactory<Key, Value>
@@ -39,7 +42,7 @@
     private var coroutineScope: CoroutineScope = GlobalScope
     private var initialLoadKey: Key? = null
     private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
-    private var fetchExecutor = ArchTaskExecutor.getIOThreadExecutor()
+    private var fetchDispatcher = Dispatchers.IO
 
     /**
      * Creates a [LivePagedListBuilder] with required parameters.
@@ -148,9 +151,9 @@
      * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created,
      * typically used to load additional data from network when paging from local storage.
      *
-     * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
-     * method is not called, or `null` is passed, you will not be notified when each
-     * DataSource runs out of data to provide to its PagedList.
+     * Pass a [PagedList.BoundaryCallback] to listen to when the PagedList runs out of data to load.
+     * If this method is not called, or `null` is passed, you will not be notified when each
+     * [PagedSource] runs out of data to provide to its [PagedList].
      *
      * If you are paging from a DataSource.Factory backed by local storage, you can set a
      * BoundaryCallback to know when there is no more information to page from local storage.
@@ -169,15 +172,17 @@
     }
 
     /**
-     * Sets executor used for background fetching of PagedLists, and the pages within.
+     * Sets [Executor] used for background fetching of [PagedList]s, and the pages within.
      *
-     * If not set, defaults to the Arch components I/O thread pool.
+     * The library will wrap this as a [kotlinx.coroutines.CoroutineDispatcher].
      *
-     * @param fetchExecutor Executor for fetching data from DataSources.
+     * If not set, defaults to [Dispatchers.IO].
+     *
+     * @param fetchExecutor [Executor] for fetching data from [PagedSource]s.
      * @return this
      */
     fun setFetchExecutor(fetchExecutor: Executor) = this.apply {
-        this.fetchExecutor = fetchExecutor
+        this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher()
     }
 
     /**
@@ -186,7 +191,7 @@
      * No work (such as loading) is done immediately, the creation of the first PagedList is is
      * deferred until the LiveData is observed.
      *
-     * @return The LiveData of PagedLists
+     * @return The [LiveData] of [PagedList]s
      */
     fun build(): LiveData<PagedList<Value>> {
         return LivePagedList(
@@ -195,8 +200,8 @@
             config,
             boundaryCallback,
             pagedSourceFactory,
-            ArchTaskExecutor.getMainThreadExecutor(),
-            fetchExecutor
+            Dispatchers.Main,
+            fetchDispatcher
         )
     }
 }
diff --git a/testutils-ktx/build.gradle b/testutils-ktx/build.gradle
index 357afcf..54e885b1 100644
--- a/testutils-ktx/build.gradle
+++ b/testutils-ktx/build.gradle
@@ -22,8 +22,9 @@
 }
 
 dependencies {
-    compile(KOTLIN_STDLIB)
-    compile(TRUTH)
+    api(TRUTH)
+    api(KOTLIN_STDLIB)
+    api(KOTLIN_COROUTINES)
 }
 
 androidx {
diff --git a/testutils-ktx/src/main/java/androidx/testutils/TestDispatcher.kt b/testutils-ktx/src/main/java/androidx/testutils/TestDispatcher.kt
new file mode 100644
index 0000000..127eaef
--- /dev/null
+++ b/testutils-ktx/src/main/java/androidx/testutils/TestDispatcher.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 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.testutils
+
+import kotlinx.coroutines.CoroutineDispatcher
+import java.util.concurrent.ConcurrentLinkedQueue
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * [CoroutineDispatcher] which keeps track of all its queues jobs.
+ */
+class TestDispatcher : CoroutineDispatcher() {
+    val queue = ConcurrentLinkedQueue<Runnable>()
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        queue.add(block)
+    }
+
+    fun executeAll() {
+        while (queue.peek() != null) {
+            queue.poll()!!.run()
+        }
+    }
+}
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
index 9de6aa7..9c933df 100644
--- a/viewpager2/build.gradle
+++ b/viewpager2/build.gradle
@@ -43,7 +43,9 @@
         exclude group: 'androidx.viewpager2', module: 'viewpager2'
     }
     androidTestImplementation(KOTLIN_STDLIB)
-    androidTestImplementation(project(":internal-testutils-ktx"))
+    androidTestImplementation(project(":internal-testutils-ktx"), {
+        exclude group: 'org.jetbrains.kotlinx'
+    })
 }
 
 android {
diff --git a/work/workmanager/api/2.3.0-alpha01.txt b/work/workmanager/api/2.3.0-alpha01.txt
index 0e3b058..d007563 100644
--- a/work/workmanager/api/2.3.0-alpha01.txt
+++ b/work/workmanager/api/2.3.0-alpha01.txt
@@ -80,7 +80,7 @@
     method public long[]? getLongArray(String);
     method public String? getString(String);
     method public String![]? getStringArray(String);
-    method public static byte[] toByteArray(androidx.work.Data);
+    method public byte[] toByteArray();
     field public static final androidx.work.Data! EMPTY;
     field public static final int MAX_DATA_BYTES = 10240; // 0x2800
   }
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index 0e3b058..d007563 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -80,7 +80,7 @@
     method public long[]? getLongArray(String);
     method public String? getString(String);
     method public String![]? getStringArray(String);
-    method public static byte[] toByteArray(androidx.work.Data);
+    method public byte[] toByteArray();
     field public static final androidx.work.Data! EMPTY;
     field public static final int MAX_DATA_BYTES = 10240; // 0x2800
   }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
index d44bdf5..780ee56 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
@@ -307,8 +307,8 @@
         contentValues.put("state", WorkTypeConverters.StateIds.ENQUEUED);
         contentValues.put("worker_class_name", TestWorker.class.getName());
         contentValues.put("input_merger_class_name", OverwritingInputMerger.class.getName());
-        contentValues.put("input", Data.toByteArray(Data.EMPTY));
-        contentValues.put("output", Data.toByteArray(Data.EMPTY));
+        contentValues.put("input", Data.EMPTY.toByteArray());
+        contentValues.put("output", Data.EMPTY.toByteArray());
         contentValues.put("initial_delay", 0L);
         contentValues.put("interval_duration", 0L);
         contentValues.put("flex_duration", 0L);
diff --git a/work/workmanager/src/main/java/androidx/work/Data.java b/work/workmanager/src/main/java/androidx/work/Data.java
index 8febaf8..4fafd31 100644
--- a/work/workmanager/src/main/java/androidx/work/Data.java
+++ b/work/workmanager/src/main/java/androidx/work/Data.java
@@ -330,6 +330,20 @@
     }
 
     /**
+     * Converts this Data to a byte array suitable for sending to other processes in your
+     * application.  There are no versioning guarantees with this byte array, so you should not
+     * use this for IPCs between applications or persistence.
+     *
+     * @return The byte array representation of the input
+     * @throws IllegalStateException if the serialized payload is bigger than
+     *                               {@link #MAX_DATA_BYTES}
+     */
+    @NonNull
+    public byte[] toByteArray() {
+        return Data.toByteArray(this);
+    }
+
+    /**
      * @return The number of elements in this Data object.
      * @hide
      */
@@ -345,8 +359,10 @@
      * @param data The {@link Data} object to convert
      * @return The byte array representation of the input
      * @throws IllegalStateException if the serialized payload is bigger than
-     *         {@link #MAX_DATA_BYTES}
+     *                               {@link #MAX_DATA_BYTES}
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @TypeConverter
     public static @NonNull byte[] toByteArray(@NonNull Data data) {
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
diff --git a/work/workmanager/src/test/java/androidx/work/DataTest.java b/work/workmanager/src/test/java/androidx/work/DataTest.java
index 9bb2466..80740e8 100644
--- a/work/workmanager/src/test/java/androidx/work/DataTest.java
+++ b/work/workmanager/src/test/java/androidx/work/DataTest.java
@@ -47,7 +47,7 @@
     public void testSerializeEmpty() {
         Data data = Data.EMPTY;
 
-        byte[] byteArray = Data.toByteArray(data);
+        byte[] byteArray = data.toByteArray();
         Data restoredData = Data.fromByteArray(byteArray);
 
         assertThat(restoredData, is(data));
@@ -62,7 +62,7 @@
                 .putString(KEY2, expectedValue2)
                 .build();
 
-        byte[] byteArray = Data.toByteArray(data);
+        byte[] byteArray = data.toByteArray();
         Data restoredData = Data.fromByteArray(byteArray);
 
         assertThat(restoredData, is(data));