Skip to content

Commit

Permalink
Merge pull request #2224 from android/axt_2024_05_14_stable_release_b…
Browse files Browse the repository at this point in the history
…ranch_in_progress

Revert back to androidx.concurrent 1.1.0.
  • Loading branch information
brettchabot authored May 16, 2024
2 parents 36c10a7 + 9f8aa54 commit 596bfd2
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 26 deletions.
3 changes: 0 additions & 3 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,17 @@ load(
"ANDROIDX_CURSOR_ADAPTER_VERSION",
"ANDROIDX_DRAWER_LAYOUT_VERSION",
"ANDROIDX_FRAGMENT_VERSION",
"ANDROIDX_JUNIT_VERSION",
"ANDROIDX_LEGACY_SUPPORT_VERSION",
"ANDROIDX_LIFECYCLE_VERSION",
"ANDROIDX_MULTIDEX_VERSION",
"ANDROIDX_RECYCLERVIEW_VERSION",
"ANDROIDX_TRACING_VERSION",
"ANDROIDX_VIEWPAGER_VERSION",
"ANDROIDX_WINDOW_VERSION",
"CORE_VERSION",
"GOOGLE_MATERIAL_VERSION",
"GUAVA_LISTENABLEFUTURE_VERSION",
"GUAVA_VERSION",
"JUNIT_VERSION",
"RUNNER_VERSION",
"UIAUTOMATOR_VERSION",
)

Expand Down
24 changes: 3 additions & 21 deletions build_extensions/axt_deps_versions.bzl
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
"""Defines current AXT versions and dependencies."""

# AXT versions listed as next # last published, stable
RUNNER_VERSION = "1.6.0-alpha06" # 1.6.0-alpha05, 1.5.1
RULES_VERSION = "1.6.0-alpha03" # 1.6.0-alpha02, 1.5.0
MONITOR_VERSION = "1.7.0-alpha04" # 1.7.0-alpha03, 1.6.0
ESPRESSO_VERSION = "3.6.0-alpha03" # 3.6.0-alpha02, 3.5.0
CORE_VERSION = "1.6.0-alpha05" # 1.6.0-alpha04, 1.5.0
ESPRESSO_DEVICE_VERSION = "1.0.0-alpha08" # 1.0.0-alpha07
ANDROIDX_JUNIT_VERSION = "1.2.0-alpha03" # 1.2.0-alpha02, 1.1.4
ANDROIDX_TRUTH_VERSION = "1.6.0-alpha03" # 1.6.0-alpha02, 1.5.0
ANNOTATION_VERSION = "1.1.0-alpha03" # 1.1.0-alpha02, 1.0.1
ORCHESTRATOR_VERSION = "1.5.0-alpha03" # 1.5.0-alpha02, 1.4.2

SERVICES_VERSION = "1.5.0-alpha03" # 1.5.0-alpha02, 1.4.2

# Full maven artifact strings for apks.
SERVICES_APK_ARTIFACT = "androidx.test.services:test-services:%s" % SERVICES_VERSION
ORCHESTRATOR_ARTIFACT = "androidx.test:orchestrator:%s" % ORCHESTRATOR_VERSION
"""Defines versions of androidx.test dependencies."""

# Maven dependency versions
ANDROIDX_ANNOTATION_VERSION = "1.7.0-beta01"
ANDROIDX_ANNOTATION_EXPERIMENTAL_VERSION = "1.1.0"
ANDROIDX_COMPAT_VERSION = "1.3.1"

# TODO(i336855276): update to beta/stable release when available
ANDROIDX_CONCURRENT_VERSION = "1.2.0-alpha03"
# TODO(i336855276): update to 1.2.0 release when available
ANDROIDX_CONCURRENT_VERSION = "1.1.0"
ANDROIDX_CORE_VERSION = "1.6.0"
ANDROIDX_FRAGMENT_VERSION = "1.3.6"
ANDROIDX_CURSOR_ADAPTER_VERSION = "1.0.0"
Expand Down
1 change: 1 addition & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
**Bug Fixes**

* Remove unused androidx.test.annotation dependency
* Revert back to androidx.concurrent 1.1.0

**New Features**

Expand Down
159 changes: 159 additions & 0 deletions core/java/androidx/test/core/view/SuspendToFutureAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2023 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.test.core.view

import androidx.concurrent.futures.ResolvableFuture
import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.createCoroutine
import kotlin.coroutines.resume
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async

/**
* A utility for launching suspending calls scoped and managed by a returned [ListenableFuture],
* used for adapting Kotlin suspending APIs to be callable from the Java programming language.
*
* TODO(b/336855276): Forked from androidx.concurrent. Remove in favor of just using androidx.concurrent
* 1.2.0 when available and toolchain compatibility issues have been addressed
*/
internal object SuspendToFutureAdapter {

// the CoroutineScope() factory function is not used here as it adds a Job by default;
// we don't want one as a failed task shouldn't fail a root Job.
// To make SuspendToFutureAdapter behave as much like a "regular" ListenableFuture-returning
// task as possible we don't want to hold additional references to child jobs from a global/root
// scope, hence no SupervisorJob either.
private val GlobalListenableFutureScope = object : CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main
}
private val GlobalListenableFutureAwaitContext = Dispatchers.Unconfined

