AccessibilityNodeInfoDumperpublic class AccessibilityNodeInfoDumper extends Object
Fields Summary |
---|
private static final String | LOGTAG | private static final String[] | NAF_EXCLUDED_CLASSES |
Methods Summary |
---|
private static boolean | childNafCheck(android.view.accessibility.AccessibilityNodeInfo node)This should be used when it's already determined that the node is NAF and
a further check of its children is in order. A node maybe a container
such as LinerLayout and may be set to be clickable but have no text or
content description but it is counting on one of its children to fulfill
the requirement for being accessibility friendly by having one or more of
its children fill the text or content-description. Such a combination is
considered by this dumper as acceptable for accessibility.
int childCount = node.getChildCount();
for (int x = 0; x < childCount; x++) {
AccessibilityNodeInfo childNode = node.getChild(x);
if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
|| !safeCharSeqToString(childNode.getText()).isEmpty())
return true;
if (childNafCheck(childNode))
return true;
}
return false;
| private static void | dumpNodeRec(android.view.accessibility.AccessibilityNodeInfo node, org.xmlpull.v1.XmlSerializer serializer, int index, int width, int height)
serializer.startTag("", "node");
if (!nafExcludedClass(node) && !nafCheck(node))
serializer.attribute("", "NAF", Boolean.toString(true));
serializer.attribute("", "index", Integer.toString(index));
serializer.attribute("", "text", safeCharSeqToString(node.getText()));
serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
serializer.attribute("", "password", Boolean.toString(node.isPassword()));
serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
node, width, height).toShortString());
int count = node.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
if (child.isVisibleToUser()) {
dumpNodeRec(child, serializer, i, width, height);
child.recycle();
} else {
Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
}
} else {
Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s",
i, count, node.toString()));
}
}
serializer.endTag("", "node");
| public static void | dumpWindowToFile(android.view.accessibility.AccessibilityNodeInfo root, int rotation, int width, int height)Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
and generates an xml dump into the /data/local/window_dump.xml
File baseDir = new File(Environment.getDataDirectory(), "local");
if (!baseDir.exists()) {
baseDir.mkdir();
baseDir.setExecutable(true, false);
baseDir.setWritable(true, false);
baseDir.setReadable(true, false);
}
dumpWindowToFile(root,
new File(new File(Environment.getDataDirectory(), "local"), "window_dump.xml"),
rotation, width, height);
| public static void | dumpWindowToFile(android.view.accessibility.AccessibilityNodeInfo root, java.io.File dumpFile, int rotation, int width, int height)Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
and generates an xml dump to the location specified by dumpFile
if (root == null) {
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("", "hierarchy");
serializer.attribute("", "rotation", Integer.toString(rotation));
dumpNodeRec(root, serializer, 0, width, height);
serializer.endTag("", "hierarchy");
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 boolean | nafCheck(android.view.accessibility.AccessibilityNodeInfo node)We're looking for UI controls that are enabled, clickable but have no
text nor content-description. Such controls configuration indicate an
interactive control is present in the UI and is most likely not
accessibility friendly. We refer to such controls here as NAF controls
(Not Accessibility Friendly)
boolean isNaf = node.isClickable() && node.isEnabled()
&& safeCharSeqToString(node.getContentDescription()).isEmpty()
&& safeCharSeqToString(node.getText()).isEmpty();
if (!isNaf)
return true;
// check children since sometimes the containing element is clickable
// and NAF but a child's text or description is available. Will assume
// such layout as fine.
return childNafCheck(node);
| private static boolean | nafExcludedClass(android.view.accessibility.AccessibilityNodeInfo node)The list of classes to exclude my not be complete. We're attempting to
only reduce noise from standard layout classes that may be falsely
configured to accept clicks and are also enabled.
String className = safeCharSeqToString(node.getClassName());
for(String excludedClassName : NAF_EXCLUDED_CLASSES) {
if(className.endsWith(excludedClassName))
return true;
}
return false;
| private static java.lang.String | safeCharSeqToString(java.lang.CharSequence cs)
if (cs == null)
return "";
else {
return stripInvalidXMLChars(cs);
}
| private static java.lang.String | stripInvalidXMLChars(java.lang.CharSequence cs)
StringBuffer ret = new StringBuffer();
char ch;
/* http://www.w3.org/TR/xml11/#charsets
[#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
[#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
[#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
[#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
[#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
[#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
[#x10FFFE-#x10FFFF].
*/
for (int i = 0; i < cs.length(); i++) {
ch = cs.charAt(i);
if((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
(ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
(ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
(ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
(ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
(ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
(ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
(ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
(ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
(ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
(ch >= 0x10FFFE && ch <= 0x10FFFF))
ret.append(".");
else
ret.append(ch);
}
return ret.toString();
|
|