Skip to content

Instantly share code, notes, and snippets.

@pyricau
Last active September 15, 2020 20:34
Show Gist options
  • Save pyricau/b729f6916d4c578d6d9ce1db261a433c to your computer and use it in GitHub Desktop.
Save pyricau/b729f6916d4c578d6d9ce1db261a433c to your computer and use it in GitHub Desktop.
import android.annotation.TargetApi
import android.os.Build.VERSION_CODES
import android.view.View
import android.view.Window
object WindowSpy {
/**
* Installs a listener for new [Window] instances. This should only be called once, any new call
* will uninstall the previous listener.
*
* Replaces WindowManagerGlobal.mViews in the WindowManagerGlobal singleton instances with an
* [ArrayList] subclass that overrides [java.util.ArrayList.add] to also notify [block], if
* the new root view is a DecorView that has a non null window.
*
* This is a no-op before API 19.
*/
@TargetApi(VERSION_CODES.LOLLIPOP)
fun onWindowAdded(block: (Window) -> Unit) {
try {
onWindowAddedThrowing(block)
} catch (ignored: Throwable) {
}
}
private fun onWindowAddedThrowing(block: (Window) -> Unit) {
val windowManagerGlobalClass = Class.forName("android.view.WindowManagerGlobal")
val windowManagerGlobalInstance =
windowManagerGlobalClass.getDeclaredMethod("getInstance").invoke(null)
val decorViewClass = try {
Class.forName("com.android.internal.policy.DecorView")
} catch (ignored: ClassNotFoundException) {
// In some later release of Android 6 / API 23 DecorView was moved out of PhoneWindow.
// https://cs.android.com/android/_/android/platform/frameworks/base/+
// /8804af2b63b0584034f7ec7d4dc701d06e6a8754
Class.forName("com.android.internal.policy.PhoneWindow\$DecorView")
}
val mWindowField = try {
decorViewClass.getDeclaredField("mWindow").apply { isAccessible = true }
} catch (ignored: NoSuchFieldException) {
// In Android 6 / API 23 PhoneWindow.DecorView became an inner static class. Before, it had
// a direct reference to its outer PhoneWindow class.
// https://cs.android.com/android/_/android/platform/frameworks/base/+
// /0daf2102a20d224edeb4ee45dd4ee91889ef3e0c
decorViewClass.getDeclaredField("this$0").apply { isAccessible = true }
}
val mViewsField = windowManagerGlobalClass.getDeclaredField("mViews").apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
val rootViews = mViewsField[windowManagerGlobalInstance] as ArrayList<View>
mViewsField[windowManagerGlobalInstance] = object : ArrayList<View>(rootViews) {
override fun add(element: View): Boolean {
// The Window class is actually not core to root views / the window manager, it's more of
// a convenience helper that is sometimes used.
// The only implementation of Window is PhoneWindow and its only ever used combined with
// DecorView. DecorView is used for activities and dialogs, and has a mWindow field. Toasts
// can have any view as their root view and don't use a window.
if (decorViewClass.isInstance(element)) {
(mWindowField[element] as Window?)?.let { block(it) }
}
return super.add(element)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment