下面列出了android.view.accessibility.AccessibilityNodeInfo#isVisibleToUser ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
/**获得红包详情页面打开节点*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public static AccessibilityNodeInfo getWechatRedEnvelopeOpenNode(AccessibilityNodeInfo info) {
if (info == null)
return null;
List<AccessibilityNodeInfo> list = info.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/b2c");
AccessibilityNodeInfo tempNode=null;
for(int i=0;i<list.size();i++){
tempNode=list.get(i);
LogUtil.d("e2ee"+tempNode.isVisibleToUser()+"-"+tempNode.isEnabled());
if ("android.widget.Button".equals(tempNode.getClassName())&&tempNode.isVisibleToUser()){
return tempNode;
}
}
return null;
}
private boolean matchNode(AccessibilityNodeInfo node) {
String clsName = node.getClassName().toString();
Class EDITTEXT, B, WEBVIEW;
boolean matchedEditText = false;
boolean matchedWebView = false;
try {
B = Class.forName(clsName, false, this.getClass().getClassLoader());
EDITTEXT = Class.forName(EditText.class.getCanonicalName(), false, this.getClass().getClassLoader());
matchedEditText = EDITTEXT.isAssignableFrom(B);
WEBVIEW = Class.forName(WebView.class.getCanonicalName(), false, this.getClass().getClassLoader());
matchedWebView = WEBVIEW.isAssignableFrom(B);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return node.isClickable() && node.isEnabled() && node.isVisibleToUser() && !node.isCheckable() && !matchedEditText && !matchedWebView;
}
private List<UiAutomationElement> buildChildren(AccessibilityNodeInfo node) {
final int childCount = node.getChildCount();
if (childCount == 0 || getDepth() >= MAX_DEPTH) {
if (getDepth() >= MAX_DEPTH) {
Logger.warn(String.format("Skipping building children of '%s' because the maximum " +
"recursion depth (%s) has been reached", node, MAX_DEPTH));
}
return Collections.emptyList();
}
List<UiAutomationElement> children = new ArrayList<>(childCount);
boolean areInvisibleElementsAllowed = AppiumUIA2Driver
.getInstance()
.getSessionOrThrow()
.getCapability(ALLOW_INVISIBLE_ELEMENTS.toString(), false);
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo child = node.getChild(i);
//Ignore if element is not visible on the screen
if (child != null && (child.isVisibleToUser() || areInvisibleElementsAllowed)) {
children.add(getOrCreateElement(child, i, getDepth() + 1));
}
}
return children;
}
private void initAccessibilityNodeInfo(AccessibilityNodeInfo info) {
this.className = StringUtil.nonNullString(info.getClassName());
this.packageName = StringUtil.nonNullString(info.getPackageName());
this.resourceId = info.getViewIdResourceName();
this.text = StringUtil.nonNullString(info.getText());
this.description = StringUtil.nonNullString(info.getContentDescription());
Rect rect = new Rect();
info.getBoundsInScreen(rect);
this.nodeBound = rect;
this.isScrollable = info.isScrollable();
this.visible = info.isVisibleToUser();
this.isClickable = info.isClickable();
this.isFocusable = info.isFocusable();
this.isEditable = info.isEditable();
}
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("", "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 @Nullable AccessibilityNodeInfoCompat getFocusedNode(
AccessibilityService service, boolean fallbackOnRoot) {
AccessibilityNodeInfo root = service.getRootInActiveWindow();
AccessibilityNodeInfo focused = null;
try {
AccessibilityNodeInfo ret = null;
if (root != null) {
focused = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
if (focused != null && focused.isVisibleToUser()) {
ret = focused;
focused = null;
} else if (fallbackOnRoot) {
ret = root;
root = null;
}
} else {
LogUtils.e(TAG, "No current window root");
}
if (ret != null) {
return AccessibilityNodeInfoUtils.toCompat(ret);
}
} finally {
if (root != null) {
root.recycle();
}
if (focused != null) {
focused.recycle();
}
}
return null;
}
public static void dumpNode(AccessibilityNodeInfo info, Node root,
int index, int width, int height) {
root.sourceId = info.getSourceNodeId();
root.index = index;
root.text = safeCharSeqToString(info.getText());
root.res = safeCharSeqToString(info.getViewIdResourceName());
root.clazz = safeCharSeqToString(info.getClassName());
root.pkg = safeCharSeqToString(info.getPackageName());
root.desc = safeCharSeqToString(info.getContentDescription());
root.checkable = info.isCheckable();
root.checked = info.isChecked();
root.clickable = info.isClickable();
root.enabled = info.isEnabled();
root.focusable = info.isFocusable();
root.focused = info.isFocused();
root.scrollable = info.isScrollable();
root.longClickable = info.isLongClickable();
root.password = info.isPassword();
root.selected = info.isSelected();
android.graphics.Rect r = AccessibilityNodeInfoHelper
.getVisibleBoundsInScreen(info, width, height);
root.rect = new Rect(r.left, r.top, r.right, r.bottom);
root.children = new ArrayList<Node>();
int count = info.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = info.getChild(i);
if (child != null) {
if (child.isVisibleToUser()) {
Node childNode = new Node();
dumpNode(child, childNode, i, width, height);
root.children.add(childNode);
child.recycle();
}
}
}
}
/**
* A snapshot of all attributes is taken at construction. The attributes of a
* {@code UiAutomationElement} instance are immutable. If the underlying
* {@link AccessibilityNodeInfo} is updated, a new {@code UiAutomationElement}
* instance will be created in
*/
protected UiAutomationElement( AccessibilityNodeInfo node,
UiAutomationElement parent, int index) {
this.node = node;
this.parent = parent;
Map<Attribute, Object> attribs = new EnumMap<Attribute, Object>(Attribute.class);
put(attribs, Attribute.INDEX, index);
put(attribs, Attribute.PACKAGE, charSequenceToString(node.getPackageName()));
put(attribs, Attribute.CLASS, charSequenceToString(node.getClassName()));
put(attribs, Attribute.TEXT, charSequenceToString(node.getText()));
put(attribs, Attribute.CONTENT_DESC, charSequenceToString(node.getContentDescription()));
put(attribs, Attribute.RESOURCE_ID, charSequenceToString(node.getViewIdResourceName()));
put(attribs, Attribute.CHECKABLE, node.isCheckable());
put(attribs, Attribute.CHECKED, node.isChecked());
put(attribs, Attribute.CLICKABLE, node.isClickable());
put(attribs, Attribute.ENABLED, node.isEnabled());
put(attribs, Attribute.FOCUSABLE, node.isFocusable());
put(attribs, Attribute.FOCUSED, node.isFocused());
put(attribs, Attribute.LONG_CLICKABLE, node.isLongClickable());
put(attribs, Attribute.PASSWORD, node.isPassword());
put(attribs, Attribute.SCROLLABLE, node.isScrollable());
if (node.getTextSelectionStart() >= 0
&& node.getTextSelectionStart() != node.getTextSelectionEnd()) {
attribs.put(Attribute.SELECTION_START, node.getTextSelectionStart());
attribs.put(Attribute.SELECTION_END, node.getTextSelectionEnd());
}
put(attribs, Attribute.SELECTED, node.isSelected());
put(attribs, Attribute.BOUNDS, getBounds(node));
attributes = Collections.unmodifiableMap(attribs);
// Order matters as getVisibleBounds depends on visible
visible = node.isVisibleToUser();
visibleBounds = getVisibleBounds(node);
List<UiAutomationElement> mutableChildren = buildChildren(node);
this.children = mutableChildren == null ? null : Collections.unmodifiableList(mutableChildren);
}
public static AccessibilityNodeInfoCompat getFocusedNode(
AccessibilityService service, boolean fallbackOnRoot) {
AccessibilityNodeInfo root =
service.getRootInActiveWindow();
AccessibilityNodeInfo focused = null;
try {
AccessibilityNodeInfo ret = null;
if (root != null) {
focused = root.findFocus(
AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
if (focused != null && focused.isVisibleToUser()) {
ret = focused;
focused = null;
} else if (fallbackOnRoot) {
ret = root;
root = null;
}
} else {
LogUtils.log(service, Log.ERROR, "No current window root");
}
if (ret != null) {
return new AccessibilityNodeInfoCompat(ret);
}
} finally {
if (root != null) {
root.recycle();
}
if (focused != null) {
focused.recycle();
}
}
return null;
}
private AccessibilityNodeInfo findNodeRegularRecursive(UiSelector subSelector,
AccessibilityNodeInfo fromNode, int index) {
if (subSelector.isMatchFor(fromNode, index)) {
if (DEBUG) {
Log.d(LOG_TAG, formatLog(String.format("%s",
subSelector.dumpToString(false))));
}
if(subSelector.isLeaf()) {
return fromNode;
}
if(subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A child selector without content");
return null; // there is an implementation fault
}
} else if(subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A parent selector without content");
return null; // there is an implementation fault
}
// the selector requested we start at this level from
// the parent node from the one we just matched
fromNode = fromNode.getParent();
if(fromNode == null)
return null;
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG, String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (VERBOSE)
Log.v(LOG_TAG,
String.format("Skipping invisible child: %s", childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodeRegularRecursive(subSelector, childNode, i);
if (retNode != null) {
return retNode;
}
}
return null;
}
private AccessibilityNodeInfo findNodePatternRecursive(
UiSelector subSelector, AccessibilityNodeInfo fromNode, int index,
UiSelector originalPattern) {
if (subSelector.isMatchFor(fromNode, index)) {
if(subSelector.isLeaf()) {
if(mPatternIndexer == 0) {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
return fromNode;
} else {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
mPatternCounter++; //count the pattern matched
mPatternIndexer--; //decrement until zero for the instance requested
// At a leaf selector within a group and still not instance matched
// then reset the selector to continue search from current position
// in the accessibility tree for the next pattern match up until the
// pattern index hits 0.
subSelector = originalPattern;
// starting over with next pattern search so reset to parent level
mLogIndent = mLogParentIndent;
}
} else {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
if(subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A child selector without content");
return null;
}
} else if(subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A parent selector without content");
return null;
}
fromNode = fromNode.getParent();
if(fromNode == null)
return null;
}
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG, String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (DEBUG)
Log.d(LOG_TAG,
String.format("Skipping invisible child: %s", childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodePatternRecursive(
subSelector, childNode, i, originalPattern);
if (retNode != null) {
return retNode;
}
}
return null;
}
/**
* Traverses the {@link AccessibilityNodeInfo} hierarchy starting at {@code node}, and returns
* a list of nodes which match the {@code selector} criteria. <br />
* <strong>Note:</strong> The caller must release each {@link AccessibilityNodeInfo} instance
* by calling {@link AccessibilityNodeInfo#recycle()} to avoid leaking resources.
*
* @param node The root of the {@link AccessibilityNodeInfo} subtree we are currently searching.
* @param index The index of this node underneath its parent.
* @param depth The distance between {@code node} and the root node.
* @param partialMatches The current list of {@link PartialMatch}es that need to be updated.
* @return A {@link List} of {@link AccessibilityNodeInfo}s that meet the search criteria.
*/
private List<AccessibilityNodeInfo> findMatches(AccessibilityNodeInfo node,
int index, int depth, SinglyLinkedList<PartialMatch> partialMatches) {
List<AccessibilityNodeInfo> ret = new ArrayList<AccessibilityNodeInfo>();
// Don't bother searching the subtree if it is not visible
// if (!node.isVisibleToUser()) {
// return ret;
// }
// Update partial matches
for (PartialMatch partialMatch : partialMatches) {
partialMatches = partialMatch.update(node, index, depth, partialMatches);
}
// Create a new match, if necessary
PartialMatch currentMatch = PartialMatch.accept(node, mSelector, index, depth);
if (currentMatch != null) {
partialMatches = SinglyLinkedList.prepend(currentMatch, partialMatches);
}
// For each child
int numChildren = node.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < numChildren; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child == null) {
if (!hasNullChild) {
Log.w(TAG, String.format("Node returned null child: %s", node.toString()));
}
hasNullChild = true;
Log.w(TAG, String.format("Skipping null child (%s of %s)", i, numChildren));
continue;
}
// Add any matches found under the child subtree
ret.addAll(findMatches(child, i, depth + 1, partialMatches));
// We're done with the child
child.recycle();
// Return early if we sound a match and shortCircuit is true
if (!ret.isEmpty() && mShortCircuit) {
return ret;
}
}
// Finalize match, if necessary
if (currentMatch != null && currentMatch.finalizeMatch() && node.isVisibleToUser()) {
ret.add(AccessibilityNodeInfo.obtain(node));
}
return ret;
}
Builder(int id, @Nullable ViewHierarchyElementAndroid parent, AccessibilityNodeInfo fromInfo) {
// Bookkeeping
this.id = id;
this.parentId = (parent != null) ? parent.getId() : null;
// API 18+ properties
this.resourceName = AT_18 ? fromInfo.getViewIdResourceName() : null;
this.editable = AT_18 ? fromInfo.isEditable() : null;
// API 16+ properties
this.visibleToUser = AT_16 ? fromInfo.isVisibleToUser() : null;
// API 21+ properties
if (AT_21) {
ImmutableList.Builder<ViewHierarchyActionAndroid> actionBuilder =
new ImmutableList.Builder<>();
actionBuilder.addAll(
Lists.transform(
fromInfo.getActionList(),
action -> ViewHierarchyActionAndroid.newBuilder(action).build()));
this.actionList = actionBuilder.build();
}
// API 24+ properties
this.drawingOrder = AT_24 ? fromInfo.getDrawingOrder() : null;
// API 29+ properties
this.hasTouchDelegate = AT_29 ? (fromInfo.getTouchDelegateInfo() != null) : null;
// Base properties
this.className = fromInfo.getClassName();
this.packageName = fromInfo.getPackageName();
this.accessibilityClassName = fromInfo.getClassName();
this.contentDescription = SpannableStringAndroid.valueOf(fromInfo.getContentDescription());
this.text = SpannableStringAndroid.valueOf(fromInfo.getText());
this.importantForAccessibility = true;
this.clickable = fromInfo.isClickable();
this.longClickable = fromInfo.isLongClickable();
this.focusable = fromInfo.isFocusable();
this.scrollable = fromInfo.isScrollable();
this.canScrollForward =
((fromInfo.getActions() & AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) != 0);
this.canScrollBackward =
((fromInfo.getActions() & AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) != 0);
this.checkable = fromInfo.isCheckable();
this.checked = fromInfo.isChecked();
this.touchDelegateBounds = new ArrayList<>(); // Populated after construction
android.graphics.Rect tempRect = new android.graphics.Rect();
fromInfo.getBoundsInScreen(tempRect);
this.boundsInScreen = new Rect(tempRect.left, tempRect.top, tempRect.right, tempRect.bottom);
this.nonclippedHeight = null;
this.nonclippedWidth = null;
this.textSize = null;
this.textColor = null;
this.backgroundDrawableColor = null;
this.typefaceStyle = null;
this.enabled = fromInfo.isEnabled();
}
private AccessibilityNodeInfo findNodeRegularRecursive(
UiSelector subSelector, AccessibilityNodeInfo fromNode, int index) {
if (subSelector.isMatchFor(fromNode, index)) {
if (DEBUG) {
Log.d(LOG_TAG,
formatLog(String.format("%s",
subSelector.dumpToString(false))));
}
if (subSelector.isLeaf()) {
return fromNode;
}
if (subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if (subSelector == null) {
Log.e(LOG_TAG, "Error: A child selector without content");
return null; // there is an implementation fault
}
} else if (subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if (subSelector == null) {
Log.e(LOG_TAG, "Error: A parent selector without content");
return null; // there is an implementation fault
}
// the selector requested we start at this level from
// the parent node from the one we just matched
fromNode = fromNode.getParent();
if (fromNode == null)
return null;
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG,
String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)",
i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG,
String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (VERBOSE)
Log.v(LOG_TAG, String.format(
"Skipping invisible child: %s",
childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodeRegularRecursive(
subSelector, childNode, i);
if (retNode != null) {
return retNode;
}
}
return null;
}
private AccessibilityNodeInfo findNodePatternRecursive(
UiSelector subSelector, AccessibilityNodeInfo fromNode, int index,
UiSelector originalPattern) {
if (subSelector.isMatchFor(fromNode, index)) {
if (subSelector.isLeaf()) {
if (mPatternIndexer == 0) {
if (DEBUG)
Log.d(LOG_TAG,
formatLog(String.format("%s",
subSelector.dumpToString(false))));
return fromNode;
} else {
if (DEBUG)
Log.d(LOG_TAG,
formatLog(String.format("%s",
subSelector.dumpToString(false))));
mPatternCounter++; // count the pattern matched
mPatternIndexer--; // decrement until zero for the instance
// requested
// At a leaf selector within a group and still not instance
// matched
// then reset the selector to continue search from current
// position
// in the accessibility tree for the next pattern match up
// until the
// pattern index hits 0.
subSelector = originalPattern;
// starting over with next pattern search so reset to parent
// level
mLogIndent = mLogParentIndent;
}
} else {
if (DEBUG)
Log.d(LOG_TAG,
formatLog(String.format("%s",
subSelector.dumpToString(false))));
if (subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if (subSelector == null) {
Log.e(LOG_TAG,
"Error: A child selector without content");
return null;
}
} else if (subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if (subSelector == null) {
Log.e(LOG_TAG,
"Error: A parent selector without content");
return null;
}
fromNode = fromNode.getParent();
if (fromNode == null)
return null;
}
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG,
String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)",
i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG,
String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (DEBUG)
Log.d(LOG_TAG, String.format(
"Skipping invisible child: %s",
childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodePatternRecursive(
subSelector, childNode, i, originalPattern);
if (retNode != null) {
return retNode;
}
}
return null;
}
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("", "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");
}
/**
* Traverses the {@link AccessibilityNodeInfo} hierarchy starting at {@code node}, and returns
* a list of nodes which match the {@code selector} criteria. <br />
* <strong>Note:</strong> The caller must release each {@link AccessibilityNodeInfo} instance
* by calling {@link AccessibilityNodeInfo#recycle()} to avoid leaking resources.
*
* @param node The root of the {@link AccessibilityNodeInfo} subtree we are currently searching.
* @param index The index of this node underneath its parent.
* @param depth The distance between {@code node} and the root node.
* @param partialMatches The current list of {@link PartialMatch}es that need to be updated.
* @return A {@link List} of {@link AccessibilityNodeInfo}s that meet the search criteria.
*/
private List<AccessibilityNodeInfo> findMatches(AccessibilityNodeInfo node,
int index, int depth, SinglyLinkedList<PartialMatch> partialMatches) {
List<AccessibilityNodeInfo> ret = new ArrayList<AccessibilityNodeInfo>();
// Don't bother searching the subtree if it is not visible
if (!node.isVisibleToUser()) {
return ret;
}
// Update partial matches
for (PartialMatch partialMatch : partialMatches) {
partialMatches = partialMatch.update(node, index, depth, partialMatches);
}
// Create a new match, if necessary
PartialMatch currentMatch = PartialMatch.accept(node, mSelector, index, depth);
if (currentMatch != null) {
partialMatches = SinglyLinkedList.prepend(currentMatch, partialMatches);
}
// For each child
int numChildren = node.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < numChildren; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child == null) {
if (!hasNullChild) {
Log.w(TAG, String.format("Node returned null child: %s", node.toString()));
}
hasNullChild = true;
Log.w(TAG, String.format("Skipping null child (%s of %s)", i, numChildren));
continue;
}
// Add any matches found under the child subtree
ret.addAll(findMatches(child, i, depth + 1, partialMatches));
// We're done with the child
child.recycle();
// Return early if we sound a match and shortCircuit is true
if (!ret.isEmpty() && mShortCircuit) {
return ret;
}
}
// Finalize match, if necessary
if (currentMatch != null && currentMatch.finalizeMatch()) {
ret.add(AccessibilityNodeInfo.obtain(node));
}
return ret;
}
private AccessibilityNodeInfo getScrollableNode(AccessibilityNodeInfo treeRoot) {
List<AccessibilityNodeInfo> ret = new ArrayList<AccessibilityNodeInfo>();
Queue<AccessibilityNodeInfo> Q = new LinkedList<AccessibilityNodeInfo>();
Q.add(treeRoot);
while (!Q.isEmpty()) {
AccessibilityNodeInfo node = Q.remove();
if (node == null) {
// Util.log("Processing NULL");
continue;
}
// Util.log("Processing " + node.getClassName());
// check current node
if (node.isVisibleToUser() && node.isEnabled() && node.isScrollable()) {
ret.add(node);
}
// add its children to queue
int childCnt = node.getChildCount();
if (childCnt > 0) {
for (int i = 0; i < childCnt; i++) {
AccessibilityNodeInfo child = node.getChild(i);
Q.add(child); // no need to check NULL, checked above
}
}
}
if (ret.isEmpty()) {
Util.log("No scrollable node found");
return null;
} else {
if (ret.size() > 1) {
Util.log("NOTE: Found " + ret.size() + " scrollable nodes.");
}
Util.log("Selected " + ret.get(0).getClassName());
return ret.get(0);
}
}
public static boolean isVisible(@Nullable AccessibilityNodeInfo nodeInfo) {
return nodeInfo != null && nodeInfo.isVisibleToUser();
}
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));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
}
serializer.attribute("", "text", safeCharSeqToString(node.getText()));
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()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
serializer.attribute("", "bounds", 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(String.format("Skipping invisible child: %s", child.toString()));
}
} else {
Log.i(String.format("Null child %d/%d, parent: %s", i, count, node.toString()));
}
}
serializer.endTag("", "node");
}