Skip to content

Commit 07f10cb

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Move UiAutomation#takeScreenshot call off main thread.
UiAutomation#takeScreenshot performs a Bitmap#copy, which can be flagged as a slow operation by strict mode. PiperOrigin-RevId: 548148641
1 parent f73d697 commit 07f10cb

File tree

1 file changed

+36
-51
lines changed

1 file changed

+36
-51
lines changed

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

Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ import androidx.test.platform.graphics.HardwareRendererCompat
3535
import androidx.test.platform.view.inspector.WindowInspectorCompat
3636
import com.google.common.util.concurrent.ListenableFuture
3737
import java.lang.RuntimeException
38-
import java.util.concurrent.ExecutionException
38+
import java.util.concurrent.CountDownLatch
3939
import java.util.concurrent.Executor
4040
import 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
5251
fun 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 {
104102
fun 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

Comments
 (0)