Addresses fragments tracking and different bugs/warnings

Summary:
^

After this change lands, it is safe to remove most of the Stetho fragment support types.

Reviewed By: LukeDefeo

Differential Revision: D39460121

fbshipit-source-id: 0e7d4ce71e828ee7bc9c6e945b8fe27dbd6f08f8
This commit is contained in:
Lorenzo Blasa
2022-09-20 05:15:50 -07:00
committed by Facebook GitHub Bot
parent e8392bdceb
commit 86364cbd40
21 changed files with 565 additions and 194 deletions

View File

@@ -9,8 +9,8 @@ package com.facebook.flipper.plugins.uidebugger.litho
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.TreeObserver
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserver
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverBuilder
import com.facebook.litho.LithoView

View File

@@ -0,0 +1,133 @@
/*
* 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.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.os.Build
import android.os.Bundle
import java.lang.ref.WeakReference
import java.lang.reflect.Field
import java.lang.reflect.Method
object ActivityTracker : Application.ActivityLifecycleCallbacks {
interface ActivityStackChangedListener {
fun onActivityAdded(activity: Activity, stack: List<Activity>)
fun onActivityStackChanged(stack: List<Activity>)
fun onActivityDestroyed(activity: Activity, stack: List<Activity>)
}
private val activities: MutableList<WeakReference<Activity>> = mutableListOf()
private val trackedActivities: MutableSet<Int> = mutableSetOf()
private var activityStackChangedListener: ActivityStackChangedListener? = null
fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) {
activityStackChangedListener = listener
}
fun start(application: Application) {
initialiseActivities()
application.registerActivityLifecycleCallbacks(this)
}
private fun trackActivity(activity: Activity) {
if (trackedActivities.contains(System.identityHashCode(activity))) {
return
}
trackedActivities.add(System.identityHashCode(activity))
activities.add(WeakReference<Activity>(activity))
FragmentTracker.trackFragmentsOfActivity(activity)
activityStackChangedListener?.onActivityAdded(activity, this.activitiesStack)
activityStackChangedListener?.onActivityStackChanged(this.activitiesStack)
}
private fun untrackActivity(activity: Activity) {
trackedActivities.remove(System.identityHashCode(activity))
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
while (activityIterator.hasNext()) {
if (activityIterator.next().get() === activity) {
activityIterator.remove()
}
}
FragmentTracker.untrackFragmentsOfActivity(activity)
activityStackChangedListener?.onActivityDestroyed(activity, this.activitiesStack)
activityStackChangedListener?.onActivityStackChanged(this.activitiesStack)
}
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
trackActivity(activity)
}
override fun onActivityStarted(activity: Activity) {
trackActivity(activity)
}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {
untrackActivity(activity)
}
val activitiesStack: List<Activity>
get() {
val stack: MutableList<Activity> = ArrayList(activities.size)
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
while (activityIterator.hasNext()) {
val activity: Activity? = activityIterator.next().get()
if (activity == null) {
activityIterator.remove()
} else {
stack.add(activity)
}
}
return stack
}
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
fun initialiseActivities() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return
}
try {
val activityThreadClass: Class<*> = Class.forName("android.app.ActivityThread")
val currentActivityThreadMethod: Method =
activityThreadClass.getMethod("currentActivityThread")
val currentActivityThread: Any? = currentActivityThreadMethod.invoke(null)
currentActivityThread?.let { activityThread ->
val mActivitiesField: Field = activityThreadClass.getDeclaredField("mActivities")
mActivitiesField.isAccessible = true
val mActivities = mActivitiesField.get(activityThread) as android.util.ArrayMap<*, *>
for (record in mActivities.values) {
val recordClass: Class<*> = record.javaClass
val activityField: Field = recordClass.getDeclaredField("activity")
activityField.isAccessible = true
val activity = activityField.get(record)
if (activity != null && activity is Activity) {
trackActivity(activity)
}
}
}
} catch (e: Exception) {}
}
}

View File

@@ -9,68 +9,25 @@ package com.facebook.flipper.plugins.uidebugger.core
import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.view.View
import java.lang.ref.WeakReference
class ApplicationRef(val application: Application) : Application.ActivityLifecycleCallbacks {
interface ActivityStackChangedListener {
fun onActivityAdded(activity: Activity, stack: List<Activity>)
fun onActivityStackChanged(stack: List<Activity>)
fun onActivityDestroyed(activity: Activity, stack: List<Activity>)
class ApplicationRef(val application: Application) {
init {
ActivityTracker.start(application)
}
val rootsResolver: RootViewResolver
private val activities: MutableList<WeakReference<Activity>>
private var activityStackChangedlistener: ActivityStackChangedListener? = null
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activities.add(WeakReference<Activity>(activity))
activityStackChangedlistener?.onActivityAdded(activity, this.activitiesStack)
activityStackChangedlistener?.onActivityStackChanged(this.activitiesStack)
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
while (activityIterator.hasNext()) {
if (activityIterator.next().get() === activity) {
activityIterator.remove()
}
}
activityStackChangedlistener?.onActivityDestroyed(activity, this.activitiesStack)
activityStackChangedlistener?.onActivityStackChanged(this.activitiesStack)
}
fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) {
activityStackChangedlistener = listener
}
val rootsResolver: RootViewResolver = RootViewResolver()
val activitiesStack: List<Activity>
get() {
val stack: MutableList<Activity> = ArrayList<Activity>(activities.size)
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
while (activityIterator.hasNext()) {
val activity: Activity? = activityIterator.next().get()
if (activity == null) {
activityIterator.remove()
} else {
stack.add(activity)
}
}
return stack
return ActivityTracker.activitiesStack
}
val rootViews: List<View>
get() {
val roots = rootsResolver.listActiveRootViews()
roots?.let { roots ->
val viewRoots: MutableList<View> = ArrayList<View>(roots.size)
val activeRootViews = rootsResolver.listActiveRootViews()
activeRootViews?.let { roots ->
val viewRoots: MutableList<View> = ArrayList(roots.size)
for (root in roots) {
viewRoots.add(root.view)
}
@@ -79,10 +36,4 @@ class ApplicationRef(val application: Application) : Application.ActivityLifecyc
return emptyList()
}
init {
rootsResolver = RootViewResolver()
application.registerActivityLifecycleCallbacks(this)
activities = ArrayList<WeakReference<Activity>>()
}
}

View File

@@ -0,0 +1,271 @@
/*
* 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.app.Activity
import android.os.Build
import android.os.Bundle
import android.view.View
import java.lang.ref.WeakReference
object FragmentTracker {
class FragmentRef {
val supportFragment: WeakReference<androidx.fragment.app.Fragment>?
val frameworkFragment: WeakReference<android.app.Fragment>?
val isSupportFragment: Boolean
constructor(supportFragment: androidx.fragment.app.Fragment) {
this.supportFragment = WeakReference(supportFragment)
this.frameworkFragment = null
this.isSupportFragment = true
}
constructor(frameworkFragment: android.app.Fragment) {
this.supportFragment = null
this.frameworkFragment = WeakReference(frameworkFragment)
this.isSupportFragment = false
}
override fun hashCode(): Int {
if (isSupportFragment) {
val fragment = supportFragment?.get()
fragment?.let { f ->
return System.identityHashCode(f)
}
} else {
val fragment = frameworkFragment?.get()
fragment?.let { f ->
return System.identityHashCode(f)
}
}
return -1
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FragmentRef
if (isSupportFragment != other.isSupportFragment) return false
if (isSupportFragment && (supportFragment != other.supportFragment)) return false
else if (frameworkFragment != other.frameworkFragment) return false
return true
}
val view: View?
get() {
if (isSupportFragment) {
val fragment = supportFragment?.get()
fragment?.let { f ->
return f.view
}
} else {
val fragment = frameworkFragment?.get()
fragment?.let { f ->
return f.view
}
}
return null
}
override fun toString(): String {
if (isSupportFragment) {
val fragment = supportFragment?.get()
fragment?.let { f ->
return "$f"
}
} else {
val fragment = frameworkFragment?.get()
fragment?.let { f ->
return "$f"
}
}
return "unknown"
}
}
private val activityFragments: MutableMap<Int, MutableSet<FragmentRef>> = mutableMapOf()
private val viewFragment: MutableMap<Int, FragmentRef> = mutableMapOf()
private val supportLibraryLifecycleTracker =
object : androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentAttached(
fm: androidx.fragment.app.FragmentManager,
f: androidx.fragment.app.Fragment,
context: android.content.Context
) {
super.onFragmentAttached(fm, f, context)
f.activity?.let { activity ->
val fragmentRef = FragmentRef(f)
activityFragments[System.identityHashCode(activity)]?.add(fragmentRef)
}
}
override fun onFragmentViewCreated(
fm: androidx.fragment.app.FragmentManager,
f: androidx.fragment.app.Fragment,
v: View,
savedInstanceState: Bundle?
) {
super.onFragmentViewCreated(fm, f, v, savedInstanceState)
val fragmentRef = FragmentRef(f)
viewFragment[System.identityHashCode(v)] = fragmentRef
}
override fun onFragmentViewDestroyed(
fm: androidx.fragment.app.FragmentManager,
f: androidx.fragment.app.Fragment
) {
super.onFragmentViewDestroyed(fm, f)
for (entry in viewFragment) {
if (entry.value.supportFragment == f) {
viewFragment.remove(entry.key)
break
}
}
}
override fun onFragmentDetached(
fm: androidx.fragment.app.FragmentManager,
f: androidx.fragment.app.Fragment
) {
super.onFragmentDetached(fm, f)
f.activity?.let { activity ->
val fragmentRef = FragmentRef(f)
activityFragments[System.identityHashCode(activity)]?.remove(fragmentRef)
}
f.view?.let { view -> viewFragment.remove(System.identityHashCode(view)) }
}
}
private var frameworkLifecycleTracker: Any? = null
fun trackFragmentsOfActivity(activity: Activity) {
activityFragments[System.identityHashCode(activity)] = mutableSetOf()
if (activity is androidx.fragment.app.FragmentActivity) {
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(
supportLibraryLifecycleTracker, true)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && frameworkLifecycleTracker != null) {
activity.fragmentManager.registerFragmentLifecycleCallbacks(
frameworkLifecycleTracker as android.app.FragmentManager.FragmentLifecycleCallbacks,
true)
}
}
}
fun untrackFragmentsOfActivity(activity: Activity) {
activityFragments[System.identityHashCode(activity)]?.let { fragments ->
fragments.forEach { f ->
f.view?.let { view -> viewFragment.remove(System.identityHashCode(view)) }
}
}
activityFragments.remove(System.identityHashCode(activity))
if (activity is androidx.fragment.app.FragmentActivity) {
activity.supportFragmentManager.unregisterFragmentLifecycleCallbacks(
supportLibraryLifecycleTracker)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && frameworkLifecycleTracker != null) {
activity.fragmentManager.unregisterFragmentLifecycleCallbacks(
frameworkLifecycleTracker as android.app.FragmentManager.FragmentLifecycleCallbacks)
}
}
}
fun getFragment(view: View): Any? {
val key = System.identityHashCode(view)
val fragmentRef = viewFragment[key]
fragmentRef?.let { fragment ->
fragment.supportFragment?.get()?.let { f ->
return f
}
fragment.frameworkFragment?.get()?.let { f ->
return f
}
}
return null
}
fun getDialogFragments(activity: Activity): List<Any> {
val key = System.identityHashCode(activity)
val fragments = mutableListOf<Any>()
activityFragments[key]?.forEach { fragmentRef ->
fragmentRef.supportFragment?.get()?.let { fragment ->
if (androidx.fragment.app.DialogFragment::class.java.isInstance(fragment)) {
fragments.add(fragmentRef)
}
}
fragmentRef.frameworkFragment?.get()?.let { fragment ->
if (android.app.DialogFragment::class.java.isInstance(fragment)) {
fragments.add(fragmentRef)
}
}
}
return fragments
}
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
Class.forName("android.app.FragmentManager\$FragmentLifecycleCallbacks") != null) {
frameworkLifecycleTracker =
object : android.app.FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentAttached(
fm: android.app.FragmentManager?,
f: android.app.Fragment?,
context: android.content.Context?
) {
super.onFragmentAttached(fm, f, context)
f?.let { fragment ->
val fragmentRef = FragmentRef(fragment)
activityFragments[System.identityHashCode(fragment.activity)]?.add(fragmentRef)
}
}
override fun onFragmentViewCreated(
fm: android.app.FragmentManager?,
f: android.app.Fragment?,
v: View?,
savedInstanceState: Bundle?
) {
super.onFragmentViewCreated(fm, f, v, savedInstanceState)
if (f != null && v != null) {
val fragmentRef = FragmentRef(f)
viewFragment[System.identityHashCode(v)] = fragmentRef
}
}
override fun onFragmentDetached(
fm: android.app.FragmentManager?,
f: android.app.Fragment?
) {
super.onFragmentDetached(fm, f)
f?.let { fragment ->
val fragmentRef = FragmentRef(fragment)
activityFragments[System.identityHashCode(fragment.activity)]?.remove(fragmentRef)
fragment.view?.let { view -> viewFragment.remove(System.identityHashCode(view)) }
}
}
}
}
}
}

View File

@@ -46,7 +46,7 @@ class LayoutTraversal(
val childDescriptor =
descriptorRegister.descriptorForClassUnsafe(child::class.java).asAny()
childrenIds.add(childDescriptor.getId(child))
// if there is an active child then dont traverse it
// if there is an active child then don't traverse it
if (activeChild == null) {
stack.add(child)
}

View File

@@ -11,7 +11,7 @@ import android.view.View
import android.view.ViewGroup
/** Layout Visitor traverses the entire view hierarchy from a given root. */
class LayoutVisitor(val visitor: Visitor) {
class LayoutVisitor(private val visitor: Visitor) {
interface Visitor {
fun visit(view: View)
}

View File

@@ -13,7 +13,6 @@ import android.view.WindowManager
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier
import java.util.ArrayList
/**
* Provides access to all root views in an application.
@@ -125,20 +124,22 @@ class RootViewResolver {
var params: List<WindowManager.LayoutParams>? = null
try {
viewsField?.let { field ->
views =
if (Build.VERSION.SDK_INT < 19) {
val arr = field[windowManagerObj] as Array<View>
views = arr.toList()
arr.toList()
} else {
views = field[windowManagerObj] as List<View>
field[windowManagerObj] as List<View>
}
}
paramsField?.let { field ->
params =
if (Build.VERSION.SDK_INT < 19) {
val arr = field[windowManagerObj] as Array<WindowManager.LayoutParams>
params = arr.toList() as List<WindowManager.LayoutParams>
arr.toList() as List<WindowManager.LayoutParams>
} else {
params = field[windowManagerObj] as List<WindowManager.LayoutParams>
field[windowManagerObj] as List<WindowManager.LayoutParams>
}
}
} catch (re: RuntimeException) {
@@ -150,12 +151,12 @@ class RootViewResolver {
val roots = mutableListOf<RootView>()
views?.let { views ->
params?.let { params ->
if (views.size == params.size) {
for (i in views.indices) {
val view = views[i]
// TODO FIX, len(param) is not always the same as len(views) For now just use first
// params
roots.add(RootView(view, null))
val param = params[i]
roots.add(RootView(view, param))
}
}
}
}

View File

@@ -9,7 +9,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
import com.facebook.flipper.plugins.uidebugger.core.FragmentTracker
object ActivityDescriptor : ChainedDescriptor<Activity>() {
@@ -24,30 +24,12 @@ object ActivityDescriptor : ChainedDescriptor<Activity>() {
override fun onGetChildren(node: Activity, children: MutableList<Any>) {
node.window?.let { window -> children.add(window) }
var fragments = getDialogFragments(FragmentCompat.supportInstance, node)
for (fragment in fragments) {
children.add(fragment)
}
fragments = getDialogFragments(FragmentCompat.frameworkInstance, node)
for (fragment in fragments) {
children.add(fragment)
}
val fragments = FragmentTracker.getDialogFragments(node)
fragments.forEach { fragment -> children.add(fragment) }
}
override fun onGetData(
node: Activity,
attributeSections: MutableMap<String, InspectableObject>
) {}
private fun getDialogFragments(
compat: FragmentCompat<*, *, *, *>?,
activity: Activity
): List<Any> {
if (compat == null) {
return emptyList()
}
return compat.getDialogFragments(activity)
}
}

View File

@@ -8,14 +8,11 @@
package com.facebook.flipper.plugins.uidebugger.descriptors
import android.app.Activity
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import android.view.View
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
object ApplicationRefDescriptor : ChainedDescriptor<ApplicationRef>() {
val rootsLocal = RootViewResolver()
override fun onGetActiveChild(node: ApplicationRef): Any? {
return if (node.activitiesStack.isNotEmpty()) node.activitiesStack.last() else null
}
@@ -34,27 +31,17 @@ object ApplicationRefDescriptor : ChainedDescriptor<ApplicationRef>() {
override fun onGetChildren(node: ApplicationRef, children: MutableList<Any>) {
val activeRoots = node.rootViews
Log.i(LogTag, rootsLocal.toString())
activeRoots.let { roots ->
for (root in roots) {
var added = false
/**
* This code serves 2 purposes: 1.it picks up root views not tied to an activity (dialogs)
* 2. We can get initialized late and miss the first activity, it does seem that the root
* view resolver is able to (usually ) get the root view regardless, with this we insert the
* root decor view without the activity. Ideally we wouldn't rely on this behaviour and find
* a better way to track activities
*/
val added = mutableSetOf<View>()
for (activity: Activity in node.activitiesStack) {
if (activity.window.decorView == root) {
children.add(activity)
added = true
break
added.add(activity.window.decorView)
}
}
if (!added) {
// Picks up root views not tied to an activity (dialogs)
for (root in activeRoots) {
if (!added.contains(root)) {
children.add(root)
}
added.add(root)
}
}
}

View File

@@ -33,21 +33,26 @@ class DescriptorRegister {
mapping.register(TextView::class.java, TextViewDescriptor)
mapping.register(Button::class.java, ButtonDescriptor)
mapping.register(ViewPager::class.java, ViewPagerDescriptor)
mapping.register(android.app.Fragment::class.java, FragmentFrameworkDescriptor)
mapping.register(androidx.fragment.app.Fragment::class.java, FragmentSupportDescriptor)
@Suppress("UNCHECKED_CAST")
for (clazz in mapping.register.keys) {
val descriptor: NodeDescriptor<*>? = mapping.register[clazz]
descriptor?.let { descriptor ->
val maybeDescriptor: NodeDescriptor<*>? = mapping.register[clazz]
maybeDescriptor?.let { descriptor ->
if (descriptor is ChainedDescriptor<*>) {
val chainedDescriptor = descriptor as ChainedDescriptor<Any>
val superClass: Class<*> = clazz.getSuperclass()
val superDescriptor: NodeDescriptor<*>? = mapping.descriptorForClass(superClass)
// todo we should walk all the way up the superclass hierarchy?
val superClass: Class<*> = clazz.superclass
val maybeSuperDescriptor: NodeDescriptor<*>? = mapping.descriptorForClass(superClass)
maybeSuperDescriptor?.let { superDescriptor ->
if (superDescriptor is ChainedDescriptor<*>) {
chainedDescriptor.setSuper(superDescriptor as ChainedDescriptor<Any>)
}
}
}
}
}
return mapping
}
@@ -58,11 +63,17 @@ class DescriptorRegister {
}
fun <T> descriptorForClass(clazz: Class<T>): NodeDescriptor<T>? {
var clazz: Class<*> = clazz
while (!register.containsKey(clazz)) {
clazz = clazz.superclass
var mutableClass: Class<*> = clazz
while (!register.containsKey(mutableClass)) {
mutableClass = mutableClass.superclass
}
return if (register[clazz] != null) {
@Suppress("UNCHECKED_CAST")
register[clazz] as NodeDescriptor<T>
} else {
null
}
return register[clazz] as NodeDescriptor<T>
}
fun <T> descriptorForClassUnsafe(clazz: Class<T>): NodeDescriptor<T> {

View File

@@ -0,0 +1,30 @@
/*
* 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 com.facebook.flipper.plugins.uidebugger.common.InspectableObject
object FragmentFrameworkDescriptor : ChainedDescriptor<android.app.Fragment>() {
override fun onGetId(node: android.app.Fragment): String {
return System.identityHashCode(node).toString()
}
override fun onGetName(node: android.app.Fragment): String {
return node.javaClass.simpleName
}
override fun onGetChildren(node: android.app.Fragment, children: MutableList<Any>) {
node.view?.let { view -> children.add(view) }
}
override fun onGetData(
node: android.app.Fragment,
attributeSections: MutableMap<String, InspectableObject>
) {}
}

View File

@@ -0,0 +1,30 @@
/*
* 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 com.facebook.flipper.plugins.uidebugger.common.InspectableObject
object FragmentSupportDescriptor : ChainedDescriptor<androidx.fragment.app.Fragment>() {
override fun onGetId(node: androidx.fragment.app.Fragment): String {
return System.identityHashCode(node).toString()
}
override fun onGetName(node: androidx.fragment.app.Fragment): String {
return node.javaClass.simpleName
}
override fun onGetChildren(node: androidx.fragment.app.Fragment, children: MutableList<Any>) {
node.view?.let { view -> children.add(view) }
}
override fun onGetData(
node: androidx.fragment.app.Fragment,
attributeSections: MutableMap<String, InspectableObject>
) {}
}

View File

@@ -7,7 +7,6 @@
package com.facebook.flipper.plugins.uidebugger.descriptors
import android.app.Fragment
import android.os.Build
import android.view.View
import android.view.ViewGroup
@@ -16,7 +15,7 @@ import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
import com.facebook.flipper.plugins.uidebugger.common.Inspectable
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.common.InspectableValue
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
import com.facebook.flipper.plugins.uidebugger.core.FragmentTracker
object ViewGroupDescriptor : ChainedDescriptor<ViewGroup>() {
@@ -32,8 +31,8 @@ object ViewGroupDescriptor : ChainedDescriptor<ViewGroup>() {
val count = node.childCount - 1
for (i in 0..count) {
val child: View = node.getChildAt(i)
val fragment = getAttachedFragmentForView(child)
if (fragment != null && !FragmentCompat.isDialogFragment(fragment)) {
val fragment = FragmentTracker.getFragment(child)
if (fragment != null) {
children.add(fragment)
} else children.add(child)
}
@@ -63,19 +62,4 @@ object ViewGroupDescriptor : ChainedDescriptor<ViewGroup>() {
"LAYOUT_MODE_CLIP_BOUNDS" to ViewGroupCompat.LAYOUT_MODE_CLIP_BOUNDS,
"LAYOUT_MODE_OPTICAL_BOUNDS" to ViewGroupCompat.LAYOUT_MODE_OPTICAL_BOUNDS,
)) {}
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

@@ -10,13 +10,12 @@ package com.facebook.flipper.plugins.uidebugger.observers
import android.util.Log
import android.view.View
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.TreeObserver
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
/**
* responsible for observing the activity stack and managing the subscription to the top most
* Responsible for observing the activity stack and managing the subscription to the top most
* content view (decor view)
*/
class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRef>() {
@@ -48,6 +47,7 @@ class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRe
}
override fun unsubscribe() {
context.applicationRef.setActivityStackChangedListener(null)
// Not entirely sure this will ever happen or be needed.
// context.applicationRef.setActivityStackChangedListener(null)
}
}

View File

@@ -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.TreeObserver
import com.facebook.flipper.plugins.uidebugger.core.Context
typealias DecorView = View

View File

@@ -7,11 +7,9 @@
package com.facebook.flipper.plugins.uidebugger.observers
import com.facebook.flipper.plugins.uidebugger.TreeObserver
import com.facebook.flipper.plugins.uidebugger.core.Context
interface TreeObserverBuilder<T> {
fun canBuildFor(node: Any): Boolean
fun build(context: Context): TreeObserver<T>
}

View File

@@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger
package com.facebook.flipper.plugins.uidebugger.observers
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.observers.SubtreeUpdate
/*
Stateful class that manages some subtree in the UI Hierarchy.
@@ -75,7 +75,7 @@ abstract class TreeObserver<T> {
}
fun cleanUpRecursive() {
Log.i(LogTag, "Cleaning up observer ${this}")
Log.i(LogTag, "Cleaning up observer $this")
children.values.forEach { it.cleanUpRecursive() }
unsubscribe()
children.clear()

View File

@@ -44,7 +44,7 @@ object AccessibilityUtil {
* @param node The [AccessibilityNodeInfoCompat] to evaluate
* @return `true` if it meets the criterion for producing spoken feedback
*/
fun isSpeakingNode(node: AccessibilityNodeInfoCompat?, view: View?): Boolean {
private fun isSpeakingNode(node: AccessibilityNodeInfoCompat?, view: View?): Boolean {
if (node == null || view == null) {
return false
}
@@ -69,7 +69,7 @@ object AccessibilityUtil {
* @param node The [AccessibilityNodeInfoCompat] to evaluate
* @return `true` if it has any non-actionable speaking descendants within its subtree
*/
fun hasNonActionableSpeakingDescendants(
private fun hasNonActionableSpeakingDescendants(
node: AccessibilityNodeInfoCompat?,
view: View?
): Boolean {
@@ -79,10 +79,7 @@ object AccessibilityUtil {
val viewGroup = view as ViewGroup
val count = viewGroup.childCount - 1
for (i in 0..count) {
val childView = viewGroup.getChildAt(i)
if (childView == null) {
continue
}
val childView = viewGroup.getChildAt(i) ?: continue
val childNode = AccessibilityNodeInfoCompat.obtain()
try {
ViewCompat.onInitializeAccessibilityNodeInfo(childView, childNode)
@@ -107,7 +104,7 @@ object AccessibilityUtil {
* @param node The [AccessibilityNodeInfoCompat] to evaluate
* @return `true` if it is possible to gain accessibility focus
*/
fun isAccessibilityFocusable(node: AccessibilityNodeInfoCompat?, view: View?): Boolean {
private fun isAccessibilityFocusable(node: AccessibilityNodeInfoCompat?, view: View?): Boolean {
if (node == null || view == null) {
return false
}

View File

@@ -47,6 +47,7 @@ abstract class FragmentCompat<
}
return field
}
private var hasSupportFragment = false
init {
hasSupportFragment = tryGetClassForName("androidx.fragment.app.Fragment") != null

View File

@@ -56,7 +56,7 @@ class FragmentCompatFramework :
}
override fun forFragmentManager():
FragmentManagerAccessorViaReflection<FragmentManager, Fragment>? {
FragmentManagerAccessorViaReflection<FragmentManager, Fragment> {
return fragmentManagerAccessor
}
@@ -75,7 +75,7 @@ class FragmentCompatFramework :
val fragmentManagerAccessor = forFragmentManager()
var addedFragments: List<Any?>? = null
try {
addedFragments = fragmentManagerAccessor?.getAddedFragments(fragmentManager)
addedFragments = fragmentManagerAccessor.getAddedFragments(fragmentManager)
} catch (e: Exception) {}
if (addedFragments != null) {
@@ -99,7 +99,7 @@ class FragmentCompatFramework :
}
val activityAccessor = forFragmentActivity()
val fragmentManager = activityAccessor?.getFragmentManager(activity) ?: return null
val fragmentManager = activityAccessor.getFragmentManager(activity) ?: return null
return findFragmentForViewInFragmentManager(fragmentManager, view)
}
@@ -111,7 +111,7 @@ class FragmentCompatFramework :
val fragmentManagerAccessor = forFragmentManager()
var fragments: List<Any>? = null
try {
fragments = fragmentManagerAccessor?.getAddedFragments(fragmentManager)
fragments = fragmentManagerAccessor.getAddedFragments(fragmentManager)
} catch (e: Exception) {}
if (fragments != null) {

View File

@@ -25,19 +25,19 @@ class FragmentCompatSupportLib :
override val fragmentActivityClass: Class<FragmentActivity>
get() = FragmentActivity::class.java
override fun forFragment(): FragmentAccessorSupportLib? {
override fun forFragment(): FragmentAccessorSupportLib {
return fragmentAccessor
}
override fun forDialogFragment(): DialogFragmentAccessorSupportLib? {
override fun forDialogFragment(): DialogFragmentAccessorSupportLib {
return dialogFragmentAccessor
}
override fun forFragmentManager(): FragmentManagerAccessor<FragmentManager, Fragment>? {
override fun forFragmentManager(): FragmentManagerAccessor<FragmentManager, Fragment> {
return fragmentManagerAccessor
}
override fun forFragmentActivity(): FragmentActivityAccessorSupportLib? {
override fun forFragmentActivity(): FragmentActivityAccessorSupportLib {
return fragmentActivityAccessor
}
@@ -48,12 +48,12 @@ class FragmentCompatSupportLib :
val activityAccessor = forFragmentActivity()
val fragmentManager =
activityAccessor?.getFragmentManager(activity as FragmentActivity) ?: return emptyList()
activityAccessor.getFragmentManager(activity as FragmentActivity) ?: return emptyList()
val fragmentManagerAccessor = forFragmentManager()
var addedFragments: List<Any?>? = null
try {
addedFragments = fragmentManagerAccessor?.getAddedFragments(fragmentManager)
addedFragments = fragmentManagerAccessor.getAddedFragments(fragmentManager)
} catch (e: Exception) {}
if (addedFragments != null) {
@@ -78,7 +78,7 @@ class FragmentCompatSupportLib :
val activityAccessor = forFragmentActivity()
val fragmentManager =
activityAccessor?.getFragmentManager(activity as FragmentActivity) ?: return null
activityAccessor.getFragmentManager(activity as FragmentActivity) ?: return null
return findFragmentForViewInFragmentManager(fragmentManager, view)
}
@@ -90,7 +90,7 @@ class FragmentCompatSupportLib :
val fragmentManagerAccessor = forFragmentManager()
var fragments: List<Any>? = null
try {
fragments = fragmentManagerAccessor?.getAddedFragments(fragmentManager)
fragments = fragmentManagerAccessor.getAddedFragments(fragmentManager)
} catch (e: Exception) {}
if (fragments != null) {
@@ -112,19 +112,15 @@ class FragmentCompatSupportLib :
}
val fragmentAccessor = forFragment()
fragmentAccessor?.let { accessor ->
if (accessor.getView(fragment as Fragment) === view) {
if (fragmentAccessor.getView(fragment as Fragment) === view) {
return fragment
}
val childFragmentManager = accessor.getChildFragmentManager(fragment as Fragment)
val childFragmentManager = fragmentAccessor.getChildFragmentManager(fragment as Fragment)
return if (childFragmentManager != null) {
findFragmentForViewInFragmentManager(childFragmentManager, view)
} else null
}
return null
}
open class FragmentAccessorSupportLib : FragmentAccessor<Fragment, FragmentManager> {
override fun getFragmentManager(fragment: Fragment): FragmentManager? {
return fragment.fragmentManager
@@ -161,7 +157,7 @@ class FragmentCompatSupportLib :
class FragmentActivityAccessorSupportLib :
FragmentActivityAccessor<FragmentActivity, FragmentManager> {
override fun getFragmentManager(activity: FragmentActivity): FragmentManager? {
override fun getFragmentManager(activity: FragmentActivity): FragmentManager {
return activity.supportFragmentManager
}
}