23

Is it possible to programmatically enumerate all of the android.view.Windows, or decor views within an application?

Dialogs for example will both open in a new Window, separate from the main Activity window. I can locate them via Dialog.getWindow() but I'm not sure how I would do this with a built-in components such as the activity menu popup.

Is there any way, from an Application, Context, or the WindowManager, or something else, to enumerate the Windows associated with my app?

I can see all of my application's windows with adb dumpsys window, but I'm looking for a way to do this within my application without requiring root.

Sufian
  • 6,405
  • 16
  • 66
  • 120
Andrew Lavers
  • 8,023
  • 1
  • 33
  • 50
  • The Window for the activity menu popup would be the same as the Activity's, would Activity.getWindow() not work for you? – kassim Nov 01 '13 at 16:16
  • Unfortunately, no. I'm running the android FingerPaint sample (on 4.3) and after I tap the three dot menu button, and I can see in monitor that the popup is in its own window. I can also run "adb shell dumpsys window tokens" and see that the paint app indeed has two windows associated with it: allAppWindows=[Window{418f9ce8 u0 com.example.paintsample/com.example.paintsample.PaintSample}, Window{41a06d08 u0 PopupWindow:41ac65a0}] Same with Dialogs. – Andrew Lavers Nov 01 '13 at 18:22
  • Just curious, why would you need this info, or rather, what are you going to do with it once you have it? – Josh Dec 10 '14 at 16:25
  • 1
    @Josh I was writing a library for taking screenshots within a given app. Simply calling getDrawingCache on the root view is easy enough but in order to include things like overlaid dialogs which aren't the same window hierarchy I had to jump through this hurdle. – Andrew Lavers Dec 11 '14 at 16:48
  • Wow, good luck with that, sounds like a lot of pain :) – Josh Dec 11 '14 at 20:26

6 Answers6

23

I've found a way to do it via reflection on the @hidden WindowManagerGlobal. At least so far I know this works for android-18.

private void logRootViews() {
    try {
        Class wmgClass = Class.forName("android.view.WindowManagerGlobal");                        
        Object wmgInstnace = wmgClass.getMethod("getInstance").invoke(null, (Object[])null);

        Method getViewRootNames = wmgClass.getMethod("getViewRootNames"); 
        Method getRootView = wmgClass.getMethod("getRootView", String.class);
        String[] rootViewNames = (String[])getViewRootNames.invoke(wmgInstnace, (Object[])null);

        for(String viewName : rootViewNames) {
            View rootView = (View)getRootView.invoke(wmgInstnace, viewName);
            Log.i(TAG, "Found root view: " + viewName + ": " + rootView);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Output:

Found root view: com.example.paintsample/com.example.paintsample.PaintSample/android.view.ViewRootImpl@41deeff0: com.android.internal.policy.impl.PhoneWindow$DecorView{41dcc278 V.E..... R....... 0,0-768,1184}

Found root view: PopupWindow:42887380/android.view.ViewRootImpl@42891820: android.widget.PopupWindow$PopupViewContainer{42891450 V.E..... ........ 0,0-424,618}

Bounty is still up for grabs of course for anyone who can find a better way :)

Sufian
  • 6,405
  • 16
  • 66
  • 120
Andrew Lavers
  • 8,023
  • 1
  • 33
  • 50
14

I'm not entirely sure this answers the actual question, but it's a better way of getting all root views as suggested in the accepted answer.

As mentioned there, I've also managed to accomplish this only using reflection, except this code supports All versions from API 14 and above (I haven't checked below):

public static List<View> getWindowManagerViews() {
    try {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
                Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {

            // get the list from WindowManagerImpl.mViews
            Class wmiClass = Class.forName("android.view.WindowManagerImpl");
            Object wmiInstance = wmiClass.getMethod("getDefault").invoke(null);

            return viewsFromWM(wmiClass, wmiInstance);

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

            // get the list from WindowManagerGlobal.mViews
            Class wmgClass = Class.forName("android.view.WindowManagerGlobal");
            Object wmgInstance = wmgClass.getMethod("getInstance").invoke(null);

            return viewsFromWM(wmgClass, wmgInstance);
        }

    } catch (Exception e) {
        e.printStackTrace();
    }

    return new ArrayList<View>();
}

private static List<View> viewsFromWM(Class wmClass, Object wmInstance) throws Exception {

    Field viewsField = wmClass.getDeclaredField("mViews");
    viewsField.setAccessible(true);
    Object views = viewsField.get(wmInstance);

    if (views instanceof List) {
        return (List<View>) viewsField.get(wmInstance);
    } else if (views instanceof View[]) {
        return Arrays.asList((View[])viewsField.get(wmInstance));
    }

    return new ArrayList<View>();
}
Boris
  • 316
  • 2
  • 10
4

The Hierarchyviewer tool that comes with the SDK is worth its weight in gold.

Pedantic
  • 5,032
  • 2
  • 24
  • 37
  • It is, but I was looking for a way to do it programmatically (which I have, though I have to touch a hidden class to do it). Will fix the question a bit to make that clearer. – Andrew Lavers Nov 04 '13 at 21:42
  • Oh its okay - I didn't consider that you were using adb dumpsys on the device itself. – Pedantic Nov 04 '13 at 21:45
2

You can use @hidden APIs directly, without using reflection by accessing the class files and adding then to your android.jar in Android SDK. Here's how: https://devmaze.wordpress.com/2011/01/18/using-com-android-internal-part-1-introduction/

And the source for the android.jar for specific android version (19,21,22,23,24) can be grabbed here: https://github.com/anggrayudi/android-hidden-api

So then you can use the WindowManagerGlobal class directly to get all the root views like,

private void logRootViews() {
    WindowManagerGlobal windowManagerGlobal = WindowManagerGlobal.getInstance();
    String[] rootViewNames = windowManagerGlobal.getViewRootNames();

    for (String viewName : rootViewNames) {
        View rootView = windowManagerGlobal.getRootView(viewName);
        Log.i("", "Root view is: " + viewName + ": " + rootView);
        /*do what you want with the rootView*/
    }
}

Output:

Root view is: com.example.paintsample/com.example.paintsample.PaintSample/android.view.ViewRootImpl@41deeff0: com.android.internal.policy.impl.PhoneWindow$DecorView{41dcc278 V.E..... R....... 0,0-768,1184}

Root view is: PopupWindow:42887380/android.view.ViewRootImpl@42891820: android.widget.PopupWindow$PopupViewContainer{42891450 V.E..... ........ 0,0-424,618}

2

Those, who can afford minSdk 29 can use WindowInspector.getGlobalWindowViews(). Internally it refers that mViews property from WindowManagerGlobal, but available for public usage.

Viacheslav
  • 5,443
  • 1
  • 29
  • 36
1

Every solution is in Java above and I made the solution for Kotlin converting the Andrew Lavers's answer-

try
    {
        val wmgClass = Class.forName("android.view.WindowManagerGlobal")
        val wagInstance = wmgClass.getMethod("getInstance").invoke(null)
        val getViewRootNames: Method = wmgClass.getMethod("getViewRootNames")
        val getRootView: Method = wmgClass.getMethod("getRootView", String::class.java)
        val rootViewNames = getViewRootNames.invoke(wagInstance) as Array<String>
        for (viewName in rootViewNames) {
            val rootView = getRootView.invoke(wagInstance, viewName) as View

        }
    } catch (exception: java.lang.Exception {}
Gk Mohammad Emon
  • 6,084
  • 3
  • 42
  • 42