Last active
September 15, 2020 20:34
-
-
Save pyricau/b729f6916d4c578d6d9ce1db261a433c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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