Skip to content

Commit ba38b34

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Revert back to androidx.concurrent 1.1.0.
Upgrading to 1.2.0-beta01 failed, so as a temporary workaround this change forks the one new API needed from 1.2.0 (SuspendToFutureAdapter) to unblock the androidx.test:core beta release. Also remove obsolete re-definitions of androidx.test versions from axt_deps_versions.bzl PiperOrigin-RevId: 634471493
1 parent 3338b50 commit ba38b34

File tree

6 files changed

+163
-26
lines changed

6 files changed

+163
-26
lines changed

WORKSPACE

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,17 @@ load(
5555
"ANDROIDX_CURSOR_ADAPTER_VERSION",
5656
"ANDROIDX_DRAWER_LAYOUT_VERSION",
5757
"ANDROIDX_FRAGMENT_VERSION",
58-
"ANDROIDX_JUNIT_VERSION",
5958
"ANDROIDX_LEGACY_SUPPORT_VERSION",
6059
"ANDROIDX_LIFECYCLE_VERSION",
6160
"ANDROIDX_MULTIDEX_VERSION",
6261
"ANDROIDX_RECYCLERVIEW_VERSION",
6362
"ANDROIDX_TRACING_VERSION",
6463
"ANDROIDX_VIEWPAGER_VERSION",
6564
"ANDROIDX_WINDOW_VERSION",
66-
"CORE_VERSION",
6765
"GOOGLE_MATERIAL_VERSION",
6866
"GUAVA_LISTENABLEFUTURE_VERSION",
6967
"GUAVA_VERSION",
7068
"JUNIT_VERSION",
71-
"RUNNER_VERSION",
7269
"UIAUTOMATOR_VERSION",
7370
)
7471

build_extensions/axt_deps_versions.bzl

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,12 @@
1-
"""Defines current AXT versions and dependencies."""
2-
3-
# AXT versions listed as next # last published, stable
4-
RUNNER_VERSION = "1.6.0-alpha06" # 1.6.0-alpha05, 1.5.1
5-
RULES_VERSION = "1.6.0-alpha03" # 1.6.0-alpha02, 1.5.0
6-
MONITOR_VERSION = "1.7.0-alpha04" # 1.7.0-alpha03, 1.6.0
7-
ESPRESSO_VERSION = "3.6.0-alpha03" # 3.6.0-alpha02, 3.5.0
8-
CORE_VERSION = "1.6.0-alpha05" # 1.6.0-alpha04, 1.5.0
9-
ESPRESSO_DEVICE_VERSION = "1.0.0-alpha08" # 1.0.0-alpha07
10-
ANDROIDX_JUNIT_VERSION = "1.2.0-alpha03" # 1.2.0-alpha02, 1.1.4
11-
ANDROIDX_TRUTH_VERSION = "1.6.0-alpha03" # 1.6.0-alpha02, 1.5.0
12-
ANNOTATION_VERSION = "1.1.0-alpha03" # 1.1.0-alpha02, 1.0.1
13-
ORCHESTRATOR_VERSION = "1.5.0-alpha03" # 1.5.0-alpha02, 1.4.2
14-
15-
SERVICES_VERSION = "1.5.0-alpha03" # 1.5.0-alpha02, 1.4.2
16-
17-
# Full maven artifact strings for apks.
18-
SERVICES_APK_ARTIFACT = "androidx.test.services:test-services:%s" % SERVICES_VERSION
19-
ORCHESTRATOR_ARTIFACT = "androidx.test:orchestrator:%s" % ORCHESTRATOR_VERSION
1+
"""Defines versions of androidx.test dependencies."""
202

213
# Maven dependency versions
224
ANDROIDX_ANNOTATION_VERSION = "1.7.0-beta01"
235
ANDROIDX_ANNOTATION_EXPERIMENTAL_VERSION = "1.1.0"
246
ANDROIDX_COMPAT_VERSION = "1.3.1"
257

