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:
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 + ">";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user