From dc28e2ad4570bb14b0b972d35415dbe8c16cc026 Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Fri, 19 Aug 2022 05:08:49 -0700 Subject: [PATCH] ApplicationRef Summary: Introduce ApplicationRef which holds a reference to Application. It exposes a few utility methods to get view roots and activities. ApplicationInspector has a few template methods for useful things which are not used in this diff and most likely will either be moved/deleted/changed on the final implementation. Reviewed By: LukeDefeo Differential Revision: D38829523 fbshipit-source-id: b8aeb133dceb3af42b5f7d6851ef531d35fc7d69 --- .../uidebugger/core/ApplicationInspector.kt | 66 +++++++++++++ .../plugins/uidebugger/core/ApplicationRef.kt | 99 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.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 new file mode 100644 index 00000000000..1578b2481db --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt @@ -0,0 +1,66 @@ +/* + * 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.commands.Context +import java.util.List + +class ApplicationInspector(val context: Context) { + + fun traverse(view: View) { + val inspector = + LayoutVisitor.create( + object : LayoutVisitor.Visitor { + override fun visit(view: View) {} + }) + inspector.traverse(view) + } + + 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: List) {} + }) + + val activeRoots = rootResolver.listActiveRootViews() + activeRoots?.let { roots -> + for (root: RootViewResolver.RootView in roots) { + traverse(root.view) + } + } + } +} 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 new file mode 100644 index 00000000000..04d66daf84b --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt @@ -0,0 +1,99 @@ +/* + * 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.app.Activity +import android.app.Application +import android.os.Bundle +import android.view.View +import java.lang.ref.WeakReference + +class ApplicationRef(val application: Application) : Application.ActivityLifecycleCallbacks { + interface ActivityStackChangedListener { + fun onActivityStackChanged(stack: List) + } + + interface ActivityDestroyedListener { + fun onActivityDestroyed(activity: Activity) + } + + private val rootsResolver: RootViewResolver + private val activities: MutableList> + private var activityStackChangedlistener: ActivityStackChangedListener? = null + private var activityDestroyedListener: ActivityDestroyedListener? = null + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + activities.add(WeakReference(activity)) + activityStackChangedlistener?.let { listener -> + listener.onActivityStackChanged(this.activitiesStack) + } + } + override fun onActivityStarted(activity: Activity) {} + override fun onActivityResumed(activity: Activity) {} + override fun onActivityPaused(activity: Activity) {} + override fun onActivityStopped(activity: Activity) {} + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} + override fun onActivityDestroyed(activity: Activity) { + val activityIterator: MutableIterator> = activities.iterator() + + while (activityIterator.hasNext()) { + if (activityIterator.next().get() === activity) { + activityIterator.remove() + } + } + + activityDestroyedListener?.let { listener -> listener.onActivityDestroyed(activity) } + + activityStackChangedlistener?.let { listener -> + listener.onActivityStackChanged(this.activitiesStack) + } + } + + fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) { + activityStackChangedlistener = listener + } + + fun setActivityDestroyedListener(listener: ActivityDestroyedListener?) { + activityDestroyedListener = listener + } + + val activitiesStack: List + get() { + val stack: MutableList = ArrayList(activities.size) + val activityIterator: MutableIterator> = activities.iterator() + while (activityIterator.hasNext()) { + val activity: Activity? = activityIterator.next().get() + if (activity == null) { + activityIterator.remove() + } else { + stack.add(activity) + } + } + return stack + } + + val rootViews: List + get() { + val roots = rootsResolver.listActiveRootViews() + roots?.let { roots -> + val viewRoots: MutableList = ArrayList(roots.size) + for (root in roots) { + viewRoots.add(root.view) + } + return viewRoots + } + + return emptyList() + } + + init { + rootsResolver = RootViewResolver() + application.registerActivityLifecycleCallbacks(this) + activities = ArrayList>() + } +}