26-
# TODO(i336855276): update to beta/stable release when available
27-
ANDROIDX_CONCURRENT_VERSION = "1.2.0-alpha03"
8+
# TODO(i336855276): update to 1.2.0 release when available
9+
ANDROIDX_CONCURRENT_VERSION = "1.1.0"
2810
ANDROIDX_CORE_VERSION = "1.6.0"
2911
ANDROIDX_FRAGMENT_VERSION = "1.3.6"
3012
ANDROIDX_CURSOR_ADAPTER_VERSION = "1.0.0"

core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
**Bug Fixes**
88

99
* Remove unused androidx.test.annotation dependency
10+
* Revert back to androidx.concurrent 1.1.0
1011
* Fix `Rect` handling in `ViewCapture` for SDK >= 34 for non root views.
1112

1213
**New Features**
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2023 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.test.core.view
18+
19+
import androidx.concurrent.futures.ResolvableFuture
20+
import com.google.common.util.concurrent.ListenableFuture
21+
import java.util.concurrent.Executor
22+
import java.util.concurrent.TimeUnit
23+
import kotlin.coroutines.Continuation
24+
import kotlin.coroutines.CoroutineContext
25+
import kotlin.coroutines.EmptyCoroutineContext
26+
import kotlin.coroutines.createCoroutine
27+
import kotlin.coroutines.resume
28+
import kotlinx.coroutines.CancellationException
29+
import kotlinx.coroutines.CoroutineScope
30+
import kotlinx.coroutines.CoroutineStart
31+
import kotlinx.coroutines.Deferred
32+
import kotlinx.coroutines.Dispatchers
33+
import kotlinx.coroutines.async
34+
35+
/**
36+
* A utility for launching suspending calls scoped and managed by a returned [ListenableFuture],
37+
* used for adapting Kotlin suspending APIs to be callable from the Java programming language.
38+
*
39+
* TODO(b/336855276): Forked from androidx.concurrent. Remove in favor of just using androidx.concurrent
40+
* 1.2.0 when available and toolchain compatibility issues have been addressed
41+
*/
42+
internal object SuspendToFutureAdapter {
43+
44+
// the CoroutineScope() factory function is not used here as it adds a Job by default;
45+
// we don't want one as a failed task shouldn't fail a root Job.
46+
// To make SuspendToFutureAdapter behave as much like a "regular" ListenableFuture-returning
47+
// task as possible we don't want to hold additional references to child jobs from a global/root
48+
// scope, hence no SupervisorJob either.
49+
private val GlobalListenableFutureScope = object : CoroutineScope {
50+
override val coroutineContext: CoroutineContext = Dispatchers.Main
51+
}
52+
private val GlobalListenableFutureAwaitContext = Dispatchers.Unconfined
53+
54+
/**
55+
* Launch [block] in [context], returning a [ListenableFuture] to manage the launched operation.
56+
* [block] will run **synchronously** to its first suspend point, behaving as
57+
* [CoroutineStart.UNDISPATCHED] by default; set [launchUndispatched] to false to override
58+
* and behave as [CoroutineStart.DEFAULT].
59+
*
60+
* [launchFuture] can be used to write adapters for calling suspending functions from the
61+
* Java programming language, e.g.
62+
*
63+
* ```
64+
* @file:JvmName("FancyServices")
65+
*
66+
* fun FancyService.requestAsync(
67+
* args: FancyServiceArgs
68+
* ): ListenableFuture<FancyResult> = SuspendToFutureAdapter.launchFuture {
69+
* request(args)
70+
* }
71+
* ```
72+
*
73+
* which can be called from Java language source code as follows:
74+
* ```
75+
* final ListenableFuture<FancyResult> result = FancyServices.requestAsync(service, args);
76+
* ```
77+
*
78+
* If no [kotlinx.coroutines.CoroutineDispatcher] is provided in [context], [Dispatchers.Main]
79+
* is used as the default. [ListenableFuture.get] should not be called from the main thread
80+
* prior to the future's completion (whether it was obtained from [SuspendToFutureAdapter]
81+
* or not) as any operation performed in the process of completing the future may require
82+
* main thread event processing in order to proceed, leading to potential main thread deadlock.
83+
*
84+
* If the operation performed by [block] is known to be safe for potentially reentrant
85+
* continuation resumption, immediate dispatchers such as [Dispatchers.Unconfined] may be used
86+
* as part of [context] to avoid additional thread dispatch latency. This should not be used
87+
* as a means of supporting clients blocking the main thread using [ListenableFuture.get];
88+
* this support can be broken by valid internal implementation changes to any transitive
89+
* dependencies of the operation performed by [block].
90+
*/
91+
@Suppress("AsyncSuffixFuture")
92+
public fun <T> launchFuture(
93+
context: CoroutineContext = EmptyCoroutineContext,
94+
launchUndispatched: Boolean = true,
95+
block: suspend CoroutineScope.() -> T,
96+
): ListenableFuture<T> {
97+
val resultDeferred = GlobalListenableFutureScope.async(
98+
context = context,
99+
start = if (launchUndispatched) CoroutineStart.UNDISPATCHED else CoroutineStart.DEFAULT,
100+
block = block
101+
)
102+
return DeferredFuture(resultDeferred).also { future ->
103+
// Deferred.getCompleted is marked experimental, so external libraries can't rely on it.
104+
// Instead, use await in a raw coroutine that will invoke [resumeWith] when it returns
105+
// using the Unconfined dispatcher.
106+
resultDeferred::await.createCoroutine(future).resume(Unit)
107+
}
108+
}
109+
110+
private class DeferredFuture<T>(
111+
private val resultDeferred: Deferred<T>
112+
) : ListenableFuture<T>, Continuation<T> {
113+
114+
private val delegateFuture = ResolvableFuture.create<T>()
115+
116+
// Implements external cancellation, propagating the cancel request to resultDeferred.
117+
// delegateFuture will be cancelled if resultDeferred becomes cancelled for
118+
// internal cancellation.
119+
override fun cancel(shouldInterrupt: Boolean): Boolean =
120+
delegateFuture.cancel(shouldInterrupt).also { didCancel ->
121+
if (didCancel) {
122+
resultDeferred.cancel()
123+
}
124+
}
125+
126+
override fun isCancelled(): Boolean = delegateFuture.isCancelled
127+
128+
override fun isDone(): Boolean = delegateFuture.isDone
129+
130+
override fun get(): T = delegateFuture.get()
131+
132+
override fun get(timeout: Long, unit: TimeUnit): T = delegateFuture.get(timeout, unit)
133+
134+
override fun addListener(listener: Runnable, executor: Executor) =
135+
delegateFuture.addListener(listener, executor)
136+
137+
override val context: CoroutineContext
138+
get() = GlobalListenableFutureAwaitContext
139+
140+
/**
141+
* Implementation of [Continuation] that will resume for the raw call to await
142+
* to resolve the [delegateFuture]
143+
*/
144+
override fun resumeWith(result: Result<T>) {
145+
val unused = result.fold(
146+
onSuccess = {
147+
delegateFuture.set(it)
148+
},
149+
onFailure = {
150+
if (it is CancellationException) {
151+
delegateFuture.cancel(false)
152+
} else {
153+
delegateFuture.setException(it)
154+
}
155+
}
156+
)
157+
}
158+
}
159+
}

core/java/androidx/test/core/view/ViewCapture.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import android.view.ViewTreeObserver.OnDrawListener
3333
import android.view.WindowManager
3434
import androidx.annotation.RequiresApi
3535
import androidx.annotation.RestrictTo
36-
import androidx.concurrent.futures.SuspendToFutureAdapter
3736
import androidx.test.core.internal.os.HandlerExecutor
3837
import androidx.test.internal.platform.ServiceLoaderWrapper
3938
import androidx.test.internal.platform.os.ControlledLooper

core/java/androidx/test/core/view/WindowCapture.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import android.os.Looper
2525
import android.view.PixelCopy
2626
import android.view.Window
2727
import androidx.annotation.RequiresApi
28-
import androidx.concurrent.futures.SuspendToFutureAdapter
2928
import androidx.test.platform.graphics.HardwareRendererCompat
3029
import com.google.common.util.concurrent.ListenableFuture
3130
import kotlin.coroutines.resumeWithException

0 commit comments

Comments
 (0)