Descriptors

Summary:
Introducing descriptors. Taken from the existing inspector, refreshed with a few things from Stetho, and translated to Kotlin.

Note: doesn't use FlipperObject or FlipperArray.

This is a very rough draft for them all, but can be used as basis for experimentation.

Reviewed By: LukeDefeo

Differential Revision: D38860763

fbshipit-source-id: 9f6bf4ad0e61fc40b0a773dfb8bfa80b1f397b3a
This commit is contained in:
Lorenzo Blasa
2022-08-19 08:47:58 -07:00
committed by Facebook GitHub Bot
parent b5bdd56d2c
commit 6adf1d666f
13 changed files with 794 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
/*
* 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.descriptors
/**
* This class derives from Descriptor and provides a canonical implementation of ChainedDescriptor}.
*/
abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor<T> {
private var mSuper: Descriptor<T>? = null
override fun setSuper(superDescriptor: Descriptor<T>) {
if (superDescriptor !== mSuper) {
check(mSuper == null)
mSuper = superDescriptor
}
}
fun getSuper(): Descriptor<T>? {
return mSuper
}
/** Initialize a descriptor. */
override fun init() {
mSuper?.init()
onInit()
}
open fun onInit() {}
/**
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a
* globally unique ID it is fine to rely on [System.identityHashCode].
*/
override fun getId(node: T): String? {
return onGetId(node)
}
abstract fun onGetId(node: T): String
/**
* The name used to identify this node in the inspector. Does not need to be unique. A good
* default is to use the class name of the node.
*/
override fun getName(node: T): String? {
return onGetName(node)
}
abstract fun onGetName(node: T): String
/** The children this node exposes in the inspector. */
override fun getChildren(node: T, children: MutableList<Any>) {
mSuper?.getChildren(node, children)
onGetChildren(node, children)
}
open fun onGetChildren(node: T, children: MutableList<Any>) {}
/**
* Get the data to show for this node in the sidebar of the inspector. The object will be showen
* in order and with a header matching the given name.
*/
override fun getData(node: T, builder: MutableMap<String, Any?>) {
mSuper?.getData(node, builder)
onGetData(node, builder)
}
open fun onGetData(node: T, builder: MutableMap<String, Any?>) {}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.descriptors
import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
class ActivityDescriptor : AbstractChainedDescriptor<Activity>() {
override fun onInit() {}
override fun onGetId(activity: Activity): String {
return Integer.toString(System.identityHashCode(activity))
}
override fun onGetName(activity: Activity): String {
return activity.javaClass.simpleName
}
override fun onGetChildren(activity: Activity, children: MutableList<Any>) {
activity.window?.let { window -> children.add(activity.window) }
var fragments = getFragments(FragmentCompat.supportInstance, activity)
for (fragment in fragments) {
children.add(fragment)
}
fragments = getFragments(FragmentCompat.frameworkInstance, activity)
for (fragment in fragments) {
children.add(fragment)
}
}
override fun onGetData(activity: Activity, builder: MutableMap<String, Any?>) {}
private fun getFragments(compat: FragmentCompat<*, *, *, *>?, activity: Activity): List<Any> {
if (compat == null) {
return emptyList()
}
return compat?.getFragments(activity)
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.descriptors
import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
class ApplicationDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
val rootResolver = RootViewResolver()
override fun onInit() {}
override fun onGetId(applicationRef: ApplicationRef): String {
return applicationRef.application.packageName
}
override fun onGetName(applicationRef: ApplicationRef): String {
val applicationInfo = applicationRef.application.getApplicationInfo()
val stringId = applicationInfo.labelRes
return if (stringId == 0) applicationInfo.nonLocalizedLabel.toString()
else applicationRef.application.getString(stringId)
}
override fun onGetChildren(applicationRef: ApplicationRef, children: MutableList<Any>) {
val activeRoots = rootResolver.listActiveRootViews()
activeRoots?.let { roots ->
for (root: RootViewResolver.RootView in roots) {
var added = false
for (activity: Activity in applicationRef.activitiesStack) {
if (activity.window.decorView == root.view) {
children.add(activity)
added = true
break
}
}
if (!added) {
children.add(root.view)
}
}
}
}
override fun onGetData(applicationRef: ApplicationRef, builder: MutableMap<String, Any?>) {}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.descriptors
import android.widget.Button
class ButtonDescriptor : AbstractChainedDescriptor<Button>() {
override fun init() {}
override fun onGetId(button: Button): String {
return Integer.toString(System.identityHashCode(button))
}
override fun onGetName(button: Button): String {
return button.javaClass.simpleName
}
override fun onGetData(button: Button, builder: MutableMap<String, Any?>) {}
override fun onGetChildren(button: Button, children: MutableList<Any>) {}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.descriptors
/**
* This interface marks a Descriptor in a way that is specially understood by the register. When
* registered for a particular class 'T', a Descriptor that implements this interface will be
* chained (via ChainedDescriptor.setSuper) to the Descriptor that is registered for the super class
* of 'T'. If the super class of 'T' doesn't have a registration, then the super-super class will be
* used (and so on). This allows you to implement Descriptor for any class in an inheritance
* hierarchy without having to couple it (via direct inheritance) to the super-class' Descriptor.
*
* To understand why this is useful, let's say you wanted to write a Descriptor for ListView. You
* have three options:
*
* The first option is to derive directly from Descriptor and write code to describe everything
* about instances of ListView, including details that are exposed by super classes such as
* ViewGroup, View, and even Object. This isn't generally a very good choice because it would
* require a lot of duplicated code amongst many descriptor implementations.
*
* The second option is to derive your ListViewDescriptor from ViewGroupDescriptor and only
* implement code to describe how ListView differs from ViewGroup. This will result in a class
* hierarchy that is parallel to the one that you are describing, but is also not a good choice for
* two reasons (let's assume for the moment that ViewGroupDescriptor is deriving from
* ViewDescriptor). The first problem is that you will need to write code for aggregating results
* from the super-class in methods such as Descriptor.getChildren and Descriptor.getAttributes. The
* second problem is that you'd end up with a log of fragility if you ever want to implement a
* descriptor for classes that are in-between ViewGroup and ListView, e.g. AbsListView. Any
* descriptor that derived from ViewGroupDescriptor and described a class deriving from AbsListView
* would have to be modified to now derive from AbsListViewDescriptor.
*
* The third option is to implement ChainedDescriptor (e.g. by deriving from
* AbstractChainedDescriptor) which solves all of these issues for you.
*/
interface ChainedDescriptor<T> {
fun setSuper(superDescriptor: Descriptor<T>)
}

View File

@@ -0,0 +1,56 @@
/*
* 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.descriptors
abstract class Descriptor<T> : NodeDescriptor<T> {
var descritorRegister: DescriptorRegister? = null
fun setDescriptorRegister(descriptorMapping: DescriptorRegister) {
this.descritorRegister = descriptorMapping
}
protected fun descriptorForClass(clazz: Class<*>): Descriptor<*>? {
descritorRegister?.let { register ->
return register.descriptorForClass(clazz)
}
return null
}
protected fun descriptorForObject(obj: Any): Descriptor<*>? {
descritorRegister?.let { register ->
return register.descriptorForClass(obj::class.java)
}
return null
}
// override fun inspect(obj: Any): FlipperObject {
// val descriptor = descriptorForObject(obj)
// descriptor?.let { descriptor ->
// return (descriptor as Descriptor<Any>).get(obj)
// }
//
// return FlipperObject.Builder().build()
// }
//
// override fun get(node: T): FlipperObject {
// val builder = FlipperObject.Builder()
//
// val propsBuilder = FlipperObject.Builder()
// getData(node, propsBuilder)
//
// val childrenBuilder = FlipperArray.Builder()
// getChildren(node, childrenBuilder)
//
// builder
// .put("key", getId(node))
// .put("title", getName(node))
// .put("data", propsBuilder)
// .put("children", childrenBuilder)
//
// return builder.build()
// }
}

View File

@@ -0,0 +1,67 @@
/*
* 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.descriptors
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.TextView
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
class DescriptorRegister {
private val register: MutableMap<Class<*>, Descriptor<*>> = HashMap()
companion object {
fun withDefaults(): DescriptorRegister {
val mapping = DescriptorRegister()
mapping.register(Any::class.java, ObjectDescriptor())
mapping.register(ApplicationRef::class.java, ApplicationDescriptor())
mapping.register(Activity::class.java, ActivityDescriptor())
mapping.register(Window::class.java, WindowDescriptor())
mapping.register(ViewGroup::class.java, ViewGroupDescriptor())
mapping.register(View::class.java, ViewDescriptor())
mapping.register(TextView::class.java, TextViewDescriptor())
mapping.register(Button::class.java, ButtonDescriptor())
for (clazz in mapping.register.keys) {
val descriptor: Descriptor<*>? = mapping.register[clazz]
descriptor?.let { descriptor ->
if (descriptor is ChainedDescriptor<*>) {
val chainedDescriptor = descriptor as ChainedDescriptor<Any>
val superClass: Class<*> = clazz.getSuperclass()
val superDescriptor: Descriptor<*>? = mapping.descriptorForClass(superClass)
superDescriptor?.let { superDescriptor ->
chainedDescriptor.setSuper(superDescriptor as Descriptor<Any>)
}
}
}
}
for (descriptor in mapping.register.values) {
descriptor.setDescriptorRegister(mapping)
}
return mapping
}
}
fun <T> register(clazz: Class<T>, descriptor: Descriptor<T>) {
register[clazz] = descriptor
}
fun descriptorForClass(clazz: Class<*>): Descriptor<*>? {
var clazz = clazz
while (!register.containsKey(clazz)) {
clazz = clazz.superclass
}
return register[clazz]
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.descriptors
interface NodeDescriptor<T> {
/** Initialize a descriptor. */
fun init()
/**
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a
* globally unique ID it is fine to rely on [System.identityHashCode].
*/
fun getId(node: T): String?
/**
* The name used to identify this node in the inspector. Does not need to be unique. A good
* default is to use the class name of the node.
*/
fun getName(node: T): String?
/** The children this node exposes in the inspector. */
fun getChildren(node: T, children: MutableList<Any>)
/**
* Get the data to show for this node in the sidebar of the inspector. The object will be showen
* in order and with a header matching the given name.
*/
fun getData(node: T, builder: MutableMap<String, Any?>)
}

View File

@@ -0,0 +1,24 @@
/*
* 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.descriptors
class ObjectDescriptor : Descriptor<Any>() {
override fun init() {}
override fun getId(obj: Any): String {
return Integer.toString(System.identityHashCode(obj))
}
override fun getName(obj: Any): String {
return obj.javaClass.simpleName
}
override fun getChildren(node: Any, children: MutableList<Any>) {}
override fun getData(obj: Any, builder: MutableMap<String, Any?>) {}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.descriptors
import android.widget.TextView
class TextViewDescriptor : AbstractChainedDescriptor<TextView>() {
override fun init() {}
override fun onGetId(textView: TextView): String {
return Integer.toString(System.identityHashCode(textView))
}
override fun onGetName(textView: TextView): String {
return textView.javaClass.simpleName
}
override fun onGetChildren(textView: TextView, children: MutableList<Any>) {}
override fun onGetData(textView: TextView, builder: MutableMap<String, Any?>) {}
}

View File

@@ -0,0 +1,237 @@
/*
* 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.descriptors
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.util.SparseArray
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.FrameLayout
import android.widget.LinearLayout
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
import com.facebook.flipper.plugins.uidebugger.common.InspectableValue
import com.facebook.flipper.plugins.uidebugger.stetho.ResourcesUtil
import java.lang.reflect.Field
class ViewDescriptor : AbstractChainedDescriptor<View>() {
override fun init() {}
override fun onGetId(view: View): String {
return Integer.toBinaryString(System.identityHashCode(view))
}
override fun onGetName(view: View): String {
return view.javaClass.simpleName
}
override fun onGetChildren(view: View, children: MutableList<Any>) {}
override fun onGetData(view: View, builder: MutableMap<String, Any?>) {
val positionOnScreen = IntArray(2)
view.getLocationOnScreen(positionOnScreen)
val props = mutableMapOf<String, Any?>()
props.put("height", InspectableValue.mutable(view.height))
props.put("width", InspectableValue.mutable(view.width))
props.put("alpha", InspectableValue.mutable(view.alpha))
props.put("visibility", VisibilityMapping.toPicker(view.visibility))
props.put("background", fromDrawable(view.background))
// props.put("tag", InspectableValue.mutable(view.tag))
// props.put("keyedTags", getTags(view))
props.put("layoutParams", getLayoutParams(view))
props.put(
"state",
mapOf<String, Any?>(
"enabled" to InspectableValue.mutable(view.isEnabled),
"activated" to InspectableValue.mutable(view.isActivated),
"focused" to view.isFocused,
"selected" to InspectableValue.mutable(view.isSelected)))
props.put(
"bounds",
mapOf<String, Any?>(
"left" to InspectableValue.mutable(view.left),
"right" to InspectableValue.mutable(view.right),
"top" to InspectableValue.mutable(view.top),
"bottom" to InspectableValue.mutable(view.bottom)))
props.put(
"padding",
mapOf<String, Any?>(
"left" to InspectableValue.mutable(view.paddingLeft),
"top" to InspectableValue.mutable(view.paddingTop),
"right" to InspectableValue.mutable(view.paddingRight),
"bottom" to InspectableValue.mutable(view.paddingBottom)))
props.put(
"rotation",
mapOf<String, Any?>(
"x" to InspectableValue.mutable(view.rotationX),
"y" to InspectableValue.mutable(view.rotationY),
"z" to InspectableValue.mutable(view.rotation)))
props.put(
"scale",
mapOf<String, Any?>(
"x" to InspectableValue.mutable(view.scaleX),
"y" to InspectableValue.mutable(view.scaleY)))
props.put(
"pivot",
mapOf<String, Any?>(
"x" to InspectableValue.mutable(view.pivotX),
"y" to InspectableValue.mutable(view.pivotY)))
props.put("positionOnScreenX", positionOnScreen[0])
props.put("positionOnScreenY", positionOnScreen[1])
builder.put("View", props)
}
fun fromDrawable(d: Drawable?): InspectableValue<*> {
return if (d is ColorDrawable) {
InspectableValue.mutable(InspectableValue.Type.Color, d.color)
} else InspectableValue.mutable(InspectableValue.Type.Color, 0)
}
fun fromSize(size: Int): InspectableValue<*> {
return when (size) {
ViewGroup.LayoutParams.WRAP_CONTENT ->
InspectableValue.mutable(InspectableValue.Type.Enum, "WRAP_CONTENT")
ViewGroup.LayoutParams.MATCH_PARENT ->
InspectableValue.mutable(InspectableValue.Type.Enum, "MATCH_PARENT")
else -> InspectableValue.mutable(InspectableValue.Type.Enum, Integer.toString(size))
}
}
fun getLayoutParams(node: View): MutableMap<String, Any> {
val layoutParams = node.layoutParams
val params = mutableMapOf<String, Any>()
params.put("width", fromSize(layoutParams.width))
params.put("height", fromSize(layoutParams.height))
if (layoutParams is MarginLayoutParams) {
val marginLayoutParams = layoutParams
val margin =
mapOf<String, Any>(
"left" to InspectableValue.mutable(marginLayoutParams.leftMargin),
"top" to InspectableValue.mutable(marginLayoutParams.topMargin),
"right" to InspectableValue.mutable(marginLayoutParams.rightMargin),
"bottom" to InspectableValue.mutable(marginLayoutParams.bottomMargin))
params.put("margin", margin)
}
if (layoutParams is FrameLayout.LayoutParams) {
params.put("gravity", GravityMapping.toPicker(layoutParams.gravity))
}
if (layoutParams is LinearLayout.LayoutParams) {
val linearLayoutParams = layoutParams
params.put("weight", InspectableValue.mutable(linearLayoutParams.weight))
params.put("gravity", GravityMapping.toPicker(linearLayoutParams.gravity))
}
return params
}
fun getTags(node: View): MutableMap<String, Any?> {
val tags = mutableMapOf<String, Any?>()
KeyedTagsField?.let { field ->
val keyedTags = field[node] as SparseArray<*>
if (keyedTags != null) {
var i = 0
val count = keyedTags.size()
while (i < count) {
val id =
ResourcesUtil.getIdStringQuietly(node.context, node.resources, keyedTags.keyAt(i))
tags.put(id, keyedTags.valueAt(i))
i++
}
}
}
return tags
}
private val VisibilityMapping: EnumMapping<Int> =
object : EnumMapping<Int>("VISIBLE") {
init {
put("VISIBLE", View.VISIBLE)
put("INVISIBLE", View.INVISIBLE)
put("GONE", View.GONE)
}
}
private val LayoutDirectionMapping: EnumMapping<Int> =
object : EnumMapping<Int>("LAYOUT_DIRECTION_INHERIT") {
init {
put("LAYOUT_DIRECTION_INHERIT", View.LAYOUT_DIRECTION_INHERIT)
put("LAYOUT_DIRECTION_LOCALE", View.LAYOUT_DIRECTION_LOCALE)
put("LAYOUT_DIRECTION_LTR", View.LAYOUT_DIRECTION_LTR)
put("LAYOUT_DIRECTION_RTL", View.LAYOUT_DIRECTION_RTL)
}
}
private val TextDirectionMapping: EnumMapping<Int> =
object : EnumMapping<Int>("TEXT_DIRECTION_INHERIT") {
init {
put("TEXT_DIRECTION_INHERIT", View.TEXT_DIRECTION_INHERIT)
put("TEXT_DIRECTION_FIRST_STRONG", View.TEXT_DIRECTION_FIRST_STRONG)
put("TEXT_DIRECTION_ANY_RTL", View.TEXT_DIRECTION_ANY_RTL)
put("TEXT_DIRECTION_LTR", View.TEXT_DIRECTION_LTR)
put("TEXT_DIRECTION_RTL", View.TEXT_DIRECTION_RTL)
put("TEXT_DIRECTION_LOCALE", View.TEXT_DIRECTION_LOCALE)
put("TEXT_DIRECTION_FIRST_STRONG_LTR", View.TEXT_DIRECTION_FIRST_STRONG_LTR)
put("TEXT_DIRECTION_FIRST_STRONG_RTL", View.TEXT_DIRECTION_FIRST_STRONG_RTL)
}
}
private val TextAlignmentMapping: EnumMapping<Int> =
object : EnumMapping<Int>("TEXT_ALIGNMENT_INHERIT") {
init {
put("TEXT_ALIGNMENT_INHERIT", View.TEXT_ALIGNMENT_INHERIT)
put("TEXT_ALIGNMENT_GRAVITY", View.TEXT_ALIGNMENT_GRAVITY)
put("TEXT_ALIGNMENT_TEXT_START", View.TEXT_ALIGNMENT_TEXT_START)
put("TEXT_ALIGNMENT_TEXT_END", View.TEXT_ALIGNMENT_TEXT_END)
put("TEXT_ALIGNMENT_CENTER", View.TEXT_ALIGNMENT_CENTER)
put("TEXT_ALIGNMENT_VIEW_START", View.TEXT_ALIGNMENT_VIEW_START)
put("TEXT_ALIGNMENT_VIEW_END", View.TEXT_ALIGNMENT_VIEW_END)
}
}
private val GravityMapping: EnumMapping<Int> =
object : EnumMapping<Int>("NO_GRAVITY") {
init {
put("NO_GRAVITY", Gravity.NO_GRAVITY)
put("LEFT", Gravity.LEFT)
put("TOP", Gravity.TOP)
put("RIGHT", Gravity.RIGHT)
put("BOTTOM", Gravity.BOTTOM)
put("CENTER", Gravity.CENTER)
put("CENTER_VERTICAL", Gravity.CENTER_VERTICAL)
put("FILL_VERTICAL", Gravity.FILL_VERTICAL)
put("CENTER_HORIZONTAL", Gravity.CENTER_HORIZONTAL)
put("FILL_HORIZONTAL", Gravity.FILL_HORIZONTAL)
}
}
companion object {
private var KeyedTagsField: Field? = null
private var ListenerInfoField: Field? = null
private var OnClickListenerField: Field? = null
init {
try {
KeyedTagsField = View::class.java.getDeclaredField("mKeyedTags")
KeyedTagsField?.let { field -> field.isAccessible = true }
ListenerInfoField = View::class.java.getDeclaredField("mListenerInfo")
ListenerInfoField?.let { field -> field.isAccessible = true }
val viewInfoClassName = View::class.java.name + "\$ListenerInfo"
OnClickListenerField = Class.forName(viewInfoClassName).getDeclaredField("mOnClickListener")
OnClickListenerField?.let { field -> field.isAccessible = true }
} catch (ignored: Exception) {}
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.descriptors
import android.app.Fragment
import android.os.Build
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewGroupCompat
import com.facebook.flipper.plugins.uidebugger.common.InspectableValue
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
class ViewGroupDescriptor : AbstractChainedDescriptor<ViewGroup>() {
override fun init() {}
override fun onGetId(viewGroup: ViewGroup): String {
return Integer.toString(System.identityHashCode(viewGroup))
}
override fun onGetName(viewGroup: ViewGroup): String {
return viewGroup.javaClass.simpleName
}
override fun onGetChildren(viewGroup: ViewGroup, children: MutableList<Any>) {
val count = viewGroup.childCount - 1
for (i in 0..count) {
val child: View = viewGroup.getChildAt(i)
val fragment = getAttachedFragmentForView(child)
if (fragment != null && !FragmentCompat.isDialogFragment(fragment)) {
children.add(fragment)
} else children.add(child)
}
}
override fun onGetData(viewGroup: ViewGroup, builder: MutableMap<String, Any?>) {
Log.d("FLIPPER_LAYOUT", "[viewgroup] onGetData")
val groupBuilder = mutableMapOf<String, Any?>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
groupBuilder.put(
"LayoutMode",
InspectableValue.mutable(
InspectableValue.Type.Enum,
if (viewGroup.getLayoutMode() == ViewGroupCompat.LAYOUT_MODE_CLIP_BOUNDS)
"LAYOUT_MODE_CLIP_BOUNDS"
else "LAYOUT_MODE_OPTICAL_BOUNDS"))
groupBuilder.put(
"ClipChildren",
InspectableValue.mutable(InspectableValue.Type.Boolean, viewGroup.getClipChildren()))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
groupBuilder.put(
"ClipToPadding",
InspectableValue.mutable(InspectableValue.Type.Boolean, viewGroup.getClipToPadding()))
}
builder.put("ViewGroup", groupBuilder)
}
companion object {
private fun getAttachedFragmentForView(v: View): Any? {
return try {
val fragment = FragmentCompat.findFragmentForView(v)
var added = false
if (fragment is Fragment) {
added = fragment.isAdded
} else if (fragment is androidx.fragment.app.Fragment) {
added = fragment.isAdded
}
if (added) fragment else null
} catch (e: RuntimeException) {
null
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.descriptors
import android.view.Window
class WindowDescriptor : AbstractChainedDescriptor<Window>() {
override fun init() {}
override fun onGetId(window: Window): String {
return Integer.toString(System.identityHashCode(window))
}
override fun onGetName(window: Window): String {
return window.javaClass.simpleName
}
override fun onGetChildren(window: Window, children: MutableList<Any>) {
children.add(window.decorView)
}
override fun onGetData(window: Window, builder: MutableMap<String, Any?>) {}
}