From f06f63306e11c71359e558f9768c5c7d62d6ba72 Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Tue, 13 Sep 2022 11:05:42 -0700 Subject: [PATCH] Restore root view tracking Summary: We use the root view resolver to detect when roots have been added. We currently have some race conditions where the activity stack managed by application ref doesnt include the first activity Reviewed By: lblasa Differential Revision: D39466929 fbshipit-source-id: fff4f830dea337d96dd9a9956a20a080fff2e965 --- .../uidebugger/core/ApplicationInspector.kt | 53 --------------- .../plugins/uidebugger/core/ApplicationRef.kt | 2 +- .../uidebugger/core/RootViewResolver.kt | 29 ++++---- .../descriptors/ApplicationRefDescriptor.kt | 30 ++++++++- .../observers/ApplicationTreeObserver.kt | 66 ++++--------------- 5 files changed, 57 insertions(+), 123 deletions(-) delete mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt 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() {