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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
29e0794ff4
commit
00334acb2b
@@ -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<Activity>
|
||||
get() {
|
||||
return ActivityTracker.activitiesStack
|
||||
}
|
||||
|
||||
val rootViews: List<View>
|
||||
get() {
|
||||
val activeRootViews = rootsResolver.listActiveRootViews()
|
||||
activeRootViews?.let { roots ->
|
||||
val viewRoots: MutableList<View> = ArrayList(roots.size)
|
||||
for (root in roots) {
|
||||
viewRoots.add(root.view)
|
||||
}
|
||||
return viewRoots
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<View>)
|
||||
}
|
||||
|
||||
class ObservableArrayList : ArrayList<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()
|
||||
}
|
||||
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<View> {
|
||||
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<View>
|
||||
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<View>() {
|
||||
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<View>
|
||||
|
||||
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<RootView>? {
|
||||
if (!initialized) {
|
||||
initialize()
|
||||
}
|
||||
if (null == windowManagerObj) {
|
||||
return null
|
||||
}
|
||||
if (null == viewsField) {
|
||||
return null
|
||||
}
|
||||
if (null == paramsField) {
|
||||
return null
|
||||
}
|
||||
var maybeViews: List<View>? = null
|
||||
var maybeParams: List<WindowManager.LayoutParams>? = null
|
||||
try {
|
||||
viewsField?.let { field ->
|
||||
maybeViews =
|
||||
if (Build.VERSION.SDK_INT < 19) {
|
||||
@Suppress("unchecked_cast") val arr = field[windowManagerObj] as Array<View>
|
||||
arr.toList()
|
||||
} else {
|
||||
@Suppress("unchecked_cast")
|
||||
field[windowManagerObj] as List<View>
|
||||
}
|
||||
}
|
||||
|
||||
paramsField?.let { field ->
|
||||
maybeParams =
|
||||
if (Build.VERSION.SDK_INT < 19) {
|
||||
@Suppress("unchecked_cast")
|
||||
val arr = field[windowManagerObj] as Array<WindowManager.LayoutParams>
|
||||
arr.toList()
|
||||
} else {
|
||||
@Suppress("unchecked_cast")
|
||||
field[windowManagerObj] as List<WindowManager.LayoutParams>
|
||||
}
|
||||
}
|
||||
} catch (re: RuntimeException) {
|
||||
return null
|
||||
} catch (iae: IllegalAccessException) {
|
||||
return null
|
||||
}
|
||||
|
||||
val roots = mutableListOf<RootView>()
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ import com.facebook.flipper.plugins.uidebugger.util.DisplayMetrics
|
||||
object ApplicationRefDescriptor : ChainedDescriptor<ApplicationRef>() {
|
||||
|
||||
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<ApplicationRef>() {
|
||||
override fun onGetChildren(node: ApplicationRef): List<Any> {
|
||||
val children = mutableListOf<Any>()
|
||||
|
||||
val activeRoots = node.rootViews
|
||||
val activeRoots = node.rootsResolver.rootViews()
|
||||
|
||||
val added = mutableSetOf<View>()
|
||||
for (activity: Activity in node.activitiesStack) {
|
||||
|
||||
@@ -40,9 +40,9 @@ class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRe
|
||||
}
|
||||
context.applicationRef.rootsResolver.attachListener(rootViewListener)
|
||||
// On subscribe, trigger a traversal on whatever roots we have
|
||||
rootViewListener.onRootViewsChanged(applicationRef.rootViews)
|
||||
rootViewListener.onRootViewsChanged(applicationRef.rootsResolver.rootViews())
|
||||
|
||||
Log.i(LogTag, "${context.applicationRef.rootViews.size} root views")
|
||||
Log.i(LogTag, "${context.applicationRef.rootsResolver.rootViews().size} root views")
|
||||
Log.i(LogTag, "${context.applicationRef.activitiesStack.size} activities")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user