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
This commit is contained in:
Evan Rosky
2020-04-28 14:55:46 -07:00
parent 046df72d9a
commit 89f367d39f
3 changed files with 127 additions and 14 deletions

View File

@@ -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;

View File

@@ -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");

View File

@@ -752,7 +752,10 @@ public final class AccessibilityWindowInfo implements Parcelable {
}
}
private static String typeToString(int type) {
/**
* @hide
*/
public static String typeToString(int type) {
switch (type) {
case TYPE_APPLICATION: {
return "TYPE_APPLICATION";
@@ -770,7 +773,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
return "TYPE_SPLIT_SCREEN_DIVIDER";
}
default:
return "<UNKNOWN>";
return "<UNKNOWN:" + type + ">";
}
}