11package com.harnessui
22
3+ import android.graphics.Bitmap
4+ import android.graphics.Canvas
35import android.os.Handler
46import android.os.Looper
57import android.os.SystemClock
@@ -8,17 +10,21 @@ import android.view.MotionEvent
810import com.facebook.react.bridge.Arguments
911import com.facebook.react.bridge.Promise
1012import com.facebook.react.bridge.ReactApplicationContext
13+ import com.facebook.react.bridge.ReadableMap
1114import com.facebook.react.bridge.UiThreadUtil
1215import com.facebook.react.bridge.WritableArray
1316import com.facebook.react.bridge.WritableMap
17+ import com.facebook.react.bridge.WritableNativeArray
18+ import java.io.ByteArrayOutputStream
19+ import java.nio.ByteBuffer
1420import java.util.concurrent.CountDownLatch
1521import java.util.concurrent.TimeUnit
1622
1723/* *
18- * Debug implementation of UIHelper with full functionality .
24+ * UI helper implementation for HarnessUI .
1925 * Includes touch simulation and view querying capabilities.
2026 */
21- class UIHelperImpl (private val context : ReactApplicationContext ) : UIHelper {
27+ class UIHelperImpl (private val context : ReactApplicationContext ) {
2228
2329 companion object {
2430 private const val TAG = " HarnessUI"
@@ -32,7 +38,7 @@ class UIHelperImpl(private val context: ReactApplicationContext) : UIHelper {
3238 // Touch Simulation
3339 // =========================================================================
3440
35- override fun simulateTap (x : Double , y : Double , promise : Promise ) {
41+ fun simulateTap (x : Double , y : Double , promise : Promise ) {
3642 Log .i(TAG , " simulateTap called with x:$x y:$y " )
3743
3844 UiThreadUtil .runOnUiThread {
@@ -81,22 +87,22 @@ class UIHelperImpl(private val context: ReactApplicationContext) : UIHelper {
8187 // Query API
8288 // =========================================================================
8389
84- override fun queryByTestId (testId : String ): WritableMap ? {
90+ fun queryByTestId (testId : String ): WritableMap ? {
8591 Log .i(TAG , " queryByTestId called with: $testId " )
8692 return executeQuery(ViewQueryType .TEST_ID , testId)
8793 }
8894
89- override fun queryByAccessibilityLabel (label : String ): WritableMap ? {
95+ fun queryByAccessibilityLabel (label : String ): WritableMap ? {
9096 Log .i(TAG , " queryByAccessibilityLabel called with: $label " )
9197 return executeQuery(ViewQueryType .ACCESSIBILITY_LABEL , label)
9298 }
9399
94- override fun queryAllByTestId (testId : String ): WritableArray {
100+ fun queryAllByTestId (testId : String ): WritableArray {
95101 Log .i(TAG , " queryAllByTestId called with: $testId " )
96102 return executeQueryAll(ViewQueryType .TEST_ID , testId)
97103 }
98104
99- override fun queryAllByAccessibilityLabel (label : String ): WritableArray {
105+ fun queryAllByAccessibilityLabel (label : String ): WritableArray {
100106 Log .i(TAG , " queryAllByAccessibilityLabel called with: $label " )
101107 return executeQueryAll(ViewQueryType .ACCESSIBILITY_LABEL , label)
102108 }
@@ -182,4 +188,72 @@ class UIHelperImpl(private val context: ReactApplicationContext) : UIHelper {
182188 Log .i(TAG , " QueryAll result count: ${result.size()} " )
183189 return result
184190 }
191+
192+ // =========================================================================
193+ // Screenshot Capture
194+ // =========================================================================
195+
196+ fun captureScreenshot (bounds : ReadableMap ? , promise : Promise ) {
197+ Log .i(TAG , " captureScreenshot called" )
198+
199+ UiThreadUtil .runOnUiThread {
200+ val activity = context.currentActivity ? : run {
201+ Log .w(TAG , " No current activity" )
202+ promise.resolve(null )
203+ return @runOnUiThread
204+ }
205+
206+ val root = activity.window.decorView.rootView
207+ val density = root.resources.displayMetrics.density
208+
209+ try {
210+ // Determine capture dimensions
211+ val captureX: Int
212+ val captureY: Int
213+ val captureWidth: Int
214+ val captureHeight: Int
215+
216+ if (bounds != null ) {
217+ // Convert dp to px for bounds
218+ captureX = (bounds.getDouble(" x" ) * density).toInt()
219+ captureY = (bounds.getDouble(" y" ) * density).toInt()
220+ captureWidth = (bounds.getDouble(" width" ) * density).toInt()
221+ captureHeight = (bounds.getDouble(" height" ) * density).toInt()
222+ } else {
223+ captureX = 0
224+ captureY = 0
225+ captureWidth = root.width
226+ captureHeight = root.height
227+ }
228+
229+ // Create bitmap and canvas
230+ val bitmap = Bitmap .createBitmap(captureWidth, captureHeight, Bitmap .Config .ARGB_8888 )
231+ val canvas = Canvas (bitmap)
232+
233+ // Translate canvas if capturing a specific region
234+ canvas.translate(- captureX.toFloat(), - captureY.toFloat())
235+
236+ // Draw the view hierarchy
237+ root.draw(canvas)
238+
239+ // Convert to PNG bytes
240+ val outputStream = ByteArrayOutputStream ()
241+ bitmap.compress(Bitmap .CompressFormat .PNG , 100 , outputStream)
242+ val pngBytes = outputStream.toByteArray()
243+
244+ // Clean up
245+ bitmap.recycle()
246+
247+ // Return as WritableArray (converted to ArrayBuffer by TurboModule bridge)
248+ val byteArray = WritableNativeArray ()
249+ for (byte in pngBytes) {
250+ byteArray.pushInt(byte.toInt() and 0xFF )
251+ }
252+ promise.resolve(byteArray)
253+ } catch (e: Exception ) {
254+ Log .e(TAG , " Screenshot capture failed" , e)
255+ promise.resolve(null )
256+ }
257+ }
258+ }
185259}
0 commit comments