diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt index a623fd02b..5a779c1a9 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt @@ -10,7 +10,6 @@ package com.facebook.flipper.plugins.uidebugger.common import android.graphics.Bitmap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch /** BitmapPool is intended to be used on the main thread. In other words, it is not thread-safe. */ class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) { @@ -57,7 +56,7 @@ class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) { override fun readyForReuse() { val key = generateKey(bitmap.width, bitmap.height) - mainScope.launch { + synchronized(this@BitmapPool) { if (isRecycled) { bitmap.recycle() } else { diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Snapshot.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Snapshot.kt index 4ce9af9c6..1467d73c9 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Snapshot.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Snapshot.kt @@ -7,9 +7,15 @@ package com.facebook.flipper.plugins.uidebugger.core +import android.app.Activity import android.graphics.Canvas +import android.os.Build +import android.os.Handler +import android.os.Looper import android.util.Log +import android.view.PixelCopy import android.view.View +import androidx.annotation.RequiresApi import com.facebook.flipper.plugins.uidebugger.LogTag import com.facebook.flipper.plugins.uidebugger.common.BitmapPool @@ -41,3 +47,50 @@ class CanvasSnapshotter(private val bitmapPool: BitmapPool) : Snapshotter { } } } + +@RequiresApi(Build.VERSION_CODES.O) +class PixelCopySnapshotter( + private val bitmapPool: BitmapPool, + private val applicationRef: ApplicationRef, + private val fallback: Snapshotter +) : Snapshotter { + + override fun takeSnapshot(view: View): BitmapPool.ReusableBitmap? { + + if (view.width <= 0 || view.height <= 0) { + return null + } + + val bitmap = bitmapPool.getBitmap(view.width, view.height) + try { + + val decorViewToActivity: Map = + applicationRef.activitiesStack.toList().associateBy { it.window.decorView } + + val activity = decorViewToActivity[view] + + // if this view belongs to an activity prefer this as it doesn't require private api hacks + if (activity != null) { + PixelCopy.request( + activity.window, + bitmap.bitmap, + { + // no-op this this api is actually synchronous despite how it looks + }, + Handler(Looper.getMainLooper())) + return bitmap + } + } catch (e: OutOfMemoryError) { + Log.e(LogTag, "OOM when taking snapshot") + null + } catch (e: Exception) { + // there was some problem with the pixel copy, fall back to canvas impl + Log.e(LogTag, "Exception when taking snapshot", e) + Log.i(LogTag, "Using fallback snapshot", e) + bitmap.readyForReuse() + fallback.takeSnapshot(view) + } + + return null + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/UIDContext.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/UIDContext.kt index 47dcac79a..6741b4ef7 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/UIDContext.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/UIDContext.kt @@ -8,6 +8,7 @@ package com.facebook.flipper.plugins.uidebugger.core import android.app.Application +import android.os.Build import com.facebook.flipper.core.FlipperConnection import com.facebook.flipper.plugins.uidebugger.common.BitmapPool import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister @@ -32,7 +33,16 @@ class UIDContext( ) { val bitmapPool = BitmapPool() - val decorViewTracker = DecorViewTracker(this, CanvasSnapshotter(bitmapPool)) + private val canvasSnapshotter = CanvasSnapshotter(bitmapPool) + + val snapshotter = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PixelCopySnapshotter(bitmapPool, applicationRef, canvasSnapshotter) + } else { + canvasSnapshotter + } + + val decorViewTracker = DecorViewTracker(this, snapshotter) val updateQueue = UpdateQueue(this) val layoutTraversal: LayoutTraversal = LayoutTraversal(this) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt index eea5a20a4..5278561bb 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt @@ -44,7 +44,7 @@ object ApplicationRefDescriptor : ChainedDescriptor() { val activeRoots = node.rootsResolver.rootViews() val decorViewToActivity: Map = - node.activitiesStack.toList().map { it.window.decorView to it }.toMap() + node.activitiesStack.toList().associateBy { it.window.decorView } for (root in activeRoots) { // if there is an activity for this root view use that,