|
17 | 17 |
|
18 | 18 | package androidx.test.core.app
|
19 | 19 |
|
| 20 | +import android.app.UiAutomation |
20 | 21 | import android.graphics.Bitmap
|
21 | 22 | import android.os.Handler
|
22 | 23 | import android.os.Looper
|
23 | 24 | import android.util.Log
|
24 | 25 | import android.view.Choreographer
|
25 | 26 | import androidx.annotation.RestrictTo
|
26 |
| -import androidx.concurrent.futures.ResolvableFuture |
27 | 27 | import androidx.test.annotation.ExperimentalTestApi
|
28 | 28 | import androidx.test.core.internal.os.HandlerExecutor
|
29 | 29 | import androidx.test.core.view.forceRedraw
|
30 | 30 | import androidx.test.internal.util.Checks
|
31 | 31 | import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
32 | 32 | import androidx.test.platform.graphics.HardwareRendererCompat
|
33 | 33 | import androidx.test.platform.view.inspector.WindowInspectorCompat
|
34 |
| -import com.google.common.util.concurrent.ListenableFuture |
35 | 34 | 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 |
39 | 41 |
|
40 | 42 | /**
|
41 | 43 | * Returns false if calling [takeScreenshot] will fail.
|
@@ -96,61 +98,63 @@ fun takeScreenshot(): Bitmap {
|
96 | 98 | fun takeScreenshotNoSync(): Bitmap {
|
97 | 99 | Checks.checkState(canTakeScreenshot())
|
98 | 100 |
|
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() |
100 | 105 | val uiAutomation = getInstrumentation().uiAutomation
|
101 | 106 | if (uiAutomation == null) {
|
102 | 107 | throw RuntimeException("uiautomation is null")
|
103 | 108 | }
|
104 | 109 |
|
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) |
116 | 112 |
|
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!! |
128 | 119 | }
|
| 120 | + } |
| 121 | +} |
129 | 122 |
|
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() |
141 | 128 | }
|
142 | 129 | }
|
143 | 130 |
|
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 | + } |
151 | 158 | }
|
152 |
| - Log.d("takeScreenshot", "Found ${views.size} global views to redraw") |
153 |
| - future.setFuture(ListFuture<Void>(viewFutures, true, mainExecutor)) |
154 | 159 | }
|
155 |
| - return future |
156 | 160 | }
|
0 commit comments