Skip to content
This repository has been archived by the owner on Nov 22, 2024. It is now read-only.

Commit

Permalink
Tree observer
Browse files Browse the repository at this point in the history
Summary:
Added concept of a tree observer which is responsible for listening to the changes for a portion of the UI tree. This structure nests so Tree observers can hold child tree observers which emit events on a different cadence. This structure should allow us to incorporate different UI frameworks down the road as well as native android views.

We push the tree updates from the tree observers onto a channel and setup a coroutine to consume this channel, serialize and send down the wire.

Reviewed By: lblasa

Differential Revision: D39276681

fbshipit-source-id: a4bc23b3578a8a10b57dd11fe88b273e1ce09ad8
  • Loading branch information
LukeDefeo authored and facebook-github-bot committed Sep 12, 2022
1 parent c76c993 commit 9a270cd
Show file tree
Hide file tree
Showing 16 changed files with 480 additions and 53 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ website/src/embedded-pages/docs/plugins/

# Logs
**/*/flipper-server-log.out

*.salive
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ android {
compileOnly deps.proguardAnnotations
implementation deps.kotlinStdLibrary

implementation deps.kotlinCoroutinesAndroid
implementation deps.openssl
implementation deps.fbjni
implementation deps.soloader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,34 @@
package com.facebook.flipper.plugins.uidebugger

import android.app.Application
import android.util.Log
import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.ConnectionRef
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.NativeScanScheduler
import com.facebook.flipper.plugins.uidebugger.core.*
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.model.InitEvent
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import com.facebook.flipper.plugins.uidebugger.scheduler.Scheduler
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json

val LogTag = "FlipperUIDebugger"
const val LogTag = "FlipperUIDebugger"

class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin {

private val context: Context = Context(ApplicationRef(application), ConnectionRef(null))
private val context: Context =
Context(
ApplicationRef(application),
ConnectionRef(null),
DescriptorRegister.withDefaults(),
TreeObserverFactory.withDefaults())

private val nativeScanScheduler = Scheduler(NativeScanScheduler(context))

init {
Log.i(LogTag, "Initializing UI Debugger")
}

override fun getId(): String {
return "ui-debugger"
}
Expand All @@ -42,16 +52,19 @@ class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin {
Json.encodeToString(
InitEvent.serializer(), InitEvent(rootDescriptor.getId(context.applicationRef))))

nativeScanScheduler.start()
context.treeObserverManager.start()
// nativeScanScheduler.start()
}

@Throws(Exception::class)
override fun onDisconnect() {
this.context.connectionRef.connection = null
Log.e(LogTag, "disconnected")

this.nativeScanScheduler.stop()
}

override fun runInBackground(): Boolean {
return false
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ open class EnumMapping<T>(val mapping: Map<String, T>) {
if (entry != null) {
return entry.key
} else {
Log.w(
Log.d(
LogTag,
"Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}")
return NoMapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,19 @@ import java.lang.ref.WeakReference

class ApplicationRef(val application: Application) : Application.ActivityLifecycleCallbacks {
interface ActivityStackChangedListener {
fun onActivityAdded(activity: Activity, stack: List<Activity>)
fun onActivityStackChanged(stack: List<Activity>)
}

interface ActivityDestroyedListener {
fun onActivityDestroyed(activity: Activity)
fun onActivityDestroyed(activity: Activity, stack: List<Activity>)
}

private val rootsResolver: RootViewResolver
private val activities: MutableList<WeakReference<Activity>>
private var activityStackChangedlistener: ActivityStackChangedListener? = null
private var activityDestroyedListener: ActivityDestroyedListener? = null

override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activities.add(WeakReference<Activity>(activity))
activityStackChangedlistener?.let { listener ->
listener.onActivityStackChanged(this.activitiesStack)
}
activityStackChangedlistener?.onActivityAdded(activity, this.activitiesStack)
activityStackChangedlistener?.onActivityStackChanged(this.activitiesStack)
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
Expand All @@ -47,21 +43,14 @@ class ApplicationRef(val application: Application) : Application.ActivityLifecyc
}
}

activityDestroyedListener?.let { listener -> listener.onActivityDestroyed(activity) }

activityStackChangedlistener?.let { listener ->
listener.onActivityStackChanged(this.activitiesStack)
}
activityStackChangedlistener?.onActivityDestroyed(activity, this.activitiesStack)
activityStackChangedlistener?.onActivityStackChanged(this.activitiesStack)
}

fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) {
activityStackChangedlistener = listener
}

fun setActivityDestroyedListener(listener: ActivityDestroyedListener?) {
activityDestroyedListener = listener
}

val activitiesStack: List<Activity>
get() {
val stack: MutableList<Activity> = ArrayList<Activity>(activities.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@
package com.facebook.flipper.plugins.uidebugger.core

import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.plugins.uidebugger.PartialLayoutTraversal
import com.facebook.flipper.plugins.uidebugger.TreeObserverManager
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory

data class Context(
val applicationRef: ApplicationRef,
val connectionRef: ConnectionRef,
val descriptorRegister: DescriptorRegister = DescriptorRegister.withDefaults()
)
val descriptorRegister: DescriptorRegister,
val observerFactory: TreeObserverFactory,
) {
val layoutTraversal: PartialLayoutTraversal =
PartialLayoutTraversal(descriptorRegister, observerFactory)

val treeObserverManager = TreeObserverManager(this)
}

data class ConnectionRef(var connection: FlipperConnection?)
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task<ScanResult> {
result.txId,
result.scanStart,
result.scanEnd,
result.scanEnd,
serializationEnd,
socketEnd,
result.nodes.size)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver

class ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
val rootResolver = RootViewResolver()

override fun onInit() {}
override fun onGetActiveChild(node: ApplicationRef): Any? {
Expand All @@ -32,22 +30,8 @@ class ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
}

override fun onGetChildren(applicationRef: ApplicationRef, children: MutableList<Any>) {
val activeRoots = rootResolver.listActiveRootViews()

activeRoots?.let { roots ->
for (root: RootViewResolver.RootView in roots) {
var added = false
for (activity: Activity in applicationRef.activitiesStack) {
if (activity.window.decorView == root.view) {
children.add(activity)
added = true
break
}
}
if (!added) {
children.add(root.view)
}
}
for (activity: Activity in applicationRef.activitiesStack) {
children.add(activity)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ data class InitEvent(val rootId: String) {
}
}

@kotlinx.serialization.Serializable
data class SubtreeUpdateEvent(val txId: Long, val observerType: String, val nodes: List<Node>) {
companion object {
const val name = "subtreeUpdate"
}
}

@kotlinx.serialization.Serializable
data class NativeScanEvent(val txId: Long, val nodes: List<Node>) {
companion object {
Expand All @@ -26,7 +33,8 @@ data class NativeScanEvent(val txId: Long, val nodes: List<Node>) {
data class PerfStatsEvent(
val txId: Long,
val start: Long,
val scanComplete: Long,
val traversalComplete: Long,
val queuingComplete: Long,
val serializationComplete: Long,
val socketComplete: Long,
val nodesCount: Int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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.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

/**
* responsible for observing the activity stack and managing the subscription to the top most
* content view (decor view)
*/
class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRef>() {

override fun subscribe(node: Any) {
Log.i(LogTag, "subscribing to application / activity changes")

val applicationRef = node as ApplicationRef

val addRemoveListener =
object : ApplicationRef.ActivityStackChangedListener {

override fun onActivityAdded(activity: Activity, stack: List<Activity>) {
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 onActivityStackChanged(stack: List<Activity>) {}

override fun onActivityDestroyed(activity: Activity, stack: List<Activity>) {
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")
}
}

context.applicationRef.setActivityStackChangedListener(addRemoveListener)

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() {
context.applicationRef.setActivityStackChangedListener(null)
}
}
Loading

0 comments on commit 9a270cd

Please sign in to comment.