diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt deleted file mode 100644 index 93e8e735c..000000000 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.view.View -import android.view.ViewTreeObserver -import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister - -class ApplicationInspector(val context: Context) { - val descriptorRegister = DescriptorRegister.withDefaults() - val traversal = LayoutTraversal(descriptorRegister, context.applicationRef) - - fun attachListeners(view: View) { - // An OnGlobalLayoutListener watches the entire hierarchy for layout changes - // (so registering one of these on any View in a hierarchy will cause it to be triggered - // when any View in that hierarchy is laid out or changes visibility). - view - .getViewTreeObserver() - .addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() {} - }) - view - .getViewTreeObserver() - .addOnPreDrawListener( - object : ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - return true - } - }) - } - - fun observe() { - val rootResolver = RootViewResolver() - rootResolver.attachListener( - object : RootViewResolver.Listener { - override fun onRootViewAdded(view: View) { - attachListeners(view) - } - - override fun onRootViewRemoved(view: View) {} - override fun onRootViewsChanged(views: java.util.List) {} - }) - - val activeRoots = rootResolver.listActiveRootViews() - activeRoots?.let { roots -> for (root: RootViewResolver.RootView in roots) {} } - } -} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt index d3f09fd41..75557f141 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt @@ -20,7 +20,7 @@ class ApplicationRef(val application: Application) : Application.ActivityLifecyc fun onActivityDestroyed(activity: Activity, stack: List) } - private val rootsResolver: RootViewResolver + val rootsResolver: RootViewResolver private val activities: MutableList> private var activityStackChangedlistener: ActivityStackChangedListener? = null diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/RootViewResolver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/RootViewResolver.kt index 7929033b1..d985a776c 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/RootViewResolver.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/RootViewResolver.kt @@ -14,7 +14,6 @@ import java.lang.reflect.Field import java.lang.reflect.InvocationTargetException import java.lang.reflect.Modifier import java.util.ArrayList -import java.util.List /** * Provides access to all root views in an application. @@ -39,28 +38,28 @@ class RootViewResolver { interface Listener { fun onRootViewAdded(rootView: View) fun onRootViewRemoved(rootView: View) - fun onRootViewsChanged(rootView: List) + fun onRootViewsChanged(rootViews: List) } - class ObservableArrayList() : ArrayList() { + class ObservableArrayList : ArrayList() { private var listener: Listener? = null fun setListener(listener: Listener?) { this.listener = listener } - override fun add(value: View): Boolean { - val ret = super.add(value) + override fun add(element: View): Boolean { + val ret = super.add(element) listener?.let { l -> - l.onRootViewAdded(value) + l.onRootViewAdded(element) l.onRootViewsChanged(this as List) } return ret } - override fun remove(value: View): Boolean { - val ret = super.remove(value) + override fun remove(element: View): Boolean { + val ret = super.remove(element) listener?.let { l -> - l.onRootViewRemoved(value) + l.onRootViewRemoved(element) l.onRootViewsChanged(this as List) } @@ -128,7 +127,7 @@ class RootViewResolver { viewsField?.let { field -> if (Build.VERSION.SDK_INT < 19) { val arr = field[windowManagerObj] as Array - views = arr.toList() as List + views = arr.toList() } else { views = field[windowManagerObj] as List } @@ -148,16 +147,20 @@ class RootViewResolver { return null } - val roots: ArrayList = ArrayList() + val roots = mutableListOf() views?.let { views -> params?.let { params -> for (i in views.indices) { - roots.add(RootView(views[i], params[i])) + val view = views[i] + // TODO FIX, len(param) is not always the same as len(views) For now just use first + val param = params[0] + // params + roots.add(RootView(view, param)) } } } - return roots as List + return roots } private fun initialize() { 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 34d43760d..7476f0ea1 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 @@ -8,10 +8,14 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.app.Activity +import android.util.Log +import com.facebook.flipper.plugins.uidebugger.LogTag import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef +import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver object ApplicationRefDescriptor : AbstractChainedDescriptor() { + val rootsLocal = RootViewResolver() override fun onGetActiveChild(node: ApplicationRef): Any? { return if (node.activitiesStack.isNotEmpty()) node.activitiesStack.last() else null } @@ -28,8 +32,30 @@ object ApplicationRefDescriptor : AbstractChainedDescriptor() { } override fun onGetChildren(node: ApplicationRef, children: MutableList) { - for (activity: Activity in node.activitiesStack) { - children.add(activity) + 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 + */ + for (activity: Activity in node.activitiesStack) { + if (activity.window.decorView == root) { + children.add(activity) + added = true + break + } + } + if (!added) { + children.add(root) + } + } } } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt index a0c312adb..5e2265608 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt @@ -7,16 +7,13 @@ package com.facebook.flipper.plugins.uidebugger.observers -import android.app.Activity -import android.content.ContextWrapper import android.util.Log import android.view.View import com.facebook.flipper.plugins.uidebugger.LogTag -import com.facebook.flipper.plugins.uidebugger.SubtreeUpdate 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.identityHashCode +import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver /** * responsible for observing the activity stack and managing the subscription to the top most @@ -27,66 +24,27 @@ class ApplicationTreeObserver(val context: Context) : TreeObserver) { - val start = System.currentTimeMillis() - val (nodes, skipped) = context.layoutTraversal.traverse(applicationRef) - val observer = - context.observerFactory.createObserver(activity.window.decorView, context)!! - observer.subscribe(activity.window.decorView) - children[activity.window.decorView.identityHashCode()] = observer - context.treeObserverManager.emit( - SubtreeUpdate("Application", nodes, start, System.currentTimeMillis())) - Log.i( - LogTag, - "Activity added,stack size ${stack.size} found ${nodes.size} skipped $skipped Listeners $children") - } + override fun onRootViewRemoved(rootView: View) {} - override fun onActivityStackChanged(stack: List) {} - - override fun onActivityDestroyed(activity: Activity, stack: List) { - val start = System.currentTimeMillis() - - val (nodes, skipped) = context.layoutTraversal.traverse(applicationRef) - - val observer = children[activity.window.decorView.identityHashCode()] - children.remove(activity.window.decorView.identityHashCode()) - observer?.cleanUpRecursive() - - context.treeObserverManager.emit( - SubtreeUpdate("Application", nodes, start, System.currentTimeMillis())) - - Log.i( - LogTag, - "Activity removed,stack size ${stack.size} found ${nodes.size} skipped $skipped Listeners $children") + override fun onRootViewsChanged(rootViews: List) { + Log.i(LogTag, "Root views updated, num ${rootViews.size}") + traverseAndSend(context, applicationRef) } } - - context.applicationRef.setActivityStackChangedListener(addRemoveListener) + context.applicationRef.rootsResolver.attachListener(rootViewListener) + // trigger a traversal on whatever roots we have now + rootViewListener.onRootViewsChanged(applicationRef.rootViews) Log.i(LogTag, "${context.applicationRef.rootViews.size} root views") Log.i(LogTag, "${context.applicationRef.activitiesStack.size} activities") - - val stack = context.applicationRef.activitiesStack - for (activity in stack) { - addRemoveListener.onActivityAdded(activity, stack) - } - } - private fun getActivity(view: View): Activity? { - var context: android.content.Context? = view.context - while (context is ContextWrapper) { - if (context is Activity) { - return context - } - context = context.baseContext - } - return null } override fun unsubscribe() {