Add concurrent:concurrent-futures-ktx

Moves workmanager-ktx's ListenableFuture to kotlinx.coroutine compat
logic to a common module to be shared with a future change in paging to
support ListenableFuture

Test: ./gradlew concurrent:concurrent-futures-ktx:check
Change-Id: Ia919462fd3615b4707b2e5f7fe08cef560795bbe
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index fa2a9db..53274b2 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -56,7 +56,8 @@
     // branch. There is no reason to push a new release for docs only,
     // so we build docs against the fake prebuilt. This prebuilt is identical
     // to 1.0.0 release modulo changes in docs.
-    prebuilts(LibraryGroups.CONCURRENT, "1.0.0-fixeddocs01")
+    prebuilts(LibraryGroups.CONCURRENT, "concurrent-futures", "1.0.0-fixeddocs01")
+    ignore(LibraryGroups.CONCURRENT.group, "concurrent-futures-ktx")
     prebuilts(LibraryGroups.CONTENTPAGER, "1.0.0")
     prebuilts(LibraryGroups.COORDINATORLAYOUT, "1.1.0-rc01")
     prebuilts(LibraryGroups.CORE, "core", "1.2.0-beta02")
diff --git a/concurrent/futures-ktx/api/1.1.0-alpha01.txt b/concurrent/futures-ktx/api/1.1.0-alpha01.txt
new file mode 100644
index 0000000..b0c8c4b
--- /dev/null
+++ b/concurrent/futures-ktx/api/1.1.0-alpha01.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+  public final class ListenableFutureKt {
+    ctor public ListenableFutureKt();
+    method public static suspend <T> Object! await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
diff --git a/concurrent/futures-ktx/api/api_lint.ignore b/concurrent/futures-ktx/api/api_lint.ignore
new file mode 100644
index 0000000..0d817eb
--- /dev/null
+++ b/concurrent/futures-ktx/api/api_lint.ignore
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+DocumentExceptions: androidx.concurrent.futures.ListenableFutureKt#await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T>):
+    Method ListenableFutureKt.await appears to be throwing java.lang.Throwable; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
+
+
+MissingNullability: androidx.concurrent.futures.ListenableFutureKt#await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T>):
+    Missing nullability on method `await` return
diff --git a/concurrent/futures-ktx/api/current.txt b/concurrent/futures-ktx/api/current.txt
new file mode 100644
index 0000000..b0c8c4b
--- /dev/null
+++ b/concurrent/futures-ktx/api/current.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+  public final class ListenableFutureKt {
+    ctor public ListenableFutureKt();
+    method public static suspend <T> Object! await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
diff --git a/concurrent/futures-ktx/api/public_plus_experimental_1.1.0-alpha01.txt b/concurrent/futures-ktx/api/public_plus_experimental_1.1.0-alpha01.txt
new file mode 100644
index 0000000..b0c8c4b
--- /dev/null
+++ b/concurrent/futures-ktx/api/public_plus_experimental_1.1.0-alpha01.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+  public final class ListenableFutureKt {
+    ctor public ListenableFutureKt();
+    method public static suspend <T> Object! await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
diff --git a/concurrent/futures-ktx/api/public_plus_experimental_current.txt b/concurrent/futures-ktx/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..b0c8c4b
--- /dev/null
+++ b/concurrent/futures-ktx/api/public_plus_experimental_current.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+  public final class ListenableFutureKt {
+    ctor public ListenableFutureKt();
+    method public static suspend <T> Object! await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
diff --git a/concurrent/futures-ktx/api/restricted_1.1.0-alpha01.txt b/concurrent/futures-ktx/api/restricted_1.1.0-alpha01.txt
new file mode 100644
index 0000000..b0c8c4b
--- /dev/null
+++ b/concurrent/futures-ktx/api/restricted_1.1.0-alpha01.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+  public final class ListenableFutureKt {
+    ctor public ListenableFutureKt();
+    method public static suspend <T> Object! await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
diff --git a/concurrent/futures-ktx/api/restricted_current.txt b/concurrent/futures-ktx/api/restricted_current.txt
new file mode 100644
index 0000000..b0c8c4b
--- /dev/null
+++ b/concurrent/futures-ktx/api/restricted_current.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+  public final class ListenableFutureKt {
+    ctor public ListenableFutureKt();
+    method public static suspend <T> Object! await(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
diff --git a/concurrent/futures-ktx/build.gradle b/concurrent/futures-ktx/build.gradle
new file mode 100644
index 0000000..84f44c7
--- /dev/null
+++ b/concurrent/futures-ktx/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import androidx.build.AndroidXExtension
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+dependencies {
+    api(project(":concurrent:concurrent-futures"))
+    api(KOTLIN_STDLIB)
+    api(KOTLIN_COROUTINES_CORE)
+
+    testImplementation(JUNIT)
+    testImplementation(KOTLIN_TEST)
+    testImplementation(KOTLIN_COROUTINES_TEST)
+    testImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    testImplementation(ANDROIDX_TEST_CORE)
+}
+
+androidx {
+    name = "AndroidX Futures Kotlin Extensions"
+    publish = Publish.SNAPSHOT_AND_RELEASE
+    mavenVersion = LibraryVersions.FUTURES
+    mavenGroup = LibraryGroups.CONCURRENT
+    inceptionYear = "2019"
+    description = "Kotlin Extensions for Androidx implementation of Guava's ListenableFuture"
+    url = AndroidXExtension.ARCHITECTURE_URL
+}
diff --git a/concurrent/futures-ktx/src/main/java/androidx/concurrent/futures/ListenableFuture.kt b/concurrent/futures-ktx/src/main/java/androidx/concurrent/futures/ListenableFuture.kt
new file mode 100644
index 0000000..7328609
--- /dev/null
+++ b/concurrent/futures-ktx/src/main/java/androidx/concurrent/futures/ListenableFuture.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.concurrent.futures
+
+import androidx.concurrent.futures.AbstractResolvableFuture.getUninterruptibly
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.suspendCancellableCoroutine
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+import kotlin.coroutines.resumeWithException
+
+/**
+ * Awaits completion of `this` [ListenableFuture] without blocking a thread.
+ *
+ * This suspend function is cancellable.
+ *
+ * If the [kotlinx.coroutines.Job] of the current coroutine is cancelled or completed while this
+ * suspending function is
+ * waiting, this function stops waiting for the future and immediately resumes with
+ * [CancellationException][kotlinx.coroutines.CancellationException].
+ *
+ * This method is intended to be used with one-shot Futures, so on coroutine cancellation, the
+ * Future is cancelled as well. If cancelling the given future is undesired, use
+ * [kotlinx.coroutines.NonCancellable].
+ */
+suspend fun <T> ListenableFuture<T>.await(): T {
+    try {
+        if (isDone) return getUninterruptibly(this)
+    } catch (e: ExecutionException) {
+        // ExecutionException is the only kind of exception that can be thrown from a gotten
+        // Future, other than CancellationException. Cancellation is propagated upward so that
+        // the coroutine running this suspend function may process it.
+        // Any other Exception showing up here indicates a very fundamental bug in a
+        // Future implementation.
+        throw e.nonNullCause()
+    }
+
+    return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
+        addListener(
+            ToContinuation(this, cont),
+            DirectExecutor.INSTANCE
+        )
+        cont.invokeOnCancellation {
+            cancel(false)
+        }
+    }
+}
+
+/**
+ * Propagates the outcome of [futureToObserve] to [continuation] on completion.
+ *
+ * Cancellation is propagated as cancelling the continuation. If [futureToObserve] completes
+ * and fails, the cause of the Future will be propagated without a wrapping
+ * [ExecutionException] when thrown.
+ */
+private class ToContinuation<T>(
+    val futureToObserve: ListenableFuture<T>,
+    val continuation: CancellableContinuation<T>
+) : Runnable {
+    override fun run() {
+        if (futureToObserve.isCancelled) {
+            continuation.cancel()
+        } else {
+            try {
+                continuation.resumeWith(
+                    Result.success(getUninterruptibly(futureToObserve))
+                )
+            } catch (e: ExecutionException) {
+                // ExecutionException is the only kind of exception that can be thrown from a gotten
+                // Future. Anything else showing up here indicates a very fundamental bug in a
+                // Future implementation.
+                continuation.resumeWithException(e.nonNullCause())
+            }
+        }
+    }
+}
+
+/**
+ * Returns the cause from an [ExecutionException] thrown by a [Future.get] or similar.
+ *
+ * [ExecutionException] _always_ wraps a non-null cause when Future.get() throws. A Future cannot
+ * fail without a non-null `cause`, because the only way a Future _can_ fail is an uncaught
+ * [Exception].
+ *
+ * If this !! throws [NullPointerException], a Future is breaking its interface contract and losing
+ * state - a serious fundamental bug.
+ */
+private fun ExecutionException.nonNullCause(): Throwable {
+    return this.cause!!
+}
\ No newline at end of file
diff --git a/concurrent/futures-ktx/src/test/java/androidx/concurrent/futures/ListenableFutureTest.kt b/concurrent/futures-ktx/src/test/java/androidx/concurrent/futures/ListenableFutureTest.kt
new file mode 100644
index 0000000..21a9530
--- /dev/null
+++ b/concurrent/futures-ktx/src/test/java/androidx/concurrent/futures/ListenableFutureTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.concurrent.futures
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.yield
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertFailsWith
+
+@RunWith(JUnit4::class)
+class ListenableFutureTest {
+    private var actionIndex = AtomicInteger()
+    private var finished = AtomicBoolean()
+
+    @Test
+    fun testFutureWithResult() {
+        val future: ResolvableFuture<Int> = ResolvableFuture.create()
+        val job = GlobalScope.launch {
+            val result = future.await()
+            assertThat(result, `is`(10))
+        }
+        future.set(10)
+        runBlocking {
+            job.join()
+        }
+    }
+
+    @Test
+    fun testFutureWithException() {
+        val future: ResolvableFuture<Int> = ResolvableFuture.create()
+        val exception = RuntimeException("Something bad happened")
+        val job = GlobalScope.launch {
+            try {
+                future.await()
+            } catch (throwable: Throwable) {
+                assertThat(throwable, `is`(instanceOf(RuntimeException::class.java)))
+                assertThat(throwable.message, `is`(exception.message))
+            }
+        }
+        future.setException(exception)
+        runBlocking {
+            job.join()
+        }
+    }
+
+    @Test
+    fun testFutureCancellation() {
+        val future: ResolvableFuture<Int> = ResolvableFuture.create()
+        val job = GlobalScope.launch {
+            future.await()
+        }
+        future.cancel(true)
+        runBlocking {
+            job.join()
+            assertThat(job.isCancelled, `is`(true))
+        }
+    }
+
+    @ExperimentalCoroutinesApi
+    @Test
+    fun testAwaitWithCancellation() = runBlockingTest {
+        val future = ResolvableFuture.create<Int>()
+        val deferred = async {
+            future.await()
+        }
+
+        deferred.cancel(TestCancellationException())
+        assertFailsWith<TestCancellationException> {
+            deferred.await()
+            expectUnreached()
+        }
+    }
+
+    @ExperimentalCoroutinesApi
+    @Test
+    fun testCancellableAwait() = runBlocking {
+        expect(1)
+        val toAwait = ResolvableFuture.create<String>()
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            expect(2)
+            try {
+                toAwait.await() // suspends
+            } catch (e: CancellationException) {
+                expect(5) // should throw cancellation exception
+                throw e
+            }
+        }
+        expect(3)
+        job.cancel() // cancel the job
+        toAwait.set("fail") // too late, the waiting job was already cancelled
+        expect(4) // job processing of cancellation was scheduled, not executed yet
+        yield() // yield main thread to job
+        finish(6)
+    }
+
+    /**
+     * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
+     */
+    private fun expect(index: Int) {
+        val wasIndex = actionIndex.incrementAndGet()
+        check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
+    }
+
+    /**
+     * Asserts that this it the last action in the test. It must be invoked by any test that used
+     * [expect].
+     */
+    private fun finish(index: Int) {
+        expect(index)
+        check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" }
+    }
+
+    /**
+     * Asserts that this line is never executed.
+     */
+    private fun expectUnreached() {
+        error("Should not be reached")
+    }
+
+    private class TestCancellationException : CancellationException()
+}
diff --git a/concurrent/futures/api/restricted_1.0.0-rc01.txt b/concurrent/futures/api/restricted_1.0.0-rc01.txt
index 6dabf0b..3103236 100644
--- a/concurrent/futures/api/restricted_1.0.0-rc01.txt
+++ b/concurrent/futures/api/restricted_1.0.0-rc01.txt
@@ -34,6 +34,11 @@
     method public Object? attachCompleter(androidx.concurrent.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public enum DirectExecutor implements java.util.concurrent.Executor {
+    method public void execute(Runnable!);
+    enum_constant public static final androidx.concurrent.futures.DirectExecutor INSTANCE;
+  }
+
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.futures.AbstractResolvableFuture<V> {
     method public static <V> androidx.concurrent.futures.ResolvableFuture<V!>! create();
     method public boolean set(V?);
diff --git a/concurrent/futures/api/restricted_1.1.0-alpha01.txt b/concurrent/futures/api/restricted_1.1.0-alpha01.txt
index 6dabf0b..3103236 100644
--- a/concurrent/futures/api/restricted_1.1.0-alpha01.txt
+++ b/concurrent/futures/api/restricted_1.1.0-alpha01.txt
@@ -34,6 +34,11 @@
     method public Object? attachCompleter(androidx.concurrent.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public enum DirectExecutor implements java.util.concurrent.Executor {
+    method public void execute(Runnable!);
+    enum_constant public static final androidx.concurrent.futures.DirectExecutor INSTANCE;
+  }
+
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.futures.AbstractResolvableFuture<V> {
     method public static <V> androidx.concurrent.futures.ResolvableFuture<V!>! create();
     method public boolean set(V?);
diff --git a/concurrent/futures/api/restricted_current.txt b/concurrent/futures/api/restricted_current.txt
index 6dabf0b..3103236 100644
--- a/concurrent/futures/api/restricted_current.txt
+++ b/concurrent/futures/api/restricted_current.txt
@@ -34,6 +34,11 @@
     method public Object? attachCompleter(androidx.concurrent.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public enum DirectExecutor implements java.util.concurrent.Executor {
+    method public void execute(Runnable!);
+    enum_constant public static final androidx.concurrent.futures.DirectExecutor INSTANCE;
+  }
+
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.futures.AbstractResolvableFuture<V> {
     method public static <V> androidx.concurrent.futures.ResolvableFuture<V!>! create();
     method public boolean set(V?);
diff --git a/concurrent/futures/lint-baseline.xml b/concurrent/futures/lint-baseline.xml
index 05fdc84..7e9e712 100644
--- a/concurrent/futures/lint-baseline.xml
+++ b/concurrent/futures/lint-baseline.xml
@@ -92,6 +92,17 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void execute(Runnable command) {"
+        errorLine2="                        ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/concurrent/futures/DirectExecutor.java"
+            line="34"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
         errorLine1="    public static &lt;V> ResolvableFuture&lt;V> create() {"
         errorLine2="                      ~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/concurrent/futures/src/main/java/androidx/concurrent/futures/AbstractResolvableFuture.java b/concurrent/futures/src/main/java/androidx/concurrent/futures/AbstractResolvableFuture.java
index 9e6051b..3acecce 100644
--- a/concurrent/futures/src/main/java/androidx/concurrent/futures/AbstractResolvableFuture.java
+++ b/concurrent/futures/src/main/java/androidx/concurrent/futures/AbstractResolvableFuture.java
@@ -844,8 +844,11 @@
 
     /**
      * internal dependency on other /util/concurrent classes.
+     *
+     * @hide
      */
-    private static <V> V getUninterruptibly(Future<V> future) throws ExecutionException {
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    static <V> V getUninterruptibly(Future<V> future) throws ExecutionException {
         boolean interrupted = false;
         try {
             while (true) {
diff --git a/concurrent/futures/src/main/java/androidx/concurrent/futures/DirectExecutor.java b/concurrent/futures/src/main/java/androidx/concurrent/futures/DirectExecutor.java
index b12180f..9f1244b 100644
--- a/concurrent/futures/src/main/java/androidx/concurrent/futures/DirectExecutor.java
+++ b/concurrent/futures/src/main/java/androidx/concurrent/futures/DirectExecutor.java
@@ -16,13 +16,18 @@
 
 package androidx.concurrent.futures;
 
+import androidx.annotation.RestrictTo;
+
 import java.util.concurrent.Executor;
 
 /**
  * An {@link Executor} that runs each task in the thread that invokes {@link Executor#execute
  * execute}.
+ *
+ * @hide
  */
-enum DirectExecutor implements Executor {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public enum DirectExecutor implements Executor {
     INSTANCE;
 
     @Override
diff --git a/settings.gradle b/settings.gradle
index aebfd08..63f87ac 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -97,6 +97,7 @@
 includeProject(":collection:collection", "collection/collection")
 includeProject(":collection:collection-ktx", "collection/collection-ktx")
 includeProject(":concurrent:concurrent-futures", "concurrent/futures")
+includeProject(":concurrent:concurrent-futures-ktx", "concurrent/futures-ktx")
 includeProject(":contentaccess", "contentaccess")
 includeProject(":contentpager", "contentpager")
 includeProject(":coordinatorlayout", "coordinatorlayout")
diff --git a/work/workmanager-ktx/api/2.3.0-alpha02.ignore b/work/workmanager-ktx/api/2.3.0-alpha02.ignore
new file mode 100644
index 0000000..8aff62a
--- /dev/null
+++ b/work/workmanager-ktx/api/2.3.0-alpha02.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.work.ListenableFutureKt:
+    Removed class androidx.work.ListenableFutureKt
diff --git a/work/workmanager-ktx/api/2.3.0-alpha02.txt b/work/workmanager-ktx/api/2.3.0-alpha02.txt
index 5873b39..b875ebc 100644
--- a/work/workmanager-ktx/api/2.3.0-alpha02.txt
+++ b/work/workmanager-ktx/api/2.3.0-alpha02.txt
@@ -18,10 +18,6 @@
     method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/2.3.0-alpha04.ignore b/work/workmanager-ktx/api/2.3.0-alpha04.ignore
new file mode 100644
index 0000000..8aff62a
--- /dev/null
+++ b/work/workmanager-ktx/api/2.3.0-alpha04.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.work.ListenableFutureKt:
+    Removed class androidx.work.ListenableFutureKt
diff --git a/work/workmanager-ktx/api/2.3.0-alpha04.txt b/work/workmanager-ktx/api/2.3.0-alpha04.txt
index 5873b39..b875ebc 100644
--- a/work/workmanager-ktx/api/2.3.0-alpha04.txt
+++ b/work/workmanager-ktx/api/2.3.0-alpha04.txt
@@ -18,10 +18,6 @@
     method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/current.txt b/work/workmanager-ktx/api/current.txt
index 5873b39..b875ebc 100644
--- a/work/workmanager-ktx/api/current.txt
+++ b/work/workmanager-ktx/api/current.txt
@@ -18,10 +18,6 @@
     method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha02.txt b/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha02.txt
index 5873b39..b875ebc 100644
--- a/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha02.txt
+++ b/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha02.txt
@@ -18,10 +18,6 @@
     method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha04.txt b/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha04.txt
index 5873b39..b875ebc 100644
--- a/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha04.txt
+++ b/work/workmanager-ktx/api/public_plus_experimental_2.3.0-alpha04.txt
@@ -18,10 +18,6 @@
     method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/public_plus_experimental_current.txt b/work/workmanager-ktx/api/public_plus_experimental_current.txt
index 5873b39..b875ebc 100644
--- a/work/workmanager-ktx/api/public_plus_experimental_current.txt
+++ b/work/workmanager-ktx/api/public_plus_experimental_current.txt
@@ -18,10 +18,6 @@
     method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/restricted_2.3.0-alpha02.ignore b/work/workmanager-ktx/api/restricted_2.3.0-alpha02.ignore
new file mode 100644
index 0000000..8aff62a
--- /dev/null
+++ b/work/workmanager-ktx/api/restricted_2.3.0-alpha02.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.work.ListenableFutureKt:
+    Removed class androidx.work.ListenableFutureKt
diff --git a/work/workmanager-ktx/api/restricted_2.3.0-alpha02.txt b/work/workmanager-ktx/api/restricted_2.3.0-alpha02.txt
index 075851a..1cff0b6 100644
--- a/work/workmanager-ktx/api/restricted_2.3.0-alpha02.txt
+++ b/work/workmanager-ktx/api/restricted_2.3.0-alpha02.txt
@@ -23,11 +23,6 @@
     enum_constant public static final androidx.work.DirectExecutor INSTANCE;
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static suspend inline <R> Object! await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.Continuation<? super R> p);
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/restricted_2.3.0-alpha04.ignore b/work/workmanager-ktx/api/restricted_2.3.0-alpha04.ignore
new file mode 100644
index 0000000..8aff62a
--- /dev/null
+++ b/work/workmanager-ktx/api/restricted_2.3.0-alpha04.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.work.ListenableFutureKt:
+    Removed class androidx.work.ListenableFutureKt
diff --git a/work/workmanager-ktx/api/restricted_2.3.0-alpha04.txt b/work/workmanager-ktx/api/restricted_2.3.0-alpha04.txt
index 075851a..1cff0b6 100644
--- a/work/workmanager-ktx/api/restricted_2.3.0-alpha04.txt
+++ b/work/workmanager-ktx/api/restricted_2.3.0-alpha04.txt
@@ -23,11 +23,6 @@
     enum_constant public static final androidx.work.DirectExecutor INSTANCE;
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static suspend inline <R> Object! await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.Continuation<? super R> p);
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/api/restricted_current.txt b/work/workmanager-ktx/api/restricted_current.txt
index 075851a..1cff0b6 100644
--- a/work/workmanager-ktx/api/restricted_current.txt
+++ b/work/workmanager-ktx/api/restricted_current.txt
@@ -23,11 +23,6 @@
     enum_constant public static final androidx.work.DirectExecutor INSTANCE;
   }
 
-  public final class ListenableFutureKt {
-    ctor public ListenableFutureKt();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static suspend inline <R> Object! await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.Continuation<? super R> p);
-  }
-
   public final class OneTimeWorkRequestKt {
     ctor public OneTimeWorkRequestKt();
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder OneTimeWorkRequestBuilder();
diff --git a/work/workmanager-ktx/build.gradle b/work/workmanager-ktx/build.gradle
index 2a279bb..6b74ce6 100644
--- a/work/workmanager-ktx/build.gradle
+++ b/work/workmanager-ktx/build.gradle
@@ -45,8 +45,8 @@
     api project(':work:work-runtime')
     api(KOTLIN_STDLIB)
     api(KOTLIN_COROUTINES_ANDROID)
+    implementation project(':concurrent:concurrent-futures-ktx')
 
-    androidTestImplementation("androidx.concurrent:concurrent-futures:1.0.0")
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/ListenableFutureTest.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/ListenableFutureTest.kt
deleted file mode 100644
index 1f87734..0000000
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/ListenableFutureTest.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.work
-
-import androidx.concurrent.futures.ResolvableFuture
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.instanceOf
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class ListenableFutureTest {
-    @Test
-    fun testFutureWithResult() {
-        val future: ResolvableFuture<Int> = ResolvableFuture.create()
-        val job = GlobalScope.launch {
-            val result = future.await()
-            assertThat(result, `is`(10))
-        }
-        future.set(10)
-        runBlocking {
-            job.join()
-        }
-    }
-    @Test
-    fun testFutureWithException() {
-        val future: ResolvableFuture<Int> = ResolvableFuture.create()
-        val exception = RuntimeException("Something bad happened")
-        val job = GlobalScope.launch {
-            try {
-                future.await()
-            } catch (throwable: Throwable) {
-                assertThat(throwable, `is`(instanceOf(RuntimeException::class.java)))
-                assertThat(throwable.message, `is`(exception.message))
-            }
-        }
-        future.setException(exception)
-        runBlocking {
-            job.join()
-        }
-    }
-    @Test
-    fun testFutureCancellation() {
-        val future: ResolvableFuture<Int> = ResolvableFuture.create()
-        val job = GlobalScope.launch {
-            future.await()
-        }
-        future.cancel(true)
-        runBlocking {
-            job.join()
-            assertThat(job.isCancelled, `is`(true))
-        }
-    }
-}
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
index fa05791..e15192f 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
@@ -17,6 +17,7 @@
 package androidx.work
 
 import android.content.Context
+import androidx.concurrent.futures.await
 import androidx.work.impl.utils.futures.SettableFuture
 import com.google.common.util.concurrent.ListenableFuture
 import kotlinx.coroutines.CoroutineScope
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/ListenableFuture.kt b/work/workmanager-ktx/src/main/java/androidx/work/ListenableFuture.kt
deleted file mode 100644
index 51504ab..0000000
--- a/work/workmanager-ktx/src/main/java/androidx/work/ListenableFuture.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("NOTHING_TO_INLINE")
-
-package androidx.work
-
-import androidx.annotation.RestrictTo
-import com.google.common.util.concurrent.ListenableFuture
-import kotlinx.coroutines.suspendCancellableCoroutine
-import java.util.concurrent.CancellationException
-import java.util.concurrent.ExecutionException
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-
-/**
- * Awaits for the completion of the [ListenableFuture] without blocking a thread.
- *
- * @return R The result from the [ListenableFuture]
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-suspend inline fun <R> ListenableFuture<R>.await(): R {
-    // Fast path
-    if (isDone) {
-        try {
-            return get()
-        } catch (e: ExecutionException) {
-            throw e.cause ?: e
-        }
-    }
-    return suspendCancellableCoroutine { cancellableContinuation ->
-        addListener(Runnable {
-            try {
-                cancellableContinuation.resume(get())
-            } catch (throwable: Throwable) {
-                val cause = throwable.cause ?: throwable
-                when (throwable) {
-                    is CancellationException -> cancellableContinuation.cancel(cause)
-                    else -> cancellableContinuation.resumeWithException(cause)
-                }
-            }
-        }, DirectExecutor.INSTANCE)
-    }
-}
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/Operation.kt b/work/workmanager-ktx/src/main/java/androidx/work/Operation.kt
index c05181c..892cb51 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/Operation.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/Operation.kt
@@ -19,6 +19,8 @@
 
 package androidx.work
 
+import androidx.concurrent.futures.await
+
 /**
  * Awaits an [Operation] without blocking a thread.
  *
diff --git a/work/workmanager-testing/build.gradle b/work/workmanager-testing/build.gradle
index 281b68b..f7566fa 100644
--- a/work/workmanager-testing/build.gradle
+++ b/work/workmanager-testing/build.gradle
@@ -40,6 +40,7 @@
     implementation("androidx.lifecycle:lifecycle-livedata-core:2.1.0")
     implementation("androidx.room:room-runtime:2.2.1")
 
+    androidTestImplementation(project(':concurrent:concurrent-futures-ktx'))
     androidTestImplementation("androidx.arch.core:core-testing:2.1.0")
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestWorkerBuilderTest.kt b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestWorkerBuilderTest.kt
index b9c085f..51d9286 100644
--- a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestWorkerBuilderTest.kt
+++ b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestWorkerBuilderTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.net.Uri
+import androidx.concurrent.futures.await
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -26,7 +27,6 @@
 import androidx.work.OneTimeWorkRequestBuilder
 import androidx.work.WorkerFactory
 import androidx.work.WorkerParameters
-import androidx.work.await
 import androidx.work.testing.workers.TestListenableWorker
 import androidx.work.testing.workers.TestWorker
 import kotlinx.coroutines.runBlocking