Skip to content

Commit 3a23fe9

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Make suspend function versions of ViewCapture/WindowCapture/DeviceCapture and rename existing functions as per androidx async API guidelines
PiperOrigin-RevId: 627863823
1 parent 64fc7bf commit 3a23fe9

File tree

9 files changed

+253
-218
lines changed

9 files changed

+253
-218
lines changed

WORKSPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ maven_install(
9191
"androidx.annotation:annotation-experimental:jar:" + ANDROIDX_ANNOTATION_EXPERIMENTAL_VERSION,
9292
"androidx.appcompat:appcompat:" + ANDROIDX_COMPAT_VERSION,
9393
"androidx.concurrent:concurrent-futures:" + ANDROIDX_CONCURRENT_VERSION,
94+
"androidx.concurrent:concurrent-futures-ktx:" + ANDROIDX_CONCURRENT_VERSION,
9495
"androidx.core:core:" + ANDROIDX_CORE_VERSION,
9596
"androidx.cursoradapter:cursoradapter:" + ANDROIDX_CURSOR_ADAPTER_VERSION,
9697
"androidx.drawerlayout:drawerlayout:" + ANDROIDX_DRAWER_LAYOUT_VERSION,
@@ -174,6 +175,8 @@ maven_install(
174175
"org.pantsbuild:jarjar:1.7.2",
175176
"org.jetbrains.kotlin:kotlin-stdlib:%s" % KOTLIN_VERSION,
176177
"org.jetbrains.kotlinx:kotlinx-coroutines-core:%s" % KOTLINX_COROUTINES_VERSION,
178+
"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:%s" % KOTLINX_COROUTINES_VERSION,
179+
"org.jetbrains.kotlinx:kotlinx-coroutines-android:%s" % KOTLINX_COROUTINES_VERSION,
177180
maven.artifact(
178181
artifact = "robolectric",
179182
exclusions = [

build_extensions/axt_deps_versions.bzl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ ORCHESTRATOR_ARTIFACT = "androidx.test:orchestrator:%s" % ORCHESTRATOR_VERSION
2222
ANDROIDX_ANNOTATION_VERSION = "1.7.0-beta01"
2323
ANDROIDX_ANNOTATION_EXPERIMENTAL_VERSION = "1.1.0"
2424
ANDROIDX_COMPAT_VERSION = "1.3.1"
25-
ANDROIDX_CONCURRENT_VERSION = "1.1.0"
25+
26+
# TODO(i336855276): update to beta/stable release when available
27+
ANDROIDX_CONCURRENT_VERSION = "1.2.0-alpha03"
2628
ANDROIDX_CORE_VERSION = "1.6.0"
2729
ANDROIDX_FRAGMENT_VERSION = "1.3.6"
2830
ANDROIDX_CURSOR_ADAPTER_VERSION = "1.0.0"

core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
**API Changes**
1616

1717
* Added ApplicationInfoBuilder.setFlags(int)
18+
* Make suspend function versions of ViewCapture/WindowCapture/DeviceCapture APIs
1819
* Make Bitmap.writeToTestStorage use the registered PlatformTestStorage instead of hardcoding TestStorage
1920
* Add *Async variants of capture*ToBitmap methods
2021

22+
2123
**Breaking API Changes**
2224

2325
**Known Issues**

core/java/androidx/test/core/BUILD

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ kt_android_library(
4949
"//annotation",
5050
"//opensource/androidx:annotation",
5151
"//runner/monitor",
52-
"@maven//:androidx_concurrent_concurrent_futures",
52+
"@maven//:androidx_concurrent_concurrent_futures_ktx",
5353
"@maven//:androidx_lifecycle_lifecycle_common",
5454
"@maven//:androidx_tracing_tracing",
55+
"@maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm",
5556
"@maven_listenablefuture//:com_google_guava_listenablefuture",
5657
],
5758
)

core/java/androidx/test/core/app/DeviceCapture.kt

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,27 @@
1717

1818
package androidx.test.core.app
1919

20+
import android.app.UiAutomation
2021
import android.graphics.Bitmap
2122
import android.os.Handler
2223
import android.os.Looper
2324
import android.util.Log
2425
import android.view.Choreographer
2526
import androidx.annotation.RestrictTo
26-
import androidx.concurrent.futures.ResolvableFuture
2727
import androidx.test.annotation.ExperimentalTestApi
2828
import androidx.test.core.internal.os.HandlerExecutor
2929
import androidx.test.core.view.forceRedraw
3030
import androidx.test.internal.util.Checks
3131
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
3232
import androidx.test.platform.graphics.HardwareRendererCompat
3333
import androidx.test.platform.view.inspector.WindowInspectorCompat
34-
import com.google.common.util.concurrent.ListenableFuture
3534
import java.lang.RuntimeException
36-
import java.util.concurrent.CountDownLatch
37-
import java.util.concurrent.Executor
38-
import java.util.concurrent.TimeUnit
35+
import kotlin.coroutines.resumeWithException
36+
import kotlin.time.Duration.Companion.seconds
37+
import kotlinx.coroutines.asCoroutineDispatcher
38+
import kotlinx.coroutines.runBlocking
39+
import kotlinx.coroutines.suspendCancellableCoroutine
40+
import kotlinx.coroutines.withTimeout
3941

4042
/**
4143
* Returns false if calling [takeScreenshot] will fail.
@@ -96,61 +98,63 @@ fun takeScreenshot(): Bitmap {
9698
fun takeScreenshotNoSync(): Bitmap {
9799
Checks.checkState(canTakeScreenshot())
98100

99-
val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
101+
var bitmap: Bitmap? = null
102+
var exception: Exception? = null
103+
val mainHandlerDispatcher =
104+
HandlerExecutor(Handler(Looper.getMainLooper())).asCoroutineDispatcher()
100105
val uiAutomation = getInstrumentation().uiAutomation
101106
if (uiAutomation == null) {
102107
throw RuntimeException("uiautomation is null")
103108
}
104109

105-
val origIsDrawingEnabled = HardwareRendererCompat.isDrawingEnabled()
106-
try {
107-
if (!origIsDrawingEnabled) {
108-
HardwareRendererCompat.setDrawingEnabled(true)
109-
}
110-
111-
try {
112-
forceRedrawGlobalWindowViews(mainExecutor).get(5, TimeUnit.SECONDS)
113-
} catch (e: Exception) {
114-
Log.w("takeScreenshot", "force redraw failed. Proceeding with screenshot", e)
115-
}
110+
val hardwareDrawingEnabled = HardwareRendererCompat.isDrawingEnabled()
111+
HardwareRendererCompat.setDrawingEnabled(true)
116112

117-
// wait on the next frame to increase probability the draw from previous step is
118-
// committed
119-
// TODO(b/289244795): use a transaction callback instead
120-
val latch = CountDownLatch(1)
121-
mainExecutor.execute { Choreographer.getInstance().postFrameCallback { latch.countDown() } }
122-
123-
if (!latch.await(1, TimeUnit.SECONDS)) {
124-
Log.w(
125-
"takeScreenshot",
126-
"frame callback did not occur in 1 seconds. Proceeding with screenshot",
127-
)
113+
return runBlocking(mainHandlerDispatcher) {
114+
withTimeout(5.seconds) {
115+
forceRedrawGlobalWindowViews()
116+
bitmap = takeScreenshotOnNextFrame(uiAutomation, hardwareDrawingEnabled)
117+
exception?.let { throw it }
118+
bitmap!!
128119
}
120+
}
121+
}
129122

130-
// do multiple retries of uiAutomation.takeScreenshot because it is known to return null
131-
// on API 31+ b/257274080
132-
for (i in 1..3) {
133-
val bitmap = uiAutomation.takeScreenshot()
134-
if (bitmap != null) {
135-
return bitmap
136-
}
137-
}
138-
throw RuntimeException("uiAutomation.takeScreenshot failed to return a bitmap")
139-
} finally {
140-
HardwareRendererCompat.setDrawingEnabled(origIsDrawingEnabled)
123+
private suspend fun forceRedrawGlobalWindowViews() {
124+
val views = WindowInspectorCompat.getGlobalWindowViews()
125+
Log.d("DeviceCapture", "Found ${views.size} global views to redraw")
126+
for (view in views) {
127+
view.forceRedraw()
141128
}
142129
}
143130

144-
private fun forceRedrawGlobalWindowViews(mainExecutor: Executor): ListenableFuture<List<Void>> {
145-
val future: ResolvableFuture<List<Void>> = ResolvableFuture.create()
146-
mainExecutor.execute {
147-
val views = WindowInspectorCompat.getGlobalWindowViews()
148-
val viewFutures: MutableList<ListenableFuture<Void>> = mutableListOf()
149-
for (view in views) {
150-
viewFutures.add(view.forceRedraw())
131+
private suspend fun takeScreenshotOnNextFrame(
132+
uiAutomation: UiAutomation,
133+
hardwareDrawingEnabled: Boolean,
134+
): Bitmap {
135+
// wait on the next frame to increase probability the draw from previous step is
136+
// committed
137+
// TODO(b/289244795): use a transaction callback instead
138+
139+
return suspendCancellableCoroutine<Bitmap> { cont ->
140+
Choreographer.getInstance().postFrameCallback {
141+
// do multiple retries of uiAutomation.takeScreenshot because it is known to return null
142+
// on API 31+ b/257274080
143+
var bitmap: Bitmap? = null
144+
for (i in 1..3) {
145+
bitmap = uiAutomation.takeScreenshot()
146+
if (bitmap != null) {
147+
Log.i("DeviceCapture", "got bitmap, returning")
148+
break
149+
}
150+
}
151+
HardwareRendererCompat.setDrawingEnabled(hardwareDrawingEnabled)
152+
if (bitmap == null) {
153+
Log.w("DeviceCapture", "failed to get bitmap, returning exception")
154+
cont.resumeWithException(RuntimeException("uiAutomation.takeScreenshot returned null"))
155+
} else {
156+
cont.resume(bitmap, {})
157+
}
151158
}
152-
Log.d("takeScreenshot", "Found ${views.size} global views to redraw")
153-
future.setFuture(ListFuture<Void>(viewFutures, true, mainExecutor))
154159
}
155-
return future
156160
}

0 commit comments

Comments
 (0)