-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
228 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
203 changes: 203 additions & 0 deletions
203
...sso/visual/src/main/java/androidx/test/uiautomator/CustomAccessibilityNodeInfoDumper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
/* | ||
* Copyright (C) 2012 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package androidx.test.uiautomator; | ||
|
||
import android.util.Log; | ||
import android.util.Xml; | ||
import android.view.accessibility.AccessibilityNodeInfo; | ||
|
||
import org.xmlpull.v1.XmlSerializer; | ||
|
||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
|
||
/** | ||
* Slightly modified version of {@link androidx.test.uiautomator.AccessibilityNodeInfoDumper} | ||
* to include non-invisible elements in window hierarchy dump | ||
*/ | ||
public class CustomAccessibilityNodeInfoDumper { | ||
|
||
private static final String LOGTAG = CustomAccessibilityNodeInfoDumper.class.getSimpleName(); | ||
private static final String[] NAF_EXCLUDED_CLASSES = new String[]{ | ||
android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(), | ||
android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName() | ||
}; | ||
|
||
public static void dumpWindowHierarchy(UiDevice device, OutputStream out) throws IOException { | ||
XmlSerializer serializer = Xml.newSerializer(); | ||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); | ||
serializer.setOutput(out, "UTF-8"); | ||
|
||
serializer.startDocument("UTF-8", true); | ||
serializer.startTag("", "hierarchy"); // TODO(allenhair): Should we use a namespace? | ||
serializer.attribute("", "rotation", Integer.toString(device.getDisplayRotation())); | ||
|
||
for (AccessibilityNodeInfo root : device.getWindowRoots()) { | ||
dumpNodeRec(root, serializer, 0, device.getDisplayWidth(), device.getDisplayHeight()); | ||
} | ||
|
||
serializer.endTag("", "hierarchy"); | ||
serializer.endDocument(); | ||
} | ||
|
||
private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer, int index, | ||
int width, int height) throws IOException { | ||
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("", "visible-to-user", Boolean.toString(node.isVisibleToUser())); | ||
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) { | ||
dumpNodeRec(child, serializer, i, width, height); | ||
child.recycle(); | ||
} else { | ||
Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s", | ||
i, count, node)); | ||
} | ||
} | ||
serializer.endTag("", "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. | ||
* | ||
* @param node | ||
* @return true if node is excluded. | ||
*/ | ||
private static boolean nafExcludedClass(AccessibilityNodeInfo node) { | ||
String className = safeCharSeqToString(node.getClassName()); | ||
for (String excludedClassName : NAF_EXCLUDED_CLASSES) { | ||
if (className.endsWith(excludedClassName)) | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* 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) | ||
* | ||
* @param node | ||
* @return false if a node fails the check, true if all is OK | ||
*/ | ||
private static boolean nafCheck(AccessibilityNodeInfo node) { | ||
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); | ||
} | ||
|
||
/** | ||
* 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. | ||
* | ||
* @param node | ||
* @return false if node fails the check. | ||
*/ | ||
private static boolean childNafCheck(AccessibilityNodeInfo node) { | ||
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 String safeCharSeqToString(CharSequence cs) { | ||
if (cs == null) | ||
return ""; | ||
else { | ||
return stripInvalidXMLChars(cs); | ||
} | ||
} | ||
|
||
private static String stripInvalidXMLChars(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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters