下面列出了android.view.accessibility.AccessibilityNodeInfo#recycle ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
private AccessibilityNodeInfo getLastDescendantDfs(
AccessibilityNodeInfo from) {
AccessibilityNodeInfo lastChild = getLastChild(from);
if (lastChild == null) {
return null;
}
while (true) {
AccessibilityNodeInfo lastGrandChild = getLastChild(lastChild);
if (lastGrandChild != null) {
lastChild.recycle();
lastChild = lastGrandChild;
} else {
break;
}
}
return lastChild;
}
/** Gets the window whose anchor equals the given node. */
public @Nullable AccessibilityWindowInfo getAnchoredWindow(
@Nullable AccessibilityNodeInfoCompat targetAnchor) {
if (!BuildVersionUtils.isAtLeastN() || targetAnchor == null) {
return null;
}
int windowCount = mWindows.size();
for (int i = 0; i < windowCount; ++i) {
AccessibilityWindowInfo window = mWindows.get(i);
if (window != null) {
AccessibilityNodeInfo anchor = window.getAnchor();
if (anchor != null) {
try {
if (anchor.equals(targetAnchor.unwrap())) {
return window;
}
} finally {
anchor.recycle();
}
}
}
}
return null;
}
public void handleLuckyMoneyReceivePage(AccessibilityNodeInfo node) {
if (node == null)
return;
LogUtil.d("handleLuckyMoneyReceivePage");
AccessibilityNodeInfo nodeDetail = RedEnvelopeHelper.getWechatRedEnvelopeOpenDetailNode(node);
LogUtil.d("nodeDetail="+nodeDetail);
if (nodeDetail != null) {// the red envelope already opened
// 红包已经被打开
if (SettingHelper.getREAutoMode())
ActivityHelper.goHome(this);
} else {
AccessibilityNodeInfo nodeOpen = RedEnvelopeHelper.getWechatRedEnvelopeOpenNode(node);
LogUtil.d("nodeOpen="+nodeOpen);
if (nodeOpen != null) {
nodeOpen.performAction(AccessibilityNodeInfo.ACTION_CLICK);
nodeOpen.recycle();
} else {// this page is loading red envelope data, no action
}
}
}
public synchronized void performActionOnFocusedNode(PerformFocusedNodeAction action) {
AccessibilityNodeInfo focusedNode = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
focusedNode = findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
}
//In case we're dealing with a webView, findFocus won't work and just remove the top frame,
//so check if the focused node makes sense and if not, fall back to findFocus_PreLollipop
if (focusedNode == null || !mTree.isAEditText(focusedNode)) {
focusedNode = findFocus_PreLollipop();
}
if (focusedNode == null) {
action.performActionWhenNothingFocused();
} else {
action.performAction(focusedNode);
focusedNode.recycle();
}
}
public static AccessibilityNodeInfo getRootAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo accessibilityNodeInfo) throws MyException {
if (null == accessibilityNodeInfo) {
throw new NullPointerException("accessibilityNodeInfo");
}
AccessibilityNodeInfo current = null;
try {
current = accessibilityNodeInfo;
while (true) {
AccessibilityNodeInfo parent = current.getParent();
if (null == parent) {
return current;
}
current = parent;
}
} catch (Exception e) {
if (null != current) {
current.recycle();
}
throw new MyException(e);
}
}
private static AccessibilityNodeInfo refreshFromChild(
AccessibilityNodeInfo node) {
if (node.getChildCount() > 0) {
AccessibilityNodeInfo firstChild = node.getChild(0);
if (firstChild != null) {
AccessibilityNodeInfo parent = firstChild.getParent();
firstChild.recycle();
if (node.equals(parent)) {
return parent;
} else {
recycleNodes(parent);
}
}
}
return null;
}
/**
* Traverses to the first child of this node if any, returning
* {@code true} on success.
*/
public boolean firstChild() {
if (mNode == null) {
return false;
}
int childCount = mNode.getChildCount();
for (int i = 0; i < childCount; ++i) {
AccessibilityNodeInfo newNode = mNode.getChild(i);
if (newNode == null) {
return false;
}
if (AccessibilityNodeInfoUtils.isVisibleOrLegacy(newNode)) {
reset(newNode);
return true;
}
newNode.recycle();
}
return false;
}
private AccessibilityNodeInfo getNextSibling(
AccessibilityNodeInfo from) {
AccessibilityNodeInfo parent = from.getParent();
if (parent == null) {
return null;
}
AccessibilityNodeInfo cur = null;
try {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; ++i) {
cur = parent.getChild(i);
if (cur == null) {
return null;
}
if (cur.equals(from)) {
return parent.getChild(i + 1);
}
if (cur != null) {
cur.recycle();
cur = null;
}
}
} finally {
parent.recycle();
if (cur != null) {
cur.recycle();
}
}
return null;
}
private boolean isValidScrollEvent(AccessibilityEvent event) {
AccessibilityNodeInfo source = event.getSource();
if (source == null) {
return true; // Cannot check source validity, so assume that it's scrollable.
}
boolean valid =
source.isScrollable() || event.getMaxScrollX() != -1 || event.getMaxScrollY() != -1;
source.recycle();
return valid;
}
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;
}
private void scrapeCompleteSubtree_MAIN(Tree.TreeNode treeNode, AccessibilityNodeInfo node, PerformNodeAction nodeAction) {
checkHandlerThread();
node.refresh();
checkFocusedNode_PreLollipop(node);
if (!node.isVisibleToUser()) {
return;
}
int cc = node.getChildCount();
for (int i = 0; i < cc; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
Tree.TreeNode childTreeNode = mTree.put(child);
treeNode.addChild(childTreeNode);
if (nodeAction != null) {
nodeAction.onNodeScanned(child);
}
scrapeCompleteSubtree_MAIN(childTreeNode, child, nodeAction);
child.recycle();
} else {
Ln.d("SKRAPE: warning, couldn't get a child!");
//TODO: one reason for this might be a too large binder transaction -> maybe at least give some feedback to the user
}
}
}
private List<AccessibilityAction> getActionList() {
if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
return Collections.EMPTY_LIST;
}
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
mDelegate.addSupportedActions(mIcon, info, true);
List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
info.recycle();
return result;
}
private final void recycle(AccessibilityNodeInfo node, String caller) {
try {
node.recycle();
} catch (IllegalStateException e) {
logOrThrow(
e,
"Caught IllegalStateException from accessibility framework with %s trying to recycle"
+ " node %s",
caller,
node);
}
}
/**
* Creates a {@link Label} and persists it to the label database, and
* refreshes the label cache.
* <p>
* NOTE: This method will not attempt to recycle {@code node}.
*
* @param node The node to label
* @param userLabel The label provided for the node by the user
*/
public void addLabel(AccessibilityNodeInfo node, String userLabel) {
if (node == null) {
throw new IllegalArgumentException("Attempted to add a label for a null node.");
}
final AccessibilityNodeInfo internalNodeCopy = AccessibilityNodeInfo.obtain(node);
addLabel(internalNodeCopy.getViewIdResourceName(), userLabel);
internalNodeCopy.recycle();
}
/**
* Traverses to the previous sibling of this node within its parent,
* returning {@code true} on success.
*/
public boolean previousSibling() {
if (mNode == null) {
return false;
}
AccessibilityNodeInfo parent = mNode.getParent();
if (parent == null) {
return false;
}
try {
int childNumber = getChildNumber(parent);
for (int i = childNumber - 1; i >= 0; --i) {
AccessibilityNodeInfo newNode = parent.getChild(i);
if (newNode == null) {
return false;
}
if (AccessibilityNodeInfoUtils.isVisibleOrLegacy(newNode)) {
reset(newNode);
return true;
}
newNode.recycle();
}
} finally {
parent.recycle();
}
return false;
}
/**
* Traverses to the next sibling of this node within its parent, returning
* {@code true} on success.
*/
public boolean nextSibling() {
if (mNode == null) {
return false;
}
AccessibilityNodeInfo parent = mNode.getParent();
if (parent == null) {
return false;
}
try {
int childCount = parent.getChildCount();
int childNumber = getChildNumber(parent);
if (childNumber < 0) {
return false;
}
for (int i = childNumber + 1; i < childCount; ++i) {
AccessibilityNodeInfo newNode =
parent.getChild(i);
if (newNode == null) {
return false;
}
if (AccessibilityNodeInfoUtils.isVisibleOrLegacy(newNode)) {
reset(newNode);
return true;
}
newNode.recycle();
}
} finally {
parent.recycle();
}
return false;
}
private WindowHierarchyElementAndroid construct(
int id,
@Nullable WindowHierarchyElementAndroid parent,
AccessibilityWindowInfo fromWindow,
Map<ViewHierarchyElementAndroid, AccessibilityNodeInfo> elementToNodeInfoMap) {
// Bookkeeping
this.parentId = (parent != null) ? parent.getId() : null;
// Window properties
this.windowId = fromWindow.getId();
this.layer = fromWindow.getLayer();
this.type = fromWindow.getType();
this.focused = fromWindow.isFocused();
this.accessibilityFocused = fromWindow.isAccessibilityFocused();
this.active = fromWindow.isActive();
android.graphics.Rect tempRect = new android.graphics.Rect();
fromWindow.getBoundsInScreen(tempRect);
this.boundsInScreen = new Rect(tempRect.left, tempRect.top, tempRect.right, tempRect.bottom);
// Build the window's view hierarchy
AccessibilityNodeInfo rootInfo = fromWindow.getRoot();
this.viewHierarchyElements = new ArrayList<>(); // The ultimate size is unknown
if (rootInfo != null) {
buildViewHierarchy(
rootInfo, viewHierarchyElements, null /* no parent */, elementToNodeInfoMap);
rootInfo.recycle();
} else {
// This could occur in the case where the application state changes between the time that
// the AccessibilityWindowInfo object is obtained and when its root AccessibilityNodeInfo is
// extracted.
LogUtils.w(TAG, "Constructed WindowHierarchyElement with no valid root.");
}
return new WindowHierarchyElementAndroid(
id,
parentId,
childIds,
windowId,
layer,
type,
focused,
accessibilityFocused,
active,
boundsInScreen,
viewHierarchyElements);
}
private static void dumpNodeRec(
AccessibilityNodeInfo node,
XmlSerializer serializer,
int index,
int width,
int height,
boolean withClassName)
throws IOException {
serializer.startTag("", withClassName ? safeTagString(node.getClassName()) : "node");
if (!nafExcludedClass(node) && !nafCheck(node)) {
serializer.attribute("", "NAF", Boolean.toString(true));
}
serializer.attribute("", "index", Integer.toString(index));
final String text;
if (node.getRangeInfo() == null) {
text = safeCharSeqToString(node.getText());
} else {
text = Float.toString(node.getRangeInfo().getCurrent());
}
serializer.attribute("", "text", text);
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()));
/** True if the device is >= API 18 */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
serializer.attribute(
"", "bounds", getVisibleBoundsInScreen(node, width, height).toShortString());
serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
}
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, withClassName);
child.recycle();
} else {
Log.i(TAG, String.format("Skipping invisible child: %s", child.toString()));
}
} else {
Log.i(TAG, String.format("Null child %d/%d, parent: %s", i, count, node.toString()));
}
}
serializer.endTag("", withClassName ? safeTagString(node.getClassName()) : "node");
}
private void handleScrapeAll_MAIN(String reason, PerformNodeAction nodeAction) {
checkHandlerThread();
mTree.clear();
AccessibilityNodeInfo rootNode = null;
try {
rootNode = getRootInActiveWindow();
} catch (Exception ex) {
Ln.e(ex);
return;
}
try {
if (rootNode != null) {
//fail-fast if this is not a package we're interested in
CharSequence csPackageName = rootNode.getPackageName();
if (csPackageName == null) return;
String aPackageName = rootNode.getPackageName().toString();
boolean ours = mCore.getDb().isShowDecryptOverlay(aPackageName);
if (!ours) {
mCore.onAcsScrapeCompleted(aPackageName, null);
return;
}
findFocusedNodePreLollipop_startTransaction();
if (LoggingConfig.INSTANCE.getLOG()) {
Ln.d("SKRAPE: FULL SCAN, root=%s", rootNode.hashCode());
}
mTree.addRootNode(rootNode);
scrapeSubtree_MAIN(rootNode, "handleScrapeAll_MAIN-> " + reason, nodeAction);
rootNode.recycle();
findFocusedNodePreLollipop_commitTransaction();
publishChanges_MAIN();
} else {
Ln.w("SKRAPE: getRootInActiveWindow returned null node!");
clearFocusedNode_PreLollipop();
//TODO: one reason for this might be a too large binder transaction -> maybe at least give some feedback to the user
//TODO: or somehow scrape sub-nodes first in order to have separate smalle transactions?#
// How to Prevent MAX binder transaction / max parcel overflow, Could happen when getting a list node with many immediate GPG text views (the listnode prefetches its children, and if all of them have a long pgp encoded text, this may just be too much for a single binder transaction)
// size is 1 mb, let's say 50 nodes gives 20 k per node, worst case every character is encoded with 4 bytes gives 5 k text!!! -> might just be enough for pgp
// >hmmm, prefetching descendants works recursively, but will always fetch max. 50 nodes,
// -> hopefully not needed but there's noreal way around it!other than using reflection to get the child node's IDs and then manually doing refresh
}
} finally {
if (nodeAction != null) {
nodeAction.onScrapeComplete();
}
}
}
/**
* 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;
}