From 00334acb2bd4c01a0ae65fe48fa904484e150e2e Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Mon, 5 Dec 2022 07:07:44 -0800 Subject: [PATCH] Fixing issue where certain activities not tracked Summary: The root view resolver will always find all root views but there was a bug in the listrootviews method. The code was very complex and most of the code seemed to be unneeded. Its now working. The listrootviews method now just returns teh contents of the observable array. The reason we needed this is that Certain activities dont seem to tracked by the listener we add to `registerActivityLifecycleCallbacks` Its as if there is a floating decor with no activity. One example in FB4a is clicking on a notification in the notifications panel Reviewed By: lblasa Differential Revision: D41522791 fbshipit-source-id: b49b0104ddf758f097e1fd3f9ac6588de2d3646e --- .../plugins/uidebugger/core/ApplicationRef.kt | 19 +- .../uidebugger/core/RootViewResolver.kt | 192 ++++++------------ .../descriptors/ApplicationRefDescriptor.kt | 5 +- .../observers/ApplicationTreeObserver.kt | 4 +- .../components/Visualization2D.tsx | 2 +- 5 files changed, 77 insertions(+), 145 deletions(-) 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 06dcacfd1..1886cec59 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 @@ -9,31 +9,20 @@ package com.facebook.flipper.plugins.uidebugger.core import android.app.Activity import android.app.Application -import android.view.View class ApplicationRef(val application: Application) { init { ActivityTracker.start(application) } + // the root view resolver will contain all root views 100% It is needed for 2 cases: + // 1. In some cases an activity will not be picked up by the activity tracker, + // the root view resolver will at least find the decor view + // 2. Dialog fragments val rootsResolver: RootViewResolver = RootViewResolver() val activitiesStack: List get() { return ActivityTracker.activitiesStack } - - val rootViews: List - get() { - val activeRootViews = rootsResolver.listActiveRootViews() - activeRootViews?.let { roots -> - val viewRoots: MutableList = ArrayList(roots.size) - for (root in roots) { - viewRoots.add(root.view) - } - return viewRoots - } - - return emptyList() - } } 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 07af33820..34961cde6 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 @@ -10,7 +10,6 @@ package com.facebook.flipper.plugins.uidebugger.core import android.annotation.SuppressLint import android.os.Build import android.view.View -import android.view.WindowManager import java.lang.reflect.Field import java.lang.reflect.InvocationTargetException import java.lang.reflect.Modifier @@ -31,17 +30,80 @@ import java.lang.reflect.Modifier class RootViewResolver { private var initialized = false private var windowManagerObj: Any? = null - private var viewsField: Field? = null - private var paramsField: Field? = null + private val observableViews: ObservableViewArrayList = ObservableViewArrayList() - class RootView(val view: View, val param: WindowManager.LayoutParams?) interface Listener { fun onRootViewAdded(rootView: View) fun onRootViewRemoved(rootView: View) fun onRootViewsChanged(rootViews: List) } - class ObservableArrayList : ArrayList() { + fun attachListener(listener: Listener?) { + if (Build.VERSION.SDK_INT < 19 || listener == null) { + // We don't have a use for this on older APIs. If you do then modify accordingly :) + return + } + if (!initialized) { + initialize() + } + observableViews?.setListener(listener) + } + + /** + * Lists the active root views in an application at this moment. + * + * @return a list of all the active root views in the application. + * @throws IllegalStateException if invoked from a thread besides the main thread. + */ + fun rootViews(): List { + if (!initialized) { + initialize() + } + return observableViews.toList() + } + + private fun initialize() { + + initialized = true + val accessClass = + if (Build.VERSION.SDK_INT > 16) WINDOW_MANAGER_GLOBAL_CLAZZ else WINDOW_MANAGER_IMPL_CLAZZ + val instanceMethod = if (Build.VERSION.SDK_INT > 16) GET_GLOBAL_INSTANCE else GET_DEFAULT_IMPL + try { + val clazz = Class.forName(accessClass) + val getMethod = clazz.getMethod(instanceMethod) + windowManagerObj = getMethod.invoke(null) + + val viewsField: Field = clazz.getDeclaredField(VIEWS_FIELD) + + viewsField.let { vf -> + vf.isAccessible = true + // Forgive me father for I have sinned... + @SuppressLint("DiscouragedPrivateApi") + val modifiers = Field::class.java.getDeclaredField("accessFlags") + modifiers.isAccessible = true + modifiers.setInt(vf, vf.modifiers and Modifier.FINAL.inv()) + + @Suppress("unchecked_cast") + val currentWindowManagerViews = vf[windowManagerObj] as List + observableViews.addAll(currentWindowManagerViews) + vf[windowManagerObj] = observableViews + } + } catch (ite: InvocationTargetException) {} catch (cnfe: ClassNotFoundException) {} catch ( + nsfe: NoSuchFieldException) {} catch (nsme: NoSuchMethodException) {} catch ( + re: RuntimeException) {} catch (iae: IllegalAccessException) {} + + try {} catch (e: Throwable) {} + } + + companion object { + private const val WINDOW_MANAGER_IMPL_CLAZZ = "android.view.WindowManagerImpl" + private const val WINDOW_MANAGER_GLOBAL_CLAZZ = "android.view.WindowManagerGlobal" + private const val VIEWS_FIELD = "mViews" + private const val GET_DEFAULT_IMPL = "getDefault" + private const val GET_GLOBAL_INSTANCE = "getInstance" + } + + class ObservableViewArrayList : ArrayList() { private var listener: Listener? = null fun setListener(listener: Listener?) { this.listener = listener @@ -75,124 +137,4 @@ class RootViewResolver { return view } } - - fun attachListener(listener: Listener?) { - if (Build.VERSION.SDK_INT < 19 || listener == null) { - // We don't have a use for this on older APIs. If you do then modify accordingly :) - return - } - if (!initialized) { - initialize() - } - try { - viewsField?.let { vf -> - // Forgive me father for I have sinned... - @SuppressLint("DiscouragedPrivateApi") - val modifiers = Field::class.java.getDeclaredField("accessFlags") - modifiers.isAccessible = true - modifiers.setInt(vf, vf.modifiers and Modifier.FINAL.inv()) - - @Suppress("unchecked_cast") val views = vf[windowManagerObj] as List - - val observableViews = ObservableArrayList() - observableViews.setListener(listener) - observableViews.addAll(views) - - vf[windowManagerObj] = observableViews - } - } catch (e: Throwable) {} - } - - /** - * Lists the active root views in an application at this moment. - * - * @return a list of all the active root views in the application. - * @throws IllegalStateException if invoked from a thread besides the main thread. - */ - fun listActiveRootViews(): List? { - if (!initialized) { - initialize() - } - if (null == windowManagerObj) { - return null - } - if (null == viewsField) { - return null - } - if (null == paramsField) { - return null - } - var maybeViews: List? = null - var maybeParams: List? = null - try { - viewsField?.let { field -> - maybeViews = - if (Build.VERSION.SDK_INT < 19) { - @Suppress("unchecked_cast") val arr = field[windowManagerObj] as Array - arr.toList() - } else { - @Suppress("unchecked_cast") - field[windowManagerObj] as List - } - } - - paramsField?.let { field -> - maybeParams = - if (Build.VERSION.SDK_INT < 19) { - @Suppress("unchecked_cast") - val arr = field[windowManagerObj] as Array - arr.toList() - } else { - @Suppress("unchecked_cast") - field[windowManagerObj] as List - } - } - } catch (re: RuntimeException) { - return null - } catch (iae: IllegalAccessException) { - return null - } - - val roots = mutableListOf() - maybeViews?.let { views -> - maybeParams?.let { params -> - if (views.size == params.size) { - for (i in views.indices) { - val view = views[i] - val param = params[i] - roots.add(RootView(view, param)) - } - } - } - } - - return roots - } - - private fun initialize() { - initialized = true - val accessClass = - if (Build.VERSION.SDK_INT > 16) WINDOW_MANAGER_GLOBAL_CLAZZ else WINDOW_MANAGER_IMPL_CLAZZ - val instanceMethod = if (Build.VERSION.SDK_INT > 16) GET_GLOBAL_INSTANCE else GET_DEFAULT_IMPL - try { - val clazz = Class.forName(accessClass) - val getMethod = clazz.getMethod(instanceMethod) - windowManagerObj = getMethod.invoke(null) - viewsField = clazz.getDeclaredField(VIEWS_FIELD) - viewsField?.let { vf -> vf.isAccessible = true } - paramsField = clazz.getDeclaredField(WINDOW_PARAMS_FIELD) - paramsField?.let { pf -> pf.isAccessible = true } - } catch (ite: InvocationTargetException) {} catch (cnfe: ClassNotFoundException) {} catch ( - nsfe: NoSuchFieldException) {} catch (nsme: NoSuchMethodException) {} catch ( - re: RuntimeException) {} catch (iae: IllegalAccessException) {} - } - - companion object { - private const val WINDOW_MANAGER_IMPL_CLAZZ = "android.view.WindowManagerImpl" - private const val WINDOW_MANAGER_GLOBAL_CLAZZ = "android.view.WindowManagerGlobal" - private const val VIEWS_FIELD = "mViews" - private const val WINDOW_PARAMS_FIELD = "mParams" - private const val GET_DEFAULT_IMPL = "getDefault" - private const val GET_GLOBAL_INSTANCE = "getInstance" - } } 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 aadf19c8f..7bf0f27af 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 @@ -16,7 +16,8 @@ import com.facebook.flipper.plugins.uidebugger.util.DisplayMetrics object ApplicationRefDescriptor : ChainedDescriptor() { override fun onGetActiveChild(node: ApplicationRef): Any? { - return if (node.activitiesStack.isNotEmpty()) node.activitiesStack.last() else null + val children = onGetChildren(node) + return if (children.isNotEmpty()) children.last() else null } override fun onGetBounds(node: ApplicationRef): Bounds = DisplayMetrics.getDisplayBounds() @@ -31,7 +32,7 @@ object ApplicationRefDescriptor : ChainedDescriptor() { override fun onGetChildren(node: ApplicationRef): List { val children = mutableListOf() - val activeRoots = node.rootViews + val activeRoots = node.rootsResolver.rootViews() val added = mutableSetOf() for (activity: Activity in node.activitiesStack) { 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 2a719f5f9..dd298da8d 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 @@ -40,9 +40,9 @@ class ApplicationTreeObserver(val context: Context) : TreeObserver