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 <V> ResolvableFuture<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