blob: 87e18b71069cf1fa311c5163176393b1b545e23a [file] [log] [blame]
Svetoslav8e3feb12014-02-24 13:46:47 -08001/*
2 * Copyright (C) 2012 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
17package android.view.accessibility;
18
19import android.os.Build;
20import android.util.ArraySet;
21import android.util.Log;
22import android.util.LongArray;
23import android.util.LongSparseArray;
24import android.util.SparseArray;
25
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * Cache for AccessibilityWindowInfos and AccessibilityNodeInfos.
31 * It is updated when windows change or nodes change.
Phil Weaverb010b122016-08-17 17:47:48 -070032 * @hide
Svetoslav8e3feb12014-02-24 13:46:47 -080033 */
Phil Weaverc140fdc2017-11-09 15:24:17 -080034public class AccessibilityCache {
Svetoslav8e3feb12014-02-24 13:46:47 -080035
36 private static final String LOG_TAG = "AccessibilityCache";
37
38 private static final boolean DEBUG = false;
39
Jeff Sharkey5ab02432017-06-27 11:01:36 -060040 private static final boolean CHECK_INTEGRITY = Build.IS_ENG;
Svetoslav8e3feb12014-02-24 13:46:47 -080041
Eugene Suslaeb1375c2016-12-20 16:32:59 -080042 /**
43 * {@link AccessibilityEvent} types that are critical for the cache to stay up to date
44 *
45 * When adding new event types in {@link #onAccessibilityEvent}, please add it here also, to
46 * make sure that the events are delivered to cache regardless of
47 * {@link android.accessibilityservice.AccessibilityServiceInfo#eventTypes}
48 */
49 public static final int CACHE_CRITICAL_EVENTS_MASK =
50 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
51 | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
52 | AccessibilityEvent.TYPE_VIEW_FOCUSED
53 | AccessibilityEvent.TYPE_VIEW_SELECTED
54 | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
55 | AccessibilityEvent.TYPE_VIEW_CLICKED
56 | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
57 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
58 | AccessibilityEvent.TYPE_VIEW_SCROLLED
59 | AccessibilityEvent.TYPE_WINDOWS_CHANGED
60 | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
61
Svetoslav8e3feb12014-02-24 13:46:47 -080062 private final Object mLock = new Object();
63
Phil Weaverb010b122016-08-17 17:47:48 -070064 private final AccessibilityNodeRefresher mAccessibilityNodeRefresher;
65
Svetoslav27ad2e92015-02-12 17:54:15 -080066 private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
67 private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
68
Maxim Bogatovc3281202015-10-21 12:38:01 -070069 private boolean mIsAllWindowsCached;
70
Svetoslav8e3feb12014-02-24 13:46:47 -080071 private final SparseArray<AccessibilityWindowInfo> mWindowCache =
Svetoslavf7c42c52014-07-24 12:19:22 -070072 new SparseArray<>();
Svetoslav8e3feb12014-02-24 13:46:47 -080073
74 private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
Svetoslavf7c42c52014-07-24 12:19:22 -070075 new SparseArray<>();
Svetoslav8e3feb12014-02-24 13:46:47 -080076
77 private final SparseArray<AccessibilityWindowInfo> mTempWindowArray =
Svetoslavf7c42c52014-07-24 12:19:22 -070078 new SparseArray<>();
Svetoslav8e3feb12014-02-24 13:46:47 -080079
Phil Weaverb010b122016-08-17 17:47:48 -070080 public AccessibilityCache(AccessibilityNodeRefresher nodeRefresher) {
81 mAccessibilityNodeRefresher = nodeRefresher;
82 }
83
Maxim Bogatovc3281202015-10-21 12:38:01 -070084 public void setWindows(List<AccessibilityWindowInfo> windows) {
85 synchronized (mLock) {
86 if (DEBUG) {
87 Log.i(LOG_TAG, "Set windows");
88 }
89 clearWindowCache();
90 if (windows == null) {
91 return;
92 }
93 final int windowCount = windows.size();
94 for (int i = 0; i < windowCount; i++) {
95 final AccessibilityWindowInfo window = windows.get(i);
96 addWindow(window);
97 }
98 mIsAllWindowsCached = true;
99 }
100 }
101
Svetoslav8e3feb12014-02-24 13:46:47 -0800102 public void addWindow(AccessibilityWindowInfo window) {
103 synchronized (mLock) {
104 if (DEBUG) {
105 Log.i(LOG_TAG, "Caching window: " + window.getId());
106 }
Svetoslav04cab1b2014-08-25 18:35:57 -0700107 final int windowId = window.getId();
108 AccessibilityWindowInfo oldWindow = mWindowCache.get(windowId);
109 if (oldWindow != null) {
110 oldWindow.recycle();
111 }
112 mWindowCache.put(windowId, AccessibilityWindowInfo.obtain(window));
Svetoslav8e3feb12014-02-24 13:46:47 -0800113 }
114 }
115
Svetoslav8e3feb12014-02-24 13:46:47 -0800116 /**
117 * Notifies the cache that the something in the UI changed. As a result
118 * the cache will either refresh some nodes or evict some nodes.
119 *
Eugene Suslaeb1375c2016-12-20 16:32:59 -0800120 * Note: any event that ends up affecting the cache should also be present in
121 * {@link #CACHE_CRITICAL_EVENTS_MASK}
122 *
Svetoslav8e3feb12014-02-24 13:46:47 -0800123 * @param event An event.
124 */
125 public void onAccessibilityEvent(AccessibilityEvent event) {
126 synchronized (mLock) {
127 final int eventType = event.getEventType();
128 switch (eventType) {
Svetoslav27ad2e92015-02-12 17:54:15 -0800129 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
130 if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
131 refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
132 }
133 mAccessibilityFocus = event.getSourceNodeId();
134 refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
135 } break;
136
137 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
138 if (mAccessibilityFocus == event.getSourceNodeId()) {
139 refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
140 mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
141 }
142 } break;
143
144 case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
145 if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
146 refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
147 }
148 mInputFocus = event.getSourceNodeId();
149 refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
150 } break;
151
Svetoslav8e3feb12014-02-24 13:46:47 -0800152 case AccessibilityEvent.TYPE_VIEW_SELECTED:
153 case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
Svet Ganov03221002014-12-05 16:52:27 -0800154 case AccessibilityEvent.TYPE_VIEW_CLICKED:
Svetoslav8e3feb12014-02-24 13:46:47 -0800155 case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
156 refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId());
157 } break;
158
159 case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
160 synchronized (mLock) {
161 final int windowId = event.getWindowId();
162 final long sourceId = event.getSourceNodeId();
163 if ((event.getContentChangeTypes()
164 & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
165 clearSubTreeLocked(windowId, sourceId);
166 } else {
167 refreshCachedNodeLocked(windowId, sourceId);
168 }
169 }
170 } break;
171
172 case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
173 clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
174 } break;
Svetoslava4725ef2014-07-24 14:17:05 -0700175
Svetoslav13bd7712014-09-19 18:18:04 -0700176 case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
177 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
178 clear();
Svetoslava4725ef2014-07-24 14:17:05 -0700179 } break;
Svetoslav8e3feb12014-02-24 13:46:47 -0800180 }
181 }
182
183 if (CHECK_INTEGRITY) {
184 checkIntegrity();
185 }
186 }
187
188 private void refreshCachedNodeLocked(int windowId, long sourceId) {
189 if (DEBUG) {
190 Log.i(LOG_TAG, "Refreshing cached node.");
191 }
192
193 LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
194 if (nodes == null) {
195 return;
196 }
197 AccessibilityNodeInfo cachedInfo = nodes.get(sourceId);
198 // If the source is not in the cache - nothing to do.
199 if (cachedInfo == null) {
200 return;
201 }
202 // The node changed so we will just refresh it right now.
Phil Weaverb010b122016-08-17 17:47:48 -0700203 if (mAccessibilityNodeRefresher.refreshNode(cachedInfo, true)) {
Svetoslav8e3feb12014-02-24 13:46:47 -0800204 return;
205 }
206 // Weird, we could not refresh. Just evict the entire sub-tree.
207 clearSubTreeLocked(windowId, sourceId);
208 }
209
210 /**
211 * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
212 * window and the accessibility id of the node.
213 *
214 * @param windowId The id of the window hosting the node.
215 * @param accessibilityNodeId The info accessibility node id.
216 * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
217 */
218 public AccessibilityNodeInfo getNode(int windowId, long accessibilityNodeId) {
219 synchronized(mLock) {
220 LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
221 if (nodes == null) {
222 return null;
223 }
224 AccessibilityNodeInfo info = nodes.get(accessibilityNodeId);
225 if (info != null) {
226 // Return a copy since the client calls to AccessibilityNodeInfo#recycle()
227 // will wipe the data of the cached info.
228 info = AccessibilityNodeInfo.obtain(info);
229 }
230 if (DEBUG) {
231 Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info);
232 }
233 return info;
234 }
235 }
236
237 public List<AccessibilityWindowInfo> getWindows() {
238 synchronized (mLock) {
Maxim Bogatovc3281202015-10-21 12:38:01 -0700239 if (!mIsAllWindowsCached) {
240 return null;
241 }
242
Svetoslav8e3feb12014-02-24 13:46:47 -0800243 final int windowCount = mWindowCache.size();
244 if (windowCount > 0) {
245 // Careful to return the windows in a decreasing layer order.
246 SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
247 sortedWindows.clear();
248
249 for (int i = 0; i < windowCount; i++) {
250 AccessibilityWindowInfo window = mWindowCache.valueAt(i);
251 sortedWindows.put(window.getLayer(), window);
252 }
253
Phil Weaveradaafb22016-05-19 10:32:52 -0700254 // It's possible in transient conditions for two windows to share the same
255 // layer, which results in sortedWindows being smaller than mWindowCache
256 final int sortedWindowCount = sortedWindows.size();
257 List<AccessibilityWindowInfo> windows = new ArrayList<>(sortedWindowCount);
258 for (int i = sortedWindowCount - 1; i >= 0; i--) {
Svetoslav8e3feb12014-02-24 13:46:47 -0800259 AccessibilityWindowInfo window = sortedWindows.valueAt(i);
260 windows.add(AccessibilityWindowInfo.obtain(window));
Svetoslav04cab1b2014-08-25 18:35:57 -0700261 sortedWindows.removeAt(i);
Svetoslav8e3feb12014-02-24 13:46:47 -0800262 }
263
Svetoslav8e3feb12014-02-24 13:46:47 -0800264 return windows;
265 }
266 return null;
267 }
268 }
269
270 public AccessibilityWindowInfo getWindow(int windowId) {
271 synchronized (mLock) {
272 AccessibilityWindowInfo window = mWindowCache.get(windowId);
273 if (window != null) {
274 return AccessibilityWindowInfo.obtain(window);
275 }
276 return null;
277 }
278 }
279
280 /**
281 * Caches an {@link AccessibilityNodeInfo}.
282 *
283 * @param info The node to cache.
284 */
285 public void add(AccessibilityNodeInfo info) {
286 synchronized(mLock) {
287 if (DEBUG) {
288 Log.i(LOG_TAG, "add(" + info + ")");
289 }
290
291 final int windowId = info.getWindowId();
292 LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
293 if (nodes == null) {
Svetoslavf7c42c52014-07-24 12:19:22 -0700294 nodes = new LongSparseArray<>();
Svetoslav8e3feb12014-02-24 13:46:47 -0800295 mNodeCache.put(windowId, nodes);
296 }
297
298 final long sourceId = info.getSourceNodeId();
299 AccessibilityNodeInfo oldInfo = nodes.get(sourceId);
300 if (oldInfo != null) {
301 // If the added node is in the cache we have to be careful if
302 // the new one represents a source state where some of the
303 // children have been removed to remove the descendants that
304 // are no longer present.
305 final LongArray newChildrenIds = info.getChildNodeIds();
Svetoslav8e3feb12014-02-24 13:46:47 -0800306
Svetoslavf7c42c52014-07-24 12:19:22 -0700307 final int oldChildCount = oldInfo.getChildCount();
308 for (int i = 0; i < oldChildCount; i++) {
Phil Weaverf1eb10b2018-08-14 16:25:53 -0700309 final long oldChildId = oldInfo.getChildId(i);
310 // If the child is no longer present, remove the sub-tree.
311 if (newChildrenIds == null || newChildrenIds.indexOf(oldChildId) < 0) {
312 clearSubTreeLocked(windowId, oldChildId);
313 }
Phil Weaver61a1fab2017-05-03 13:55:51 -0700314 if (nodes.get(sourceId) == null) {
315 // We've removed (and thus recycled) this node because it was its own
316 // ancestor (the app gave us bad data), we can't continue using it.
317 // Clear the cache for this window and give up on adding the node.
318 clearNodesForWindowLocked(windowId);
319 return;
320 }
Svetoslav8e3feb12014-02-24 13:46:47 -0800321 }
322
323 // Also be careful if the parent has changed since the new
324 // parent may be a predecessor of the old parent which will
Phil Weaverb010b122016-08-17 17:47:48 -0700325 // add cycles to the cache.
Svetoslav8e3feb12014-02-24 13:46:47 -0800326 final long oldParentId = oldInfo.getParentNodeId();
327 if (info.getParentNodeId() != oldParentId) {
328 clearSubTreeLocked(windowId, oldParentId);
Phil Weaverc140fdc2017-11-09 15:24:17 -0800329 } else {
330 oldInfo.recycle();
Svetoslav8e3feb12014-02-24 13:46:47 -0800331 }
332 }
333
334 // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
335 // will wipe the data of the cached info.
336 AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
337 nodes.put(sourceId, clone);
Phil Weaverb010b122016-08-17 17:47:48 -0700338 if (clone.isAccessibilityFocused()) {
Rhed Jaoca9b5262018-07-27 21:13:00 +0800339 if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
340 && mAccessibilityFocus != sourceId) {
341 refreshCachedNodeLocked(windowId, mAccessibilityFocus);
342 }
Phil Weaverb010b122016-08-17 17:47:48 -0700343 mAccessibilityFocus = sourceId;
Rhed Jaoca9b5262018-07-27 21:13:00 +0800344 } else if (mAccessibilityFocus == sourceId) {
345 mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
Phil Weaverb010b122016-08-17 17:47:48 -0700346 }
347 if (clone.isFocused()) {
348 mInputFocus = sourceId;
349 }
Svetoslav8e3feb12014-02-24 13:46:47 -0800350 }
351 }
352
353 /**
354 * Clears the cache.
355 */
356 public void clear() {
357 synchronized(mLock) {
358 if (DEBUG) {
359 Log.i(LOG_TAG, "clear()");
360 }
Maxim Bogatovc3281202015-10-21 12:38:01 -0700361 clearWindowCache();
Svetoslav8e3feb12014-02-24 13:46:47 -0800362 final int nodesForWindowCount = mNodeCache.size();
363 for (int i = 0; i < nodesForWindowCount; i++) {
364 final int windowId = mNodeCache.keyAt(i);
365 clearNodesForWindowLocked(windowId);
366 }
Svetoslav27ad2e92015-02-12 17:54:15 -0800367
368 mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
369 mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
Svetoslav8e3feb12014-02-24 13:46:47 -0800370 }
371 }
372
Maxim Bogatovc3281202015-10-21 12:38:01 -0700373 private void clearWindowCache() {
374 final int windowCount = mWindowCache.size();
375 for (int i = windowCount - 1; i >= 0; i--) {
376 AccessibilityWindowInfo window = mWindowCache.valueAt(i);
377 window.recycle();
378 mWindowCache.removeAt(i);
379 }
380 mIsAllWindowsCached = false;
381 }
382
Eugene Susla74c6cba2016-12-28 14:42:54 -0800383 /**
384 * Clears nodes for the window with the given id
385 */
Svetoslav8e3feb12014-02-24 13:46:47 -0800386 private void clearNodesForWindowLocked(int windowId) {
387 if (DEBUG) {
Svetoslav13bd7712014-09-19 18:18:04 -0700388 Log.i(LOG_TAG, "clearNodesForWindowLocked(" + windowId + ")");
Svetoslav8e3feb12014-02-24 13:46:47 -0800389 }
390 LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
391 if (nodes == null) {
392 return;
393 }
394 // Recycle the nodes before clearing the cache.
395 final int nodeCount = nodes.size();
396 for (int i = nodeCount - 1; i >= 0; i--) {
397 AccessibilityNodeInfo info = nodes.valueAt(i);
398 nodes.removeAt(i);
399 info.recycle();
400 }
401 mNodeCache.remove(windowId);
402 }
403
404 /**
405 * Clears a subtree rooted at the node with the given id that is
406 * hosted in a given window.
407 *
408 * @param windowId The id of the hosting window.
409 * @param rootNodeId The root id.
410 */
411 private void clearSubTreeLocked(int windowId, long rootNodeId) {
412 if (DEBUG) {
413 Log.i(LOG_TAG, "Clearing cached subtree.");
414 }
415 LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
416 if (nodes != null) {
417 clearSubTreeRecursiveLocked(nodes, rootNodeId);
418 }
419 }
420
421 /**
422 * Clears a subtree given a pointer to the root id and the nodes
423 * in the hosting window.
424 *
425 * @param nodes The nodes in the hosting window.
426 * @param rootNodeId The id of the root to evict.
427 */
Qasid Ahmad Sadiq4d700222019-02-21 21:06:22 -0800428 private void clearSubTreeRecursiveLocked(LongSparseArray<AccessibilityNodeInfo> nodes,
Svetoslav8e3feb12014-02-24 13:46:47 -0800429 long rootNodeId) {
430 AccessibilityNodeInfo current = nodes.get(rootNodeId);
431 if (current == null) {
Qasid Ahmad Sadiq4d700222019-02-21 21:06:22 -0800432 return;
Svetoslav8e3feb12014-02-24 13:46:47 -0800433 }
434 nodes.remove(rootNodeId);
435 final int childCount = current.getChildCount();
436 for (int i = 0; i < childCount; i++) {
437 final long childNodeId = current.getChildId(i);
Qasid Ahmad Sadiq4d700222019-02-21 21:06:22 -0800438 clearSubTreeRecursiveLocked(nodes, childNodeId);
Svetoslav8e3feb12014-02-24 13:46:47 -0800439 }
Phil Weaverb010b122016-08-17 17:47:48 -0700440 current.recycle();
Svetoslav8e3feb12014-02-24 13:46:47 -0800441 }
442
443 /**
444 * Check the integrity of the cache which is nodes from different windows
445 * are not mixed, there is a single active window, there is a single focused
446 * window, for every window there are no duplicates nodes, all nodes for a
447 * window are connected, for every window there is a single input focused
448 * node, and for every window there is a single accessibility focused node.
449 */
450 public void checkIntegrity() {
451 synchronized (mLock) {
452 // Get the root.
453 if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
454 return;
455 }
456
457 AccessibilityWindowInfo focusedWindow = null;
458 AccessibilityWindowInfo activeWindow = null;
459
460 final int windowCount = mWindowCache.size();
461 for (int i = 0; i < windowCount; i++) {
462 AccessibilityWindowInfo window = mWindowCache.valueAt(i);
463
464 // Check for one active window.
465 if (window.isActive()) {
466 if (activeWindow != null) {
467 Log.e(LOG_TAG, "Duplicate active window:" + window);
468 } else {
469 activeWindow = window;
470 }
471 }
472
473 // Check for one focused window.
474 if (window.isFocused()) {
475 if (focusedWindow != null) {
476 Log.e(LOG_TAG, "Duplicate focused window:" + window);
477 } else {
478 focusedWindow = window;
479 }
480 }
481 }
482
483 // Traverse the tree and do some checks.
484 AccessibilityNodeInfo accessFocus = null;
485 AccessibilityNodeInfo inputFocus = null;
486
487 final int nodesForWindowCount = mNodeCache.size();
488 for (int i = 0; i < nodesForWindowCount; i++) {
489 LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.valueAt(i);
490 if (nodes.size() <= 0) {
491 continue;
492 }
493
Svetoslavf7c42c52014-07-24 12:19:22 -0700494 ArraySet<AccessibilityNodeInfo> seen = new ArraySet<>();
Svetoslav8e3feb12014-02-24 13:46:47 -0800495 final int windowId = mNodeCache.keyAt(i);
496
497 final int nodeCount = nodes.size();
498 for (int j = 0; j < nodeCount; j++) {
499 AccessibilityNodeInfo node = nodes.valueAt(j);
500
501 // Check for duplicates
502 if (!seen.add(node)) {
503 Log.e(LOG_TAG, "Duplicate node: " + node
504 + " in window:" + windowId);
Svetoslavf7c42c52014-07-24 12:19:22 -0700505 // Stop now as we potentially found a loop.
506 continue;
Svetoslav8e3feb12014-02-24 13:46:47 -0800507 }
508
509 // Check for one accessibility focus.
510 if (node.isAccessibilityFocused()) {
511 if (accessFocus != null) {
512 Log.e(LOG_TAG, "Duplicate accessibility focus:" + node
513 + " in window:" + windowId);
514 } else {
515 accessFocus = node;
516 }
517 }
518
519 // Check for one input focus.
520 if (node.isFocused()) {
521 if (inputFocus != null) {
522 Log.e(LOG_TAG, "Duplicate input focus: " + node
523 + " in window:" + windowId);
524 } else {
525 inputFocus = node;
526 }
527 }
528
529 // The node should be a child of its parent if we have the parent.
530 AccessibilityNodeInfo nodeParent = nodes.get(node.getParentNodeId());
531 if (nodeParent != null) {
532 boolean childOfItsParent = false;
533 final int childCount = nodeParent.getChildCount();
534 for (int k = 0; k < childCount; k++) {
535 AccessibilityNodeInfo child = nodes.get(nodeParent.getChildId(k));
536 if (child == node) {
537 childOfItsParent = true;
538 break;
539 }
540 }
541 if (!childOfItsParent) {
Svetoslav13bd7712014-09-19 18:18:04 -0700542 Log.e(LOG_TAG, "Invalid parent-child relation between parent: "
Svetoslav8e3feb12014-02-24 13:46:47 -0800543 + nodeParent + " and child: " + node);
544 }
545 }
546
547 // The node should be the parent of its child if we have the child.
548 final int childCount = node.getChildCount();
549 for (int k = 0; k < childCount; k++) {
550 AccessibilityNodeInfo child = nodes.get(node.getChildId(k));
551 if (child != null) {
552 AccessibilityNodeInfo parent = nodes.get(child.getParentNodeId());
553 if (parent != node) {
Svetoslav13bd7712014-09-19 18:18:04 -0700554 Log.e(LOG_TAG, "Invalid child-parent relation between child: "
Svetoslav8e3feb12014-02-24 13:46:47 -0800555 + node + " and parent: " + nodeParent);
556 }
557 }
558 }
559 }
560 }
561 }
562 }
Phil Weaverb010b122016-08-17 17:47:48 -0700563
564 // Layer of indirection included to break dependency chain for testing
565 public static class AccessibilityNodeRefresher {
566 public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
Phil Weaverc2e28932016-12-08 12:29:25 -0800567 return info.refresh(null, bypassCache);
Phil Weaverb010b122016-08-17 17:47:48 -0700568 }
569 }
Svetoslav8e3feb12014-02-24 13:46:47 -0800570}