diff options
author | Evan Rosky <erosky@google.com> | 2020-04-28 14:55:46 -0700 |
---|---|---|
committer | Evan Rosky <erosky@google.com> | 2020-04-28 15:00:21 -0700 |
commit | 89f367d39fa915b90ddcd45f6294c19d580e8426 (patch) | |
tree | deae80785b7a69ac0578b49f842a54693ae7e7d5 /cmds/uiautomator | |
parent | 046df72d9a8a7d5f6e6f255c313fc129db874f68 (diff) |
Add a window dump for uiautomator
There's no good way to dump all the windows and views that
uiautomator sees, so added one. Otherwise its a pain to
do multi-window debugging.
This could also possibly be used by various hierarchy
viewers that currently are only able to look at the active
window.
Bug: 151632128
Test: adb shell uiautomator dump --windows
Change-Id: I20b45507584e35ef5d725691b2ffb060bcfa18db
Diffstat (limited to 'cmds/uiautomator')
2 files changed, 122 insertions, 12 deletions
diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java index c35f7fc3fcc8..3b14be7327f7 100644 --- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java +++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java @@ -16,6 +16,7 @@ package com.android.commands.uiautomator; +import android.accessibilityservice.AccessibilityServiceInfo; import android.app.UiAutomation; import android.graphics.Point; import android.hardware.display.DisplayManagerGlobal; @@ -61,11 +62,14 @@ public class DumpCommand extends Command { public void run(String[] args) { File dumpFile = DEFAULT_DUMP_FILE; boolean verboseMode = true; + boolean allWindows = false; for (String arg : args) { if (arg.equals("--compressed")) verboseMode = false; - else if (!arg.startsWith("-")) { + else if (arg.equals("--windows")) { + allWindows = true; + } else if (!arg.startsWith("-")) { dumpFile = new File(arg); } } @@ -85,18 +89,28 @@ public class DumpCommand extends Command { try { UiAutomation uiAutomation = automationWrapper.getUiAutomation(); uiAutomation.waitForIdle(1000, 1000 * 10); - AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow(); - if (info == null) { - System.err.println("ERROR: null root node returned by UiTestAutomationBridge."); - return; - } + if (allWindows) { + AccessibilityServiceInfo info = uiAutomation.getServiceInfo(); + info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; + uiAutomation.setServiceInfo(info); + AccessibilityNodeInfoDumper.dumpWindowsToFile( + uiAutomation.getWindowsOnAllDisplays(), dumpFile, + DisplayManagerGlobal.getInstance()); + } else { + AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow(); + if (info == null) { + System.err.println("ERROR: null root node returned by UiTestAutomationBridge."); + return; + } - Display display = - DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); - int rotation = display.getRotation(); - Point size = new Point(); - display.getSize(size); - AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y); + Display display = + DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); + int rotation = display.getRotation(); + Point size = new Point(); + display.getSize(size); + AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, + size.y); + } } catch (TimeoutException re) { System.err.println("ERROR: could not get idle state."); return; diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java index 63c51e84d74a..ab198b319e27 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java @@ -16,11 +16,17 @@ package com.android.uiautomator.core; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.display.DisplayManagerGlobal; import android.os.Environment; import android.os.SystemClock; import android.util.Log; +import android.util.SparseArray; import android.util.Xml; +import android.view.Display; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import org.xmlpull.v1.XmlSerializer; @@ -28,6 +34,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.StringWriter; +import java.util.List; /** * @@ -98,6 +105,95 @@ public class AccessibilityNodeInfoDumper { Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms"); } + /** + * Using {@link AccessibilityWindowInfo} this method will dump some window information and + * then walk the layout hierarchy of it's + * and generates an xml dump to the location specified by <code>dumpFile</code> + * @param allWindows All windows indexed by display-id. + * @param dumpFile The file to dump to. + */ + public static void dumpWindowsToFile(SparseArray<List<AccessibilityWindowInfo>> allWindows, + File dumpFile, DisplayManagerGlobal displayManager) { + if (allWindows.size() == 0) { + return; + } + final long startTime = SystemClock.uptimeMillis(); + try { + FileWriter writer = new FileWriter(dumpFile); + XmlSerializer serializer = Xml.newSerializer(); + StringWriter stringWriter = new StringWriter(); + serializer.setOutput(stringWriter); + serializer.startDocument("UTF-8", true); + serializer.startTag("", "displays"); + for (int d = 0, nd = allWindows.size(); d < nd; ++d) { + int displayId = allWindows.keyAt(d); + Display display = displayManager.getRealDisplay(displayId); + if (display == null) { + continue; + } + final List<AccessibilityWindowInfo> windows = allWindows.valueAt(d); + if (windows.isEmpty()) { + continue; + } + serializer.startTag("", "display"); + serializer.attribute("", "id", Integer.toString(displayId)); + int rotation = display.getRotation(); + Point size = new Point(); + display.getSize(size); + for (int i = 0, n = windows.size(); i < n; ++i) { + dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation); + } + serializer.endTag("", "display"); + } + serializer.endTag("", "displays"); + serializer.endDocument(); + writer.write(stringWriter.toString()); + writer.close(); + } catch (IOException e) { + Log.e(LOGTAG, "failed to dump window to file", e); + } + final long endTime = SystemClock.uptimeMillis(); + Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms"); + } + + private static void dumpWindowRec(AccessibilityWindowInfo winfo, XmlSerializer serializer, + int index, int width, int height, int rotation) throws IOException { + serializer.startTag("", "window"); + serializer.attribute("", "index", Integer.toString(index)); + final CharSequence title = winfo.getTitle(); + serializer.attribute("", "title", title != null ? title.toString() : ""); + final Rect tmpBounds = new Rect(); + winfo.getBoundsInScreen(tmpBounds); + serializer.attribute("", "bounds", tmpBounds.toShortString()); + serializer.attribute("", "active", Boolean.toString(winfo.isActive())); + serializer.attribute("", "focused", Boolean.toString(winfo.isFocused())); + serializer.attribute("", "accessibility-focused", + Boolean.toString(winfo.isAccessibilityFocused())); + serializer.attribute("", "id", Integer.toString(winfo.getId())); + serializer.attribute("", "layer", Integer.toString(winfo.getLayer())); + serializer.attribute("", "type", AccessibilityWindowInfo.typeToString(winfo.getType())); + int count = winfo.getChildCount(); + for (int i = 0; i < count; ++i) { + AccessibilityWindowInfo child = winfo.getChild(i); + if (child == null) { + Log.i(LOGTAG, String.format("Null window child %d/%d, parent: %s", i, count, + winfo.getTitle())); + continue; + } + dumpWindowRec(child, serializer, i, width, height, rotation); + child.recycle(); + } + AccessibilityNodeInfo root = winfo.getRoot(); + if (root != null) { + serializer.startTag("", "hierarchy"); + serializer.attribute("", "rotation", Integer.toString(rotation)); + dumpNodeRec(root, serializer, 0, width, height); + root.recycle(); + serializer.endTag("", "hierarchy"); + } + serializer.endTag("", "window"); + } + private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,int index, int width, int height) throws IOException { serializer.startTag("", "node"); |