• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.rotary;
18 
19 import android.view.accessibility.AccessibilityNodeInfo;
20 
21 import androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 import androidx.annotation.VisibleForTesting;
24 
25 import java.util.List;
26 import java.util.function.Predicate;
27 
28 /**
29  * Utility methods for traversing {@link AccessibilityNodeInfo} trees.
30  */
31 class TreeTraverser {
32 
33     @NonNull
34     private NodeCopier mNodeCopier = new NodeCopier();
35 
36     /**
37      * Iterates starting at {@code node} and then progressing through its ancestors, looking for a
38      * node that satisfies {@code targetPredicate}. Returns the first such node (or a copy if it's
39      * the {@code node} itself), or null if none. The caller is responsible for recycling the
40      * result.
41      */
42     @Nullable
findNodeOrAncestor(@onNull AccessibilityNodeInfo node, @NonNull Predicate<AccessibilityNodeInfo> targetPredicate)43     AccessibilityNodeInfo findNodeOrAncestor(@NonNull AccessibilityNodeInfo node,
44             @NonNull Predicate<AccessibilityNodeInfo> targetPredicate) {
45         return findNodeOrAncestor(node, /* stopPredicate= */ null, targetPredicate);
46     }
47 
48     /**
49      * Iterates starting at {@code node} and then progressing through its ancestors, looking for a
50      * node that satisfies {@code targetPredicate}. Returns the first such node (or a copy if it's
51      * the {@code node} itself), or null if no such nodes are encountered before reaching the root
52      * or a node which satisfies {@code stopPredicate}. The caller is responsible for recycling the
53      * result.
54      */
55     @VisibleForTesting
56     @Nullable
findNodeOrAncestor(@onNull AccessibilityNodeInfo node, @Nullable Predicate<AccessibilityNodeInfo> stopPredicate, @NonNull Predicate<AccessibilityNodeInfo> targetPredicate)57     AccessibilityNodeInfo findNodeOrAncestor(@NonNull AccessibilityNodeInfo node,
58             @Nullable Predicate<AccessibilityNodeInfo> stopPredicate,
59             @NonNull Predicate<AccessibilityNodeInfo> targetPredicate) {
60         AccessibilityNodeInfo currentNode = copyNode(node);
61         while (currentNode != null
62                 && (stopPredicate == null || !stopPredicate.test(currentNode))) {
63             if (targetPredicate.test(currentNode)) {
64                 return currentNode;
65             }
66             AccessibilityNodeInfo parentNode = currentNode.getParent();
67             currentNode.recycle();
68             currentNode = parentNode;
69         }
70         Utils.recycleNode(currentNode);
71         return null;
72     }
73 
74     /**
75      * Searches {@code node} and its descendants in depth-first order and returns the first node
76      * satisfying {@code targetPredicate}, if any, or returns null if not found. The caller is
77      * responsible for recycling the result.
78      */
79     @Nullable
depthFirstSearch(@onNull AccessibilityNodeInfo node, @NonNull Predicate<AccessibilityNodeInfo> targetPredicate)80     AccessibilityNodeInfo depthFirstSearch(@NonNull AccessibilityNodeInfo node,
81             @NonNull Predicate<AccessibilityNodeInfo> targetPredicate) {
82         return depthFirstSearch(node, /* skipPredicate= */ null, targetPredicate);
83     }
84 
85     /**
86      * Searches {@code node} and its descendants in depth-first order, skipping any node that
87      * satisfies {@code skipPredicate} as well as its descendants, and returns the first node
88      * satisfying {@code targetPredicate}, if any, or returns null if not found. If
89      * {@code skipPredicate} is null, no nodes are skipped. The caller is responsible for recycling
90      * the result.
91      */
92     @Nullable
93     @VisibleForTesting
depthFirstSearch(@onNull AccessibilityNodeInfo node, @Nullable Predicate<AccessibilityNodeInfo> skipPredicate, @NonNull Predicate<AccessibilityNodeInfo> targetPredicate)94     AccessibilityNodeInfo depthFirstSearch(@NonNull AccessibilityNodeInfo node,
95             @Nullable Predicate<AccessibilityNodeInfo> skipPredicate,
96             @NonNull Predicate<AccessibilityNodeInfo> targetPredicate) {
97         if (skipPredicate != null && skipPredicate.test(node)) {
98             return null;
99         }
100         if (targetPredicate.test(node)) {
101             return copyNode(node);
102         }
103         for (int i = 0; i < node.getChildCount(); i++) {
104             AccessibilityNodeInfo child = node.getChild(i);
105             if (child == null) {
106                 continue;
107             }
108             AccessibilityNodeInfo result = depthFirstSearch(child, skipPredicate, targetPredicate);
109             child.recycle();
110             if (result != null) {
111                 return result;
112             }
113         }
114         return null;
115     }
116 
117     /**
118      * Searches {@code node} and its descendants in reverse depth-first order, and returns the first
119      * node satisfying {@code targetPredicate}, if any, or returns null if not found. The caller is
120      * responsible for recycling the result.
121      */
122     @VisibleForTesting
123     @Nullable
reverseDepthFirstSearch(@onNull AccessibilityNodeInfo node, @NonNull Predicate<AccessibilityNodeInfo> targetPredicate)124     AccessibilityNodeInfo reverseDepthFirstSearch(@NonNull AccessibilityNodeInfo node,
125             @NonNull Predicate<AccessibilityNodeInfo> targetPredicate) {
126         for (int i = node.getChildCount() - 1; i >= 0; i--) {
127             AccessibilityNodeInfo child = node.getChild(i);
128             if (child == null) {
129                 continue;
130             }
131             AccessibilityNodeInfo result =
132                     reverseDepthFirstSearch(child, targetPredicate);
133             child.recycle();
134             if (result != null) {
135                 return result;
136             }
137         }
138         if (targetPredicate.test(node)) {
139             return copyNode(node);
140         }
141         return null;
142     }
143 
144     /**
145      * Iterates through {@code node} and its descendants in depth-first order, adding nodes which
146      * satisfy {@code selectPredicate} to {@code selectedNodes}. Descendants of these nodes aren't
147      * checked. The caller is responsible for recycling the added nodes.
148      */
149     @VisibleForTesting
depthFirstSelect(@onNull AccessibilityNodeInfo node, @NonNull Predicate<AccessibilityNodeInfo> selectPredicate, @NonNull List<AccessibilityNodeInfo> selectedNodes)150     void depthFirstSelect(@NonNull AccessibilityNodeInfo node,
151             @NonNull Predicate<AccessibilityNodeInfo> selectPredicate,
152             @NonNull List<AccessibilityNodeInfo> selectedNodes) {
153         if (selectPredicate.test(node)) {
154             selectedNodes.add(copyNode(node));
155             return;
156         }
157         for (int i = 0; i < node.getChildCount(); i++) {
158             AccessibilityNodeInfo child = node.getChild(i);
159             if (child == null) {
160                 continue;
161             }
162             depthFirstSelect(child, selectPredicate, selectedNodes);
163             child.recycle();
164         }
165     }
166 
167     /** Sets a node copier for testing. */
168     @VisibleForTesting
setNodeCopier(@onNull NodeCopier nodeCopier)169     void setNodeCopier(@NonNull NodeCopier nodeCopier) {
170         mNodeCopier = nodeCopier;
171     }
172 
copyNode(@ullable AccessibilityNodeInfo node)173     private AccessibilityNodeInfo copyNode(@Nullable AccessibilityNodeInfo node) {
174         return mNodeCopier.copy(node);
175     }
176 }
177