@@ -35,10 +35,9 @@ import androidx.test.platform.graphics.HardwareRendererCompat
3535import androidx.test.platform.view.inspector.WindowInspectorCompat
3636import com.google.common.util.concurrent.ListenableFuture
3737import java.lang.RuntimeException
38- import java.util.concurrent.ExecutionException
38+ import java.util.concurrent.CountDownLatch
3939import java.util.concurrent.Executor
4040import java.util.concurrent.TimeUnit
41- import java.util.concurrent.TimeoutException
4241
4342/* *
4443 * Returns false if calling [takeScreenshot] will fail.
@@ -50,9 +49,9 @@ import java.util.concurrent.TimeoutException
5049 */
5150@ExperimentalTestApi
5251fun canTakeScreenshot (): Boolean =
53- Build .VERSION .SDK_INT >= Build .VERSION_CODES .JELLY_BEAN_MR2
54- && getInstrumentation().uiAutomation != null
55- && Looper .myLooper() != Looper .getMainLooper()
52+ Build .VERSION .SDK_INT >= Build .VERSION_CODES .JELLY_BEAN_MR2 &&
53+ getInstrumentation().uiAutomation != null &&
54+ Looper .myLooper() != Looper .getMainLooper()
5655
5756/* *
5857 * Captures an image of the device's screen into a [Bitmap].
@@ -73,7 +72,7 @@ fun canTakeScreenshot(): Boolean =
7372 *
7473 * @return a [Bitmap] that contains the image
7574 * @throws [IllegalStateException] if called on the main thread. This is a limitation of connecting
76- * to UiAutomation, [RuntimeException] if UiAutomation fails to take the screenshot
75+ * to UiAutomation, [RuntimeException] if UiAutomation fails to take the screenshot
7776 */
7877@ExperimentalTestApi
7978@RequiresApi(Build .VERSION_CODES .JELLY_BEAN_MR2 )
@@ -92,8 +91,7 @@ fun takeScreenshot(): Bitmap {
9291 *
9392 * @return a [Bitmap]
9493 * @throws [IllegalStateException] if called on the main thread. This is a limitation of connecting
95- * to UiAutomation, [RuntimeException] if UiAutomation fails to take the screenshot
96- *
94+ * to UiAutomation, [RuntimeException] if UiAutomation fails to take the screenshot
9795 * @hide
9896 */
9997@ExperimentalTestApi
@@ -104,61 +102,48 @@ fun takeScreenshot(): Bitmap {
104102fun takeScreenshotNoSync (): Bitmap {
105103 Checks .checkState(canTakeScreenshot())
106104
107- val bitmapFuture: ResolvableFuture <Bitmap > = ResolvableFuture .create()
108105 val mainExecutor = HandlerExecutor (Handler (Looper .getMainLooper()))
109106 val uiAutomation = getInstrumentation().uiAutomation
110107 if (uiAutomation == null ) {
111108 throw RuntimeException (" uiautomation is null" )
112109 }
113110
114- if (! HardwareRendererCompat .isDrawingEnabled()) {
115- HardwareRendererCompat .setDrawingEnabled(true )
116- bitmapFuture.addListener({ HardwareRendererCompat .setDrawingEnabled(false ) }, mainExecutor)
117- }
118-
111+ val origIsDrawingEnabled = HardwareRendererCompat .isDrawingEnabled()
119112 try {
120- forceRedrawGlobalWindowViews(mainExecutor).get(5 , TimeUnit .SECONDS )
121- } catch (e: Exception ) {
122- Log .w(" takeScreenshot" , " force redraw failed. Proceeding with screenshot" , e)
123- }
113+ if (! origIsDrawingEnabled) {
114+ HardwareRendererCompat .setDrawingEnabled(true )
115+ }
124116
125- // take the screenshot on the next frame to increase probability the draw from previous step is
126- // committed
127- mainExecutor.execute {
128- Choreographer .getInstance().postFrameCallback {
129- // do multiple retries of uiAutomation.takeScreenshot because it is known to return null
130- // on API 31+ b/257274080
131- var bitmap: Bitmap ? = null
132- var i = 0
133- while (i < 3 && bitmap == null ) {
134- bitmap = uiAutomation.takeScreenshot()
135- i++
136- }
137- if (bitmap == null ) {
138- bitmapFuture.setException(RuntimeException (" uiAutomation.takeScreenshot returned null" ))
139- } else {
140- bitmapFuture.set(bitmap)
141- }
117+ try {
118+ forceRedrawGlobalWindowViews(mainExecutor).get(5 , TimeUnit .SECONDS )
119+ } catch (e: Exception ) {
120+ Log .w(" takeScreenshot" , " force redraw failed. Proceeding with screenshot" , e)
142121 }
143- }
144122
145- // remap future exceptions as RuntimeExceptions
146- try {
147- return bitmapFuture.get( 5 , TimeUnit . SECONDS )
148- } catch (e : ExecutionException ) {
149- if (e.cause is RuntimeException ) {
150- throw e.cause as RuntimeException
151- } else {
152- throw RuntimeException (
153- " UiAutomation. takeScreenshot failed with unrecognized exception " ,
154- e.cause
123+ // wait on the next frame to increase probability the draw from previous step is
124+ // committed
125+ // TODO(b/289244795): use a transaction callback instead
126+ val latch = CountDownLatch ( 1 )
127+ mainExecutor.execute { Choreographer .getInstance().postFrameCallback { latch.countDown() } }
128+
129+ if ( ! latch.await( 1 , TimeUnit . SECONDS )) {
130+ Log .w (
131+ " takeScreenshot" ,
132+ " frame callback did not occur in 1 seconds. Proceeding with screenshot " ,
155133 )
156134 }
157- } catch (e: TimeoutException ) {
158- throw RuntimeException (" Uiautomation.takeScreenshot failed to complete in 5 seconds" , e)
159- } catch (e: InterruptedException ) {
160- Thread .currentThread().interrupt()
161- throw RuntimeException (" Uiautomation.takeScreenshot was interrupted" )
135+
136+ // do multiple retries of uiAutomation.takeScreenshot because it is known to return null
137+ // on API 31+ b/257274080
138+ for (i in 1 .. 3 ) {
139+ val bitmap = uiAutomation.takeScreenshot()
140+ if (bitmap != null ) {
141+ return bitmap
142+ }
143+ }
144+ throw RuntimeException (" uiAutomation.takeScreenshot failed to return a bitmap" )
145+ } finally {
146+ HardwareRendererCompat .setDrawingEnabled(origIsDrawingEnabled)
162147 }
163148}
164149
0 commit comments