diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt index 8e430d89a..c68a51eda 100644 --- a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt @@ -7,7 +7,6 @@ package com.facebook.flipper.plugins.jetpackcompose.descriptors -import android.graphics.Bitmap import android.view.ViewGroup import com.facebook.flipper.plugins.jetpackcompose.model.ComposeInnerViewNode import com.facebook.flipper.plugins.uidebugger.descriptors.Id @@ -49,13 +48,6 @@ object ComposeInnerViewDescriptor : NodeDescriptor { return ViewDescriptor.getChildren(node.view) } - override fun getSnapshot(node: ComposeInnerViewNode, bitmap: Bitmap?): Bitmap? { - if (node.view is ViewGroup) { - return ViewGroupDescriptor.getSnapshot(node.view, bitmap) - } - return ViewDescriptor.getSnapshot(node.view, bitmap) - } - override fun getActiveChild(node: ComposeInnerViewNode): Any? { if (node.view is ViewGroup) { return ViewGroupDescriptor.getActiveChild(node.view) diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt index 22b5e0046..8cc2bb328 100644 --- a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt @@ -7,7 +7,6 @@ package com.facebook.flipper.plugins.jetpackcompose.descriptors -import android.graphics.Bitmap import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags import com.facebook.flipper.plugins.uidebugger.descriptors.Id @@ -130,8 +129,6 @@ object ComposeNodeDescriptor : NodeDescriptor { override fun getQualifiedName(node: ComposeNode): String = node.inspectorNode.name - override fun getSnapshot(node: ComposeNode, bitmap: Bitmap?): Bitmap? = null - override fun getActiveChild(node: ComposeNode): Any? = null override fun getTags(node: ComposeNode): Set = setOf(BaseTags.Android, "Compose") 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 778fc2e7c..a623fd02b 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 @@ -16,7 +16,7 @@ import kotlinx.coroutines.launch class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) { interface ReusableBitmap { - val bitmap: Bitmap? + val bitmap: Bitmap fun readyForReuse() } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/DecorViewTracker.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/DecorViewTracker.kt index c4e98e59e..96f3cf0f6 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/DecorViewTracker.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/DecorViewTracker.kt @@ -11,7 +11,6 @@ import android.util.Log import android.view.View import android.view.ViewTreeObserver import com.facebook.flipper.plugins.uidebugger.LogTag -import com.facebook.flipper.plugins.uidebugger.common.BitmapPool import com.facebook.flipper.plugins.uidebugger.descriptors.ViewDescriptor import com.facebook.flipper.plugins.uidebugger.util.StopWatch import com.facebook.flipper.plugins.uidebugger.util.Throttler @@ -22,7 +21,7 @@ import com.facebook.flipper.plugins.uidebugger.util.objectIdentity * to it This predraw observer triggers a full traversal of the UI. There should only ever be one * active predraw listener at once */ -class DecorViewTracker(val context: UIDContext) { +class DecorViewTracker(private val context: UIDContext, private val snapshotter: Snapshotter) { private var currentDecorView: View? = null private var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null @@ -88,18 +87,7 @@ class DecorViewTracker(val context: UIDContext) { val (nodes, traversalTime) = StopWatch.time { context.layoutTraversal.traverse(context.applicationRef) } - mStopWatch.start() - var snapshotBitmap: BitmapPool.ReusableBitmap? = null - if (decorView.width > 0 && decorView.height > 0) { - snapshotBitmap = context.bitmapPool.getBitmap(decorView.width, decorView.height) - context.bitmapPool.getBitmap(decorView.width, decorView.height) - Log.i( - LogTag, - "Snapshotting view ${ViewDescriptor.getId(decorView)}", - ) - ViewDescriptor.getSnapshot(decorView, snapshotBitmap.bitmap) - } - val snapshotTime = mStopWatch.stop() + val (reusableBitmap, snapshotMs) = StopWatch.time { snapshotter.takeSnapshot(decorView) } context.updateQueue.enqueueUpdate( Update( @@ -107,8 +95,8 @@ class DecorViewTracker(val context: UIDContext) { nodes, startTimestamp, traversalTime, - snapshotTime, + snapshotMs, System.currentTimeMillis(), - snapshotBitmap)) + reusableBitmap)) } } 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 new file mode 100644 index 000000000..4ce9af9c6 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Snapshot.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.flipper.plugins.uidebugger.core + +import android.graphics.Canvas +import android.util.Log +import android.view.View +import com.facebook.flipper.plugins.uidebugger.LogTag +import com.facebook.flipper.plugins.uidebugger.common.BitmapPool + +interface Snapshotter { + fun takeSnapshot(view: View): BitmapPool.ReusableBitmap? +} + +/** + * Takes a snapshot by redrawing the view into a bitmap backed canvas, Since this is software + * rendering there can be discrepancies between the real image and the snapshot: + * 1. It can be unreliable when snapshotting views that are added directly to window manager + * 2. It doesnt include certain types of content (video / images) + */ +class CanvasSnapshotter(private val bitmapPool: BitmapPool) : Snapshotter { + override fun takeSnapshot(view: View): BitmapPool.ReusableBitmap? { + + if (view.width <= 0 || view.height <= 0) { + return null + } + + return try { + val reuseAbleBitmap = bitmapPool.getBitmap(view.width, view.height) + val canvas = Canvas(reuseAbleBitmap.bitmap) + view.draw(canvas) + reuseAbleBitmap + } catch (e: OutOfMemoryError) { + Log.e(LogTag, "OOM when taking snapshot") + 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 6c7680322..47dcac79a 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 @@ -31,12 +31,11 @@ class UIDContext( private val pendingFrameworkEvents: MutableList ) { - val decorViewTracker = DecorViewTracker(this) + val bitmapPool = BitmapPool() + val decorViewTracker = DecorViewTracker(this, CanvasSnapshotter(bitmapPool)) val updateQueue = UpdateQueue(this) val layoutTraversal: LayoutTraversal = LayoutTraversal(this) - val bitmapPool = BitmapPool() - fun addFrameworkEvent(frameworkEvent: FrameworkEvent) { synchronized(pendingFrameworkEvents) { pendingFrameworkEvents.add(frameworkEvent) } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt index ccc9413e6..ba0ef3cba 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt @@ -7,7 +7,6 @@ package com.facebook.flipper.plugins.uidebugger.descriptors -import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId @@ -133,15 +132,6 @@ abstract class ChainedDescriptor : NodeDescriptor { */ open fun onGetAttributes(node: T, attributeSections: MutableMap) {} - /** Get a snapshot of the node. */ - final override fun getSnapshot(node: T, bitmap: Bitmap?): Bitmap? { - return onGetSnapshot(node, bitmap) ?: mSuper?.onGetSnapshot(node, bitmap) - } - - open fun onGetSnapshot(node: T, bitmap: Bitmap?): Bitmap? { - return null - } - final override fun getInlineAttributes(node: T): Map { val builder = mutableMapOf() diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt index d7b569c9d..7ab6b9e50 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt @@ -7,7 +7,6 @@ package com.facebook.flipper.plugins.uidebugger.descriptors -import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId @@ -58,13 +57,6 @@ interface NodeDescriptor { /** The children this node exposes in the inspector. */ fun getChildren(node: T): List - /** - * Get a snapshot of the node. Bitmaps are not cheap to create, so accept one as an optional - * parameter. If a bitmap is provided, it will be used by the canvas to draw on it. Otherwise, a - * bitmap will be created. - */ - fun getSnapshot(node: T, bitmap: Bitmap?): Bitmap? = null - /** * If you have overlapping children this indicates which child is active / on top, we will only * listen to / traverse this child. If return null we assume all children are 'active' diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt index f60127995..96bb8169b 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt @@ -7,7 +7,6 @@ package com.facebook.flipper.plugins.uidebugger.descriptors -import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId @@ -34,6 +33,4 @@ object ObjectDescriptor : NodeDescriptor { override fun getBounds(node: Any): Bounds = Bounds(0, 0, 0, 0) override fun getTags(node: Any): Set = setOf(BaseTags.Unknown) - - override fun getSnapshot(node: Any, bitmap: Bitmap?): Bitmap? = null } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt index 2471c86c0..8a9d6a287 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt @@ -7,7 +7,6 @@ package com.facebook.flipper.plugins.uidebugger.descriptors -import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId @@ -42,7 +41,4 @@ object OffsetChildDescriptor : NodeDescriptor { node.descriptor.getAttributes(node.child) override fun getTags(node: OffsetChild): Set = node.descriptor.getTags(node.child) - - override fun getSnapshot(node: OffsetChild, bitmap: Bitmap?): Bitmap? = - node.descriptor.getSnapshot(node.child, bitmap) } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt index 0c6a63bf2..5003bb3f2 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt @@ -8,8 +8,6 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.annotation.SuppressLint -import android.graphics.Bitmap -import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable @@ -380,30 +378,6 @@ object ViewDescriptor : ChainedDescriptor() { attributes["id"] = value } - override fun onGetSnapshot(node: View, bitmap: Bitmap?): Bitmap? { - if (node.width <= 0 || node.height <= 0) { - return null - } - var workingBitmap = bitmap - - try { - val differentSize = - if (bitmap != null) (node.width != bitmap.width || node.height != bitmap.height) - else false - if (workingBitmap == null || differentSize) { - val viewWidth: Int = node.width - val viewHeight: Int = node.height - - workingBitmap = BitmapPool.createBitmapWithDefaultConfig(viewWidth, viewHeight) - } - - val canvas = Canvas(workingBitmap) - node.draw(canvas) - } catch (e: OutOfMemoryError) {} - - return workingBitmap - } - private fun fromDrawable(d: Drawable?): Inspectable? { return if (d is ColorDrawable) { InspectableValue.Color(Color.fromColor(d.color))