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