/**
* Launch [block] in [context], returning a [ListenableFuture] to manage the launched operation.
* [block] will run **synchronously** to its first suspend point, behaving as
* [CoroutineStart.UNDISPATCHED] by default; set [launchUndispatched] to false to override
* and behave as [CoroutineStart.DEFAULT].
*
* [launchFuture] can be used to write adapters for calling suspending functions from the
* Java programming language, e.g.
*
* ```
* @file:JvmName("FancyServices")
*
* fun FancyService.requestAsync(
* args: FancyServiceArgs
* ): ListenableFuture<FancyResult> = SuspendToFutureAdapter.launchFuture {
* request(args)
* }
* ```
*
* which can be called from Java language source code as follows:
* ```
* final ListenableFuture<FancyResult> result = FancyServices.requestAsync(service, args);
* ```
*
* If no [kotlinx.coroutines.CoroutineDispatcher] is provided in [context], [Dispatchers.Main]
* is used as the default. [ListenableFuture.get] should not be called from the main thread
* prior to the future's completion (whether it was obtained from [SuspendToFutureAdapter]
* or not) as any operation performed in the process of completing the future may require
* main thread event processing in order to proceed, leading to potential main thread deadlock.
*
* If the operation performed by [block] is known to be safe for potentially reentrant
* continuation resumption, immediate dispatchers such as [Dispatchers.Unconfined] may be used
* as part of [context] to avoid additional thread dispatch latency. This should not be used
* as a means of supporting clients blocking the main thread using [ListenableFuture.get];
* this support can be broken by valid internal implementation changes to any transitive
* dependencies of the operation performed by [block].
*/
@Suppress("AsyncSuffixFuture")
public fun <T> launchFuture(
context: CoroutineContext = EmptyCoroutineContext,
launchUndispatched: Boolean = true,
block: suspend CoroutineScope.() -> T,
): ListenableFuture<T> {
val resultDeferred = GlobalListenableFutureScope.async(
context = context,
start = if (launchUndispatched) CoroutineStart.UNDISPATCHED else CoroutineStart.DEFAULT,
block = block
)
return DeferredFuture(resultDeferred).also { future ->
// Deferred.getCompleted is marked experimental, so external libraries can't rely on it.
// Instead, use await in a raw coroutine that will invoke [resumeWith] when it returns
// using the Unconfined dispatcher.
resultDeferred::await.createCoroutine(future).resume(Unit)
}
}

private class DeferredFuture<T>(
private val resultDeferred: Deferred<T>
) : ListenableFuture<T>, Continuation<T> {

private val delegateFuture = ResolvableFuture.create<T>()

// Implements external cancellation, propagating the cancel request to resultDeferred.
// delegateFuture will be cancelled if resultDeferred becomes cancelled for
// internal cancellation.
override fun cancel(shouldInterrupt: Boolean): Boolean =
delegateFuture.cancel(shouldInterrupt).also { didCancel ->
if (didCancel) {
resultDeferred.cancel()
}
}

override fun isCancelled(): Boolean = delegateFuture.isCancelled

override fun isDone(): Boolean = delegateFuture.isDone

override fun get(): T = delegateFuture.get()

override fun get(timeout: Long, unit: TimeUnit): T = delegateFuture.get(timeout, unit)

override fun addListener(listener: Runnable, executor: Executor) =
delegateFuture.addListener(listener, executor)

override val context: CoroutineContext
get() = GlobalListenableFutureAwaitContext

/**
* Implementation of [Continuation] that will resume for the raw call to await
* to resolve the [delegateFuture]
*/
override fun resumeWith(result: Result<T>) {
val unused = result.fold(
onSuccess = {
delegateFuture.set(it)
},
onFailure = {
if (it is CancellationException) {
delegateFuture.cancel(false)
} else {
delegateFuture.setException(it)
}
}
)
}
}
}
1 change: 0 additions & 1 deletion core/java/androidx/test/core/view/ViewCapture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import android.view.ViewTreeObserver.OnDrawListener
import android.view.WindowManager
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.concurrent.futures.SuspendToFutureAdapter
import androidx.test.core.internal.os.HandlerExecutor
import androidx.test.internal.platform.ServiceLoaderWrapper
import androidx.test.internal.platform.os.ControlledLooper
Expand Down
1 change: 0 additions & 1 deletion core/java/androidx/test/core/view/WindowCapture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import android.os.Looper
import android.view.PixelCopy
import android.view.Window
import androidx.annotation.RequiresApi
import androidx.concurrent.futures.SuspendToFutureAdapter
import androidx.test.platform.graphics.HardwareRendererCompat
import com.google.common.util.concurrent.ListenableFuture
import kotlin.coroutines.resumeWithException
Expand Down

0 comments on commit 596bfd2

Please sign in to comment.