<lambda>null1 package platform.test.screenshot
2
3 import android.graphics.Bitmap
4 import android.graphics.Rect
5 import android.os.Build
6 import android.os.Handler
7 import android.os.Looper
8 import android.view.PixelCopy
9 import android.view.Window
10 import androidx.concurrent.futures.SuspendToFutureAdapter
11 import androidx.test.platform.graphics.HardwareRendererCompat
12 import com.google.common.util.concurrent.ListenableFuture
13 import kotlinx.coroutines.Dispatchers
14 import kotlinx.coroutines.Job
15
16 /**
17 * Suspend function that captures an image of the underlying window into a [Bitmap].
18 *
19 * For devices below [Build.VERSION_CODES#O] the image is obtained using [View#draw] on the windows
20 * decorView. Otherwise, [PixelCopy] is used.
21 *
22 * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required.
23 *
24 * This API is primarily intended for use in lower layer libraries or frameworks. For test authors,
25 * its recommended to use espresso or compose's captureToImage.
26 *
27 * This API must be called from the UI thread.
28 *
29 * This API is currently experimental and subject to change or removal.
30 */
31 suspend fun Window.captureRegionToBitmap(boundsInWindow: Rect? = null): Bitmap {
32 var bitmap: Bitmap? = null
33
34 val hardwareDrawingEnabled = HardwareRendererCompat.isDrawingEnabled()
35 HardwareRendererCompat.setDrawingEnabled(true)
36 try {
37 decorView.forceRedraw()
38 bitmap = generateBitmap(boundsInWindow)
39 } finally {
40 HardwareRendererCompat.setDrawingEnabled(hardwareDrawingEnabled)
41 }
42
43 return bitmap!!
44 }
45
46 /** A ListenableFuture variant of captureRegionToBitmap intended for use from Java. */
Windownull47 fun Window.captureRegionToBitmapAsync(boundsInWindow: Rect? = null): ListenableFuture<Bitmap> {
48 return SuspendToFutureAdapter.launchFuture(Dispatchers.Default + Job()) {
49 captureRegionToBitmap(boundsInWindow)
50 }
51 }
52
generateBitmapnull53 internal suspend fun Window.generateBitmap(boundsInWindow: Rect? = null): Bitmap {
54 val destBitmap =
55 Bitmap.createBitmap(
56 boundsInWindow?.width() ?: decorView.width,
57 boundsInWindow?.height() ?: decorView.height,
58 Bitmap.Config.ARGB_8888,
59 )
60 when {
61 Build.VERSION.SDK_INT < 26 ->
62 // TODO: handle boundsInWindow
63 decorView.generateBitmapFromDraw(destBitmap, boundsInWindow)
64 else -> generateBitmapFromPixelCopy(boundsInWindow, destBitmap)
65 }
66
67 return destBitmap
68 }
69
70 @SuppressWarnings("NewApi")
generateBitmapFromPixelCopynull71 internal suspend fun Window.generateBitmapFromPixelCopy(
72 boundsInWindow: Rect? = null,
73 destBitmap: Bitmap,
74 ) {
75 val job = Job()
76 var exception: Exception? = null
77 val onCopyFinished =
78 PixelCopy.OnPixelCopyFinishedListener { result ->
79 if (result != PixelCopy.SUCCESS) {
80 exception = RuntimeException(String.format("PixelCopy failed: %d", result))
81 }
82 job.complete()
83 }
84
85 PixelCopy.request(
86 this,
87 boundsInWindow,
88 destBitmap,
89 onCopyFinished,
90 Handler(Looper.getMainLooper()),
91 )
92
93 job.join()
94 exception?.let { throw it }
95 }
96