1 /*
2  * Copyright 2016 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 android.view.accessibility;
18 
19 import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;
20 import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_INPUT;
21 import static android.view.accessibility.AccessibilityNodeInfo.ROOT_ITEM_ID;
22 import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
23 import static android.view.accessibility.AccessibilityWindowInfo.ANY_WINDOW_ID;
24 
25 import static junit.framework.Assert.assertEquals;
26 import static junit.framework.Assert.assertNotNull;
27 import static junit.framework.Assert.assertNull;
28 
29 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertTrue;
31 import static org.junit.Assert.fail;
32 import static org.mockito.Matchers.anyBoolean;
33 import static org.mockito.Matchers.anyObject;
34 import static org.mockito.Mockito.doAnswer;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.never;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.when;
39 
40 import android.os.SystemClock;
41 import android.util.SparseArray;
42 import android.view.Display;
43 import android.view.View;
44 
45 import androidx.test.filters.LargeTest;
46 import androidx.test.runner.AndroidJUnit4;
47 
48 import com.google.common.base.Throwables;
49 
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.invocation.InvocationOnMock;
54 import org.mockito.stubbing.Answer;
55 
56 import java.util.Arrays;
57 import java.util.List;
58 
59 @LargeTest
60 @RunWith(AndroidJUnit4.class)
61 public class AccessibilityCacheTest {
62     private static final int WINDOW_ID_1 = 0xBEEF;
63     private static final int WINDOW_ID_2 = 0xFACE;
64     private static final int WINDOW_ID_3 = 0xABCD;
65     private static final int WINDOW_ID_4 = 0xDCBA;
66     private static final int SINGLE_VIEW_ID = 0xCAFE;
67     private static final int OTHER_VIEW_ID = 0xCAB2;
68     private static final int PARENT_VIEW_ID = 0xFED4;
69     private static final int CHILD_VIEW_ID = 0xFEED;
70     private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
71     private static final int MOCK_CONNECTION_ID = 1;
72     private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
73     private static final int DEFAULT_WINDOW_LAYER = 0;
74     private static final int SPECIFIC_WINDOW_LAYER = 5;
75 
76     AccessibilityCache mAccessibilityCache;
77     AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
78 
79     @Before
setUp()80     public void setUp() {
81         mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class);
82         when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true);
83         mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
84     }
85 
86     @Test
testEmptyCache_returnsNull()87     public void testEmptyCache_returnsNull() {
88         assertNull(mAccessibilityCache.getNode(0, 0));
89         assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
90         assertNull(mAccessibilityCache.getWindow(0));
91     }
92 
93     @Test
testEmptyCache_clearDoesntCrash()94     public void testEmptyCache_clearDoesntCrash() {
95         mAccessibilityCache.clear();
96     }
97 
98     @Test
testEmptyCache_a11yEventsHaveNoEffect()99     public void testEmptyCache_a11yEventsHaveNoEffect() {
100         AccessibilityEvent event = AccessibilityEvent.obtain();
101         int[] a11yEventTypes = {
102                 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
103                 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
104                 AccessibilityEvent.TYPE_VIEW_FOCUSED,
105                 AccessibilityEvent.TYPE_VIEW_SELECTED,
106                 AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
107                 AccessibilityEvent.TYPE_VIEW_CLICKED,
108                 AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
109                 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
110                 AccessibilityEvent.TYPE_VIEW_SCROLLED,
111                 AccessibilityEvent.TYPE_WINDOWS_CHANGED,
112                 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED};
113         for (int i = 0; i < a11yEventTypes.length; i++) {
114             event.setEventType(a11yEventTypes[i]);
115             mAccessibilityCache.onAccessibilityEvent(event);
116         }
117     }
118 
119     @Test
addThenGetWindow_returnsEquivalentButNotSameWindow()120     public void addThenGetWindow_returnsEquivalentButNotSameWindow() {
121         AccessibilityWindowInfo windowInfo = null, copyOfInfo = null, windowFromCache = null;
122         try {
123             windowInfo = AccessibilityWindowInfo.obtain();
124             windowInfo.setId(WINDOW_ID_1);
125             windowInfo.setDisplayId(Display.DEFAULT_DISPLAY);
126             mAccessibilityCache.addWindow(windowInfo);
127             // Make a copy
128             copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo);
129             windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info.
130             windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1);
131             assertEquals(copyOfInfo, windowFromCache);
132         } finally {
133             windowFromCache.recycle();
134             windowInfo.recycle();
135             copyOfInfo.recycle();
136         }
137     }
138 
139     @Test
addWindowThenClear_noLongerInCache()140     public void addWindowThenClear_noLongerInCache() {
141         putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
142                 DEFAULT_WINDOW_LAYER);
143         mAccessibilityCache.clear();
144         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
145     }
146 
147     @Test
addWindowGetOtherId_returnsNull()148     public void addWindowGetOtherId_returnsNull() {
149         putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
150                 DEFAULT_WINDOW_LAYER);
151         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1));
152     }
153 
154     @Test
addWindowThenGetWindows_returnsNull()155     public void addWindowThenGetWindows_returnsNull() {
156         putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
157                 DEFAULT_WINDOW_LAYER);
158         assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
159     }
160 
161     @Test
setWindowsThenGetWindows_returnsInDecreasingLayerOrder()162     public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() {
163         AccessibilityWindowInfo windowInfo1 = null;
164         AccessibilityWindowInfo windowInfo2 = null;
165         AccessibilityWindowInfo window1Out = null;
166         AccessibilityWindowInfo window2Out = null;
167         List<AccessibilityWindowInfo> windowsOut = null;
168         try {
169             windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
170             windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
171             List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
172             setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
173 
174             windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
175             window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
176             window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
177 
178             assertEquals(2, windowsOut.size());
179             assertEquals(windowInfo2, windowsOut.get(0));
180             assertEquals(windowInfo1, windowsOut.get(1));
181             assertEquals(windowInfo1, window1Out);
182             assertEquals(windowInfo2, window2Out);
183         } finally {
184             window1Out.recycle();
185             window2Out.recycle();
186             windowInfo1.recycle();
187             windowInfo2.recycle();
188             for (AccessibilityWindowInfo windowInfo : windowsOut) {
189                 windowInfo.recycle();
190             }
191         }
192     }
193 
194     @Test
setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder()195     public void setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder() {
196         AccessibilityWindowInfo windowInfo1 = null;
197         AccessibilityWindowInfo windowInfo2 = null;
198         AccessibilityWindowInfo window1Out = null;
199         AccessibilityWindowInfo window2Out = null;
200         AccessibilityWindowInfo window3Out = null;
201         List<AccessibilityWindowInfo> windowsOut = null;
202         try {
203             windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
204             windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
205             List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
206             setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
207 
208             putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, Display.DEFAULT_DISPLAY,
209                     windowInfo1.getLayer() + 1);
210 
211             windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
212             window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
213             window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
214             window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
215 
216             assertEquals(3, windowsOut.size());
217             assertEquals(windowInfo2, windowsOut.get(0));
218             assertEquals(windowInfo1, windowsOut.get(2));
219             assertEquals(windowInfo1, window1Out);
220             assertEquals(windowInfo2, window2Out);
221             assertEquals(window3Out, windowsOut.get(1));
222         } finally {
223             window1Out.recycle();
224             window2Out.recycle();
225             window3Out.recycle();
226             windowInfo1.recycle();
227             windowInfo2.recycle();
228             for (AccessibilityWindowInfo windowInfo : windowsOut) {
229                 windowInfo.recycle();
230             }
231         }
232     }
233 
234     @Test
235     public void
setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange()236             setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange() {
237         AccessibilityWindowInfo windowInfo1 = null;
238         AccessibilityWindowInfo windowInfo2 = null;
239         AccessibilityWindowInfo window1Out = null;
240         AccessibilityWindowInfo window2Out = null;
241         List<AccessibilityWindowInfo> windowsOut = null;
242         try {
243             // Sets windows to default display.
244             windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
245             windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
246             List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
247             setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
248             // Adds one window to second display.
249             putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, SECONDARY_DISPLAY_ID,
250                     windowInfo1.getLayer() + 1);
251 
252             windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
253             window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
254             window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
255 
256             assertEquals(2, windowsOut.size());
257             assertEquals(windowInfo2, windowsOut.get(0));
258             assertEquals(windowInfo1, windowsOut.get(1));
259             assertEquals(windowInfo1, window1Out);
260             assertEquals(windowInfo2, window2Out);
261         } finally {
262             window1Out.recycle();
263             window2Out.recycle();
264             windowInfo1.recycle();
265             windowInfo2.recycle();
266             for (AccessibilityWindowInfo windowInfo : windowsOut) {
267                 windowInfo.recycle();
268             }
269         }
270     }
271 
272     @Test
setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder()273     public void setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder() {
274         AccessibilityWindowInfo windowInfo1 = null;
275         AccessibilityWindowInfo windowInfo2 = null;
276         AccessibilityWindowInfo window1Out = null;
277         AccessibilityWindowInfo window2Out = null;
278         AccessibilityWindowInfo windowInfo3 = null;
279         AccessibilityWindowInfo windowInfo4 = null;
280         AccessibilityWindowInfo window3Out = null;
281         AccessibilityWindowInfo window4Out = null;
282         List<AccessibilityWindowInfo> windowsOut1 = null;
283         List<AccessibilityWindowInfo> windowsOut2 = null;
284         try {
285             // Prepares all windows for default display.
286             windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
287             windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
288             List<AccessibilityWindowInfo> windowsIn1 = Arrays.asList(windowInfo1, windowInfo2);
289             // Prepares all windows for second display.
290             windowInfo3 = obtainAccessibilityWindowInfo(WINDOW_ID_3, windowInfo1.getLayer() + 2);
291             windowInfo4 = obtainAccessibilityWindowInfo(WINDOW_ID_4, windowInfo1.getLayer() + 3);
292             List<AccessibilityWindowInfo> windowsIn2 = Arrays.asList(windowInfo3, windowInfo4);
293             // Sets all windows of all displays into A11y cache.
294             SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
295             allWindows.put(Display.DEFAULT_DISPLAY, windowsIn1);
296             allWindows.put(SECONDARY_DISPLAY_ID, windowsIn2);
297             final long populationTimeStamp = SystemClock.uptimeMillis();
298             mAccessibilityCache.setWindowsOnAllDisplays(allWindows, populationTimeStamp);
299             // Gets windows at default display.
300             windowsOut1 = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
301             window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
302             window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
303 
304             assertEquals(2, windowsOut1.size());
305             assertEquals(windowInfo2, windowsOut1.get(0));
306             assertEquals(windowInfo1, windowsOut1.get(1));
307             assertEquals(windowInfo1, window1Out);
308             assertEquals(windowInfo2, window2Out);
309             // Gets windows at seocnd display.
310             windowsOut2 = getWindowsByDisplay(SECONDARY_DISPLAY_ID);
311             window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
312             window4Out = mAccessibilityCache.getWindow(WINDOW_ID_4);
313 
314             assertEquals(2, windowsOut2.size());
315             assertEquals(windowInfo4, windowsOut2.get(0));
316             assertEquals(windowInfo3, windowsOut2.get(1));
317             assertEquals(windowInfo3, window3Out);
318             assertEquals(windowInfo4, window4Out);
319         } finally {
320             window1Out.recycle();
321             window2Out.recycle();
322             windowInfo1.recycle();
323             windowInfo2.recycle();
324             window3Out.recycle();
325             window4Out.recycle();
326             windowInfo3.recycle();
327             windowInfo4.recycle();
328             for (AccessibilityWindowInfo windowInfo : windowsOut1) {
329                 windowInfo.recycle();
330             }
331             for (AccessibilityWindowInfo windowInfo : windowsOut2) {
332                 windowInfo.recycle();
333             }
334         }
335     }
336 
337     @Test
setInvalidWindowsAfterWindowsChangedEvent_notInCache()338     public void setInvalidWindowsAfterWindowsChangedEvent_notInCache() {
339         final AccessibilityEvent event = new AccessibilityEvent(
340                 AccessibilityEvent.TYPE_WINDOWS_CHANGED);
341         final long eventTime = 1000L;
342         event.setEventTime(eventTime);
343         mAccessibilityCache.onAccessibilityEvent(event);
344 
345         final AccessibilityWindowInfo windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1,
346                 SPECIFIC_WINDOW_LAYER);
347         List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1);
348         setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn, eventTime - 10);
349 
350         try {
351             assertNull(getWindowsByDisplay(Display.DEFAULT_DISPLAY));
352         } finally {
353             windowInfo1.recycle();
354         }
355     }
356 
357     @Test
setInvalidWindowsAfterStateChangedEvent_notInCache()358     public void setInvalidWindowsAfterStateChangedEvent_notInCache() {
359         final AccessibilityEvent event = new AccessibilityEvent(
360                 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
361         final long eventTime = 1000L;
362         event.setEventTime(eventTime);
363         mAccessibilityCache.onAccessibilityEvent(event);
364 
365         final AccessibilityWindowInfo windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1,
366                 SPECIFIC_WINDOW_LAYER);
367         List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1);
368         setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn, eventTime - 10);
369 
370         try {
371             assertNull(getWindowsByDisplay(Display.DEFAULT_DISPLAY));
372         } finally {
373             windowInfo1.recycle();
374         }
375     }
376 
377     @Test
addWindowThenStateChangedEvent_noLongerInCache()378     public void addWindowThenStateChangedEvent_noLongerInCache() {
379         putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
380                 DEFAULT_WINDOW_LAYER);
381         mAccessibilityCache.onAccessibilityEvent(
382                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
383         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
384     }
385 
386     @Test
addWindowThenWindowsChangedEvent_noLongerInCache()387     public void addWindowThenWindowsChangedEvent_noLongerInCache() {
388         putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
389                 DEFAULT_WINDOW_LAYER);
390         mAccessibilityCache.onAccessibilityEvent(
391                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED));
392         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
393     }
394 
395     @Test
addThenGetNode_returnsEquivalentNode()396     public void addThenGetNode_returnsEquivalentNode() {
397         AccessibilityNodeInfo nodeInfo, nodeCopy = null, nodeFromCache = null;
398         try {
399             nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
400             long id = nodeInfo.getSourceNodeId();
401             nodeCopy = AccessibilityNodeInfo.obtain(nodeInfo);
402             mAccessibilityCache.add(nodeInfo);
403             nodeInfo.recycle();
404             nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
405             assertEquals(nodeCopy, nodeFromCache);
406         } finally {
407             nodeFromCache.recycle();
408             nodeCopy.recycle();
409         }
410     }
411 
412     @Test
overwriteThenGetNode_returnsNewNode()413     public void overwriteThenGetNode_returnsNewNode() {
414         final CharSequence contentDescription1 = "foo";
415         final CharSequence contentDescription2 = "bar";
416         AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null, nodeFromCache = null;
417         try {
418             nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
419             nodeInfo1.setContentDescription(contentDescription1);
420             long id = nodeInfo1.getSourceNodeId();
421             nodeInfo2 = AccessibilityNodeInfo.obtain(nodeInfo1);
422             nodeInfo2.setContentDescription(contentDescription2);
423             mAccessibilityCache.add(nodeInfo1);
424             mAccessibilityCache.add(nodeInfo2);
425             nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
426             assertEquals(nodeInfo2, nodeFromCache);
427             assertEquals(contentDescription2, nodeFromCache.getContentDescription());
428         } finally {
429             nodeFromCache.recycle();
430             nodeInfo2.recycle();
431             nodeInfo1.recycle();
432         }
433     }
434 
435     @Test
nodesInDifferentWindowWithSameId_areKeptSeparate()436     public void nodesInDifferentWindowWithSameId_areKeptSeparate() {
437         final CharSequence contentDescription1 = "foo";
438         final CharSequence contentDescription2 = "bar";
439         AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null,
440                 node1FromCache = null, node2FromCache = null;
441         try {
442             nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
443             nodeInfo1.setContentDescription(contentDescription1);
444             long id = nodeInfo1.getSourceNodeId();
445             nodeInfo2 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_2);
446             nodeInfo2.setContentDescription(contentDescription2);
447             assertEquals(id, nodeInfo2.getSourceNodeId());
448             mAccessibilityCache.add(nodeInfo1);
449             mAccessibilityCache.add(nodeInfo2);
450             node1FromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
451             node2FromCache = mAccessibilityCache.getNode(WINDOW_ID_2, id);
452             assertEquals(nodeInfo1, node1FromCache);
453             assertEquals(nodeInfo2, node2FromCache);
454             assertEquals(nodeInfo1.getContentDescription(), node1FromCache.getContentDescription());
455             assertEquals(nodeInfo2.getContentDescription(), node2FromCache.getContentDescription());
456         } finally {
457             node1FromCache.recycle();
458             node2FromCache.recycle();
459             nodeInfo1.recycle();
460             nodeInfo2.recycle();
461         }
462     }
463 
464     @Test
addNodeThenClear_nodeIsRemoved()465     public void addNodeThenClear_nodeIsRemoved() {
466         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
467         long id = nodeInfo.getSourceNodeId();
468         mAccessibilityCache.add(nodeInfo);
469         nodeInfo.recycle();
470         mAccessibilityCache.clear();
471         assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, id));
472     }
473 
474     @Test
windowStateChangeAndWindowsChangedEvents_clearsNode()475     public void windowStateChangeAndWindowsChangedEvents_clearsNode() {
476         assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
477         assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
478     }
479 
480     @Test
windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache()481     public void windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache() {
482         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
483         mAccessibilityCache.add(nodeInfo);
484         AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
485         event.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED);
486         mAccessibilityCache.onAccessibilityEvent(event);
487         AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1,
488                 nodeInfo.getSourceNodeId());
489         try {
490             assertNotNull(cachedNode);
491         } finally {
492             nodeInfo.recycle();
493         }
494     }
495 
496     @Test
subTreeChangeEvent_clearsNodeAndChild()497     public void subTreeChangeEvent_clearsNodeAndChild() {
498         AccessibilityEvent event = AccessibilityEvent
499                 .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
500         event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
501         event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
502 
503         try {
504             assertEventClearsParentAndChild(event);
505         } finally {
506             event.recycle();
507         }
508     }
509 
510     @Test
subTreeChangeEventFromUncachedNode_clearsNodeInCache()511     public void subTreeChangeEventFromUncachedNode_clearsNodeInCache() {
512         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
513         long id = nodeInfo.getSourceNodeId();
514         mAccessibilityCache.add(nodeInfo);
515         nodeInfo.recycle();
516 
517         AccessibilityEvent event = AccessibilityEvent
518                 .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
519         event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
520         event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
521 
522         mAccessibilityCache.onAccessibilityEvent(event);
523         AccessibilityNodeInfo shouldBeNull = mAccessibilityCache.getNode(WINDOW_ID_1, id);
524         if (shouldBeNull != null) {
525             shouldBeNull.recycle();
526         }
527         assertNull(shouldBeNull);
528     }
529 
530     @Test
scrollEvent_clearsNodeAndChild()531     public void scrollEvent_clearsNodeAndChild() {
532         AccessibilityEvent event = AccessibilityEvent
533                 .obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
534         event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
535         try {
536             assertEventClearsParentAndChild(event);
537         } finally {
538             event.recycle();
539         }
540     }
541 
542     @Test
reparentNode_clearsOldParent()543     public void reparentNode_clearsOldParent() {
544         AccessibilityNodeInfo parentNodeInfo = getParentNode();
545         AccessibilityNodeInfo childNodeInfo = getChildNode();
546         long parentId = parentNodeInfo.getSourceNodeId();
547         mAccessibilityCache.add(parentNodeInfo);
548         mAccessibilityCache.add(childNodeInfo);
549 
550         childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID + 1, WINDOW_ID_1));
551         mAccessibilityCache.add(childNodeInfo);
552 
553         AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId);
554         try {
555             assertNull(parentFromCache);
556         } finally {
557             parentNodeInfo.recycle();
558             childNodeInfo.recycle();
559             if (parentFromCache != null) {
560                 parentFromCache.recycle();
561             }
562         }
563     }
564 
565     @Test
removeChildFromParent_clearsChild()566     public void removeChildFromParent_clearsChild() {
567         AccessibilityNodeInfo parentNodeInfo = getParentNode();
568         AccessibilityNodeInfo childNodeInfo = getChildNode();
569         long childId = childNodeInfo.getSourceNodeId();
570         mAccessibilityCache.add(parentNodeInfo);
571         mAccessibilityCache.add(childNodeInfo);
572 
573         AccessibilityNodeInfo parentNodeInfoWithNoChildren =
574                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
575         mAccessibilityCache.add(parentNodeInfoWithNoChildren);
576 
577         AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId);
578         try {
579             assertNull(childFromCache);
580         } finally {
581             parentNodeInfoWithNoChildren.recycle();
582             parentNodeInfo.recycle();
583             childNodeInfo.recycle();
584             if (childFromCache != null) {
585                 childFromCache.recycle();
586             }
587         }
588     }
589 
590     @Test
nodeSourceOfA11yFocusEvent_getsRefreshed()591     public void nodeSourceOfA11yFocusEvent_getsRefreshed() {
592         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
593         nodeInfo.setAccessibilityFocused(false);
594         mAccessibilityCache.add(nodeInfo);
595         AccessibilityEvent event = AccessibilityEvent.obtain(
596                 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
597         event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
598         mAccessibilityCache.onAccessibilityEvent(event);
599         event.recycle();
600         try {
601             verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
602         } finally {
603             nodeInfo.recycle();
604         }
605     }
606 
607     @Test
nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRemoved()608     public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRemoved() {
609         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
610         nodeInfo.setAccessibilityFocused(true);
611         mAccessibilityCache.add(nodeInfo);
612         AccessibilityEvent event = AccessibilityEvent.obtain(
613                 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
614         event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1));
615         mAccessibilityCache.onAccessibilityEvent(event);
616         event.recycle();
617         try {
618             assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
619         } finally {
620             nodeInfo.recycle();
621         }
622     }
623 
624     @Test
nodeWithA11yFocusClearsIt_refreshes()625     public void nodeWithA11yFocusClearsIt_refreshes() {
626         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
627         nodeInfo.setAccessibilityFocused(true);
628         mAccessibilityCache.add(nodeInfo);
629         AccessibilityEvent event = AccessibilityEvent.obtain(
630                 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
631         event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
632         mAccessibilityCache.onAccessibilityEvent(event);
633         event.recycle();
634         try {
635             verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
636         } finally {
637             nodeInfo.recycle();
638         }
639     }
640 
641     @Test
getFocus_focusedNodeAsInitialNode_returnsNodeWithA11yFocus()642     public void getFocus_focusedNodeAsInitialNode_returnsNodeWithA11yFocus() {
643         AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_ACCESSIBILITY);
644         assertFocus(nodeInfo, FOCUS_ACCESSIBILITY, nodeInfo.getSourceNodeId(),
645                 nodeInfo.getWindowId());
646     }
647 
648     @Test
getFocus_focusedNodeAsInitialNode_returnsNodeWithInputFocus()649     public void getFocus_focusedNodeAsInitialNode_returnsNodeWithInputFocus() {
650         AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_INPUT);
651         assertFocus(nodeInfo, FOCUS_INPUT, nodeInfo.getSourceNodeId(), nodeInfo.getWindowId());
652     }
653 
654 
655     @Test
getFocus_fromAnyWindow_returnsNodeWithA11yFocus()656     public void getFocus_fromAnyWindow_returnsNodeWithA11yFocus() {
657         AccessibilityNodeInfo parentNodeInfo =
658                 getNodeWithA11yAndWindowId(ROOT_ITEM_ID, WINDOW_ID_1);
659         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
660         nodeInfo.setParent(getMockViewWithA11yAndWindowIds(ROOT_ITEM_ID, WINDOW_ID_1));
661         setFocus(nodeInfo, FOCUS_ACCESSIBILITY);
662         mAccessibilityCache.add(parentNodeInfo);
663         mAccessibilityCache.add(nodeInfo);
664 
665         AccessibilityNodeInfo focusedNodeInfo =
666                 mAccessibilityCache.getFocus(FOCUS_ACCESSIBILITY, ROOT_NODE_ID, ANY_WINDOW_ID);
667         try {
668             assertEquals(focusedNodeInfo, nodeInfo);
669         } finally {
670             nodeInfo.recycle();
671             parentNodeInfo.recycle();
672         }
673     }
674 
675     @Test
getFocus_fromAnyWindow_returnsNodeWithInputFocus()676     public void getFocus_fromAnyWindow_returnsNodeWithInputFocus() {
677         AccessibilityNodeInfo parentNodeInfo =
678                 getNodeWithA11yAndWindowId(ROOT_ITEM_ID, WINDOW_ID_1);
679         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
680         nodeInfo.setParent(getMockViewWithA11yAndWindowIds(ROOT_ITEM_ID, WINDOW_ID_1));
681         setFocus(nodeInfo, FOCUS_INPUT);
682         mAccessibilityCache.add(parentNodeInfo);
683         mAccessibilityCache.add(nodeInfo);
684 
685         AccessibilityNodeInfo focusedNodeInfo =
686                 mAccessibilityCache.getFocus(FOCUS_INPUT, ROOT_NODE_ID, ANY_WINDOW_ID);
687         try {
688             assertEquals(focusedNodeInfo, nodeInfo);
689         } finally {
690             nodeInfo.recycle();
691             parentNodeInfo.recycle();
692         }
693     }
694 
695 
696     @Test
getFocus_parentNodeAsInitialNode_returnsNodeWithA11yFocus()697     public void getFocus_parentNodeAsInitialNode_returnsNodeWithA11yFocus() {
698         findFocusReturnsFocus_initialNodeIsParent(FOCUS_ACCESSIBILITY);
699     }
700 
701     @Test
getFocus_parentNodeAsInitialNode_returnsNodeWithInputFocus()702     public void getFocus_parentNodeAsInitialNode_returnsNodeWithInputFocus() {
703         findFocusReturnsFocus_initialNodeIsParent(FOCUS_INPUT);
704 
705     }
706 
707     @Test
getFocus_nonFocusedChildNodeAsInitialNode_returnsNullA11yFocus()708     public void getFocus_nonFocusedChildNodeAsInitialNode_returnsNullA11yFocus() {
709         findFocusReturnNull_initialNodeIsNotFocusOrFocusAncestor(FOCUS_ACCESSIBILITY);
710     }
711 
712     @Test
getFocus_nonFocusedChildNodeAsInitialNode_returnsNullInputFocus()713     public void getFocus_nonFocusedChildNodeAsInitialNode_returnsNullInputFocus() {
714         findFocusReturnNull_initialNodeIsNotFocusOrFocusAncestor(FOCUS_INPUT);
715     }
716 
717     @Test
getFocus_cacheCleared_returnsNullA11yFocus()718     public void getFocus_cacheCleared_returnsNullA11yFocus() {
719         AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_ACCESSIBILITY);
720         mAccessibilityCache.clear();
721         assertFocus(null, FOCUS_ACCESSIBILITY, nodeInfo.getSourceNodeId(),
722                 nodeInfo.getWindowId());
723     }
724 
725     @Test
getFocus_cacheCleared_returnsNullInputFocus()726     public void getFocus_cacheCleared_returnsNullInputFocus() {
727         AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_INPUT);
728         mAccessibilityCache.clear();
729         assertFocus(null, FOCUS_INPUT, nodeInfo.getSourceNodeId(), nodeInfo.getWindowId());
730     }
731 
732     @Test
nodeSourceOfInputFocusEvent_getsRefreshed()733     public void nodeSourceOfInputFocusEvent_getsRefreshed() {
734         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
735         nodeInfo.setFocused(false);
736         mAccessibilityCache.add(nodeInfo);
737         AccessibilityEvent event = AccessibilityEvent.obtain(
738                 AccessibilityEvent.TYPE_VIEW_FOCUSED);
739         event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
740         mAccessibilityCache.onAccessibilityEvent(event);
741         event.recycle();
742         try {
743             verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
744         } finally {
745             nodeInfo.recycle();
746         }
747     }
748 
749     @Test
nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRemoved()750     public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRemoved() {
751         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
752         nodeInfo.setFocused(true);
753         mAccessibilityCache.add(nodeInfo);
754         AccessibilityEvent event = AccessibilityEvent.obtain(
755                 AccessibilityEvent.TYPE_VIEW_FOCUSED);
756         event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1));
757         mAccessibilityCache.onAccessibilityEvent(event);
758         event.recycle();
759         try {
760             assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
761         } finally {
762             nodeInfo.recycle();
763         }
764     }
765 
766     @Test
nodeEventSaysWasSelected_getsRefreshed()767     public void nodeEventSaysWasSelected_getsRefreshed() {
768         assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_SELECTED,
769                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
770     }
771 
772     @Test
nodeEventSaysHadTextChanged_getsRefreshed()773     public void nodeEventSaysHadTextChanged_getsRefreshed() {
774         assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
775                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
776     }
777 
778     @Test
nodeEventSaysWasClicked_getsRefreshed()779     public void nodeEventSaysWasClicked_getsRefreshed() {
780         assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_CLICKED,
781                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
782     }
783 
784     @Test
nodeEventSaysHadSelectionChange_getsRefreshed()785     public void nodeEventSaysHadSelectionChange_getsRefreshed() {
786         assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
787                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
788     }
789 
790     @Test
nodeEventSaysHadTextContentChange_getsRefreshed()791     public void nodeEventSaysHadTextContentChange_getsRefreshed() {
792         assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
793                 AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
794     }
795 
796     @Test
nodeEventSaysHadContentDescriptionChange_getsRefreshed()797     public void nodeEventSaysHadContentDescriptionChange_getsRefreshed() {
798         assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
799                 AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
800     }
801 
802     @Test
addNode_whenNodeBeingReplacedIsOwnGrandparentWithTwoChildren_doesntCrash()803     public void addNode_whenNodeBeingReplacedIsOwnGrandparentWithTwoChildren_doesntCrash() {
804         AccessibilityNodeInfo parentNodeInfo =
805                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
806         parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
807         parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(OTHER_CHILD_VIEW_ID, WINDOW_ID_1));
808         AccessibilityNodeInfo childNodeInfo =
809                 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
810         childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
811         childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
812 
813         AccessibilityNodeInfo replacementParentNodeInfo =
814                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
815         try {
816             mAccessibilityCache.add(parentNodeInfo);
817             mAccessibilityCache.add(childNodeInfo);
818             mAccessibilityCache.add(replacementParentNodeInfo);
819         } finally {
820             parentNodeInfo.recycle();
821             childNodeInfo.recycle();
822             replacementParentNodeInfo.recycle();
823         }
824     }
825 
826     @Test
addNode_whenNodeBeingReplacedIsOwnGrandparentWithOneChild_doesntCrash()827     public void addNode_whenNodeBeingReplacedIsOwnGrandparentWithOneChild_doesntCrash() {
828         AccessibilityNodeInfo parentNodeInfo =
829                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
830         parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
831         AccessibilityNodeInfo childNodeInfo =
832                 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
833         childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
834         childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
835 
836         AccessibilityNodeInfo replacementParentNodeInfo =
837                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
838         try {
839             mAccessibilityCache.add(parentNodeInfo);
840             mAccessibilityCache.add(childNodeInfo);
841             mAccessibilityCache.add(replacementParentNodeInfo);
842         } finally {
843             parentNodeInfo.recycle();
844             childNodeInfo.recycle();
845             replacementParentNodeInfo.recycle();
846         }
847     }
848 
849     @Test
testCacheCriticalEventList_doesntLackEvents()850     public void testCacheCriticalEventList_doesntLackEvents() {
851         for (int i = 0; i < 32; i++) {
852             int eventType = 1 << i;
853             if ((eventType & AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK) == 0) {
854                 try {
855                     assertEventTypeClearsNode(eventType, false);
856                     verify(mAccessibilityNodeRefresher, never())
857                             .refreshNode(anyObject(), anyBoolean());
858                 } catch (Throwable e) {
859                     throw new AssertionError(
860                             "Failed for eventType: " + AccessibilityEvent.eventTypeToString(
861                                     eventType),
862                             e);
863                 }
864             }
865         }
866     }
867 
868     @Test
addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRemoved()869     public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRemoved() {
870         AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
871         nodeInfo1.setAccessibilityFocused(true);
872         mAccessibilityCache.add(nodeInfo1);
873         AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1);
874         nodeInfo2.setAccessibilityFocused(true);
875         mAccessibilityCache.add(nodeInfo2);
876         try {
877             assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
878         } finally {
879             nodeInfo1.recycle();
880             nodeInfo2.recycle();
881         }
882     }
883 
884     @Test
addSameParentNodeWithDifferentChildNode_whenOriginalChildHasChild_doesntCrash()885     public void addSameParentNodeWithDifferentChildNode_whenOriginalChildHasChild_doesntCrash() {
886         AccessibilityNodeInfo parentNodeInfo = getParentNode();
887         AccessibilityNodeInfo childNodeInfo = getChildNode();
888         childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID + 1, WINDOW_ID_1));
889 
890         AccessibilityNodeInfo replacementParentNodeInfo =
891                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
892         replacementParentNodeInfo.addChild(
893                 getMockViewWithA11yAndWindowIds(OTHER_CHILD_VIEW_ID, WINDOW_ID_1));
894         try {
895             mAccessibilityCache.add(parentNodeInfo);
896             mAccessibilityCache.add(childNodeInfo);
897             mAccessibilityCache.add(replacementParentNodeInfo);
898         } catch (IllegalStateException e) {
899             fail("recycle A11yNodeInfo twice" + Throwables.getStackTraceAsString(e));
900         } finally {
901             parentNodeInfo.recycle();
902             childNodeInfo.recycle();
903             replacementParentNodeInfo.recycle();
904         }
905     }
906 
assertNodeIsRefreshedWithEventType(int eventType, int contentChangeTypes)907     private void assertNodeIsRefreshedWithEventType(int eventType, int contentChangeTypes) {
908         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
909         mAccessibilityCache.add(nodeInfo);
910         AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
911         event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
912         event.setContentChangeTypes(contentChangeTypes);
913         mAccessibilityCache.onAccessibilityEvent(event);
914         event.recycle();
915         try {
916             verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
917         } finally {
918             nodeInfo.recycle();
919         }
920     }
921 
addFocusedNode(int focusType)922     private AccessibilityNodeInfo addFocusedNode(int focusType) {
923         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
924         setFocus(nodeInfo, focusType);
925         mAccessibilityCache.add(nodeInfo);
926         return nodeInfo;
927     }
928 
assertFocus(AccessibilityNodeInfo focus, int focusType, long nodeId, int windowId)929     private void assertFocus(AccessibilityNodeInfo focus, int focusType, long nodeId,
930             int windowId) {
931         AccessibilityNodeInfo focusedNodeInfo = mAccessibilityCache.getFocus(
932                 focusType, nodeId, windowId);
933         try {
934             assertEquals(focusedNodeInfo, focus);
935         } finally {
936             if (focus != null) {
937                 focus.recycle();
938             }
939             if (focusedNodeInfo != null) {
940                 focusedNodeInfo.recycle();
941             }
942         }
943     }
944 
945 
findFocusReturnNull_initialNodeIsNotFocusOrFocusAncestor(int focusType)946     private void findFocusReturnNull_initialNodeIsNotFocusOrFocusAncestor(int focusType) {
947         AccessibilityNodeInfo parentNodeInfo =
948                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
949         AccessibilityNodeInfo childNodeInfo =
950                 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
951         AccessibilityNodeInfo otherChildNodeInfo =
952                 getNodeWithA11yAndWindowId(OTHER_CHILD_VIEW_ID, WINDOW_ID_1);
953         childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
954         otherChildNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
955         setFocus(childNodeInfo, focusType);
956         mAccessibilityCache.add(parentNodeInfo);
957         mAccessibilityCache.add(childNodeInfo);
958         mAccessibilityCache.add(otherChildNodeInfo);
959 
960         AccessibilityNodeInfo focusedNodeInfo = mAccessibilityCache.getFocus(
961                 focusType, otherChildNodeInfo.getSourceNodeId(), WINDOW_ID_1);
962 
963         try {
964             assertNull(focusedNodeInfo);
965 
966         } finally {
967             parentNodeInfo.recycle();
968             childNodeInfo.recycle();
969         }
970     }
971 
findFocusReturnsFocus_initialNodeIsParent(int focusType)972     private void findFocusReturnsFocus_initialNodeIsParent(int focusType) {
973         AccessibilityNodeInfo parentNodeInfo =
974                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
975         AccessibilityNodeInfo childNodeInfo =
976                 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
977         childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
978         setFocus(childNodeInfo, focusType);
979         mAccessibilityCache.add(parentNodeInfo);
980         mAccessibilityCache.add(childNodeInfo);
981 
982         AccessibilityNodeInfo focusedNodeInfo = mAccessibilityCache.getFocus(
983                 focusType, parentNodeInfo.getSourceNodeId(), WINDOW_ID_1);
984 
985         try {
986             assertEquals(focusedNodeInfo, childNodeInfo);
987 
988         } finally {
989             parentNodeInfo.recycle();
990             childNodeInfo.recycle();
991         }
992     }
993 
setFocus(AccessibilityNodeInfo info, int focusType)994     private void setFocus(AccessibilityNodeInfo info, int focusType) {
995         if (focusType == FOCUS_ACCESSIBILITY) {
996             info.setAccessibilityFocused(true);
997         } else {
998             info.setFocused(true);
999         }
1000     }
1001 
1002     @Test
enable_cacheEnabled()1003     public void enable_cacheEnabled() {
1004         mAccessibilityCache.setEnabled(false);
1005         assertFalse(mAccessibilityCache.isEnabled());
1006 
1007         mAccessibilityCache.setEnabled(true);
1008         assertTrue(mAccessibilityCache.isEnabled());
1009     }
1010 
1011     @Test
disable_cacheDisabled()1012     public void disable_cacheDisabled() {
1013         mAccessibilityCache.setEnabled(false);
1014         assertFalse(mAccessibilityCache.isEnabled());
1015     }
1016 
1017     @Test
queryNode_nodeIsInCache()1018     public void queryNode_nodeIsInCache() {
1019         AccessibilityNodeInfo info = new AccessibilityNodeInfo();
1020         mAccessibilityCache.add(info);
1021 
1022         assertTrue(mAccessibilityCache.isNodeInCache(info));
1023     }
1024 
1025     @Test
clearSubtreeWithNode_nodeInCacheInvalidated()1026     public void clearSubtreeWithNode_nodeInCacheInvalidated() {
1027         AccessibilityNodeInfo info = new AccessibilityNodeInfo();
1028         info.setSource(getMockViewWithA11yAndWindowIds(1, 1));
1029         mAccessibilityCache.add(info);
1030 
1031         mAccessibilityCache.clearSubTree(info);
1032         assertFalse(mAccessibilityCache.isNodeInCache(info));
1033     }
1034 
1035     @Test
clearSubtreeWithNode_subtreeInCacheInvalidated()1036     public void clearSubtreeWithNode_subtreeInCacheInvalidated() {
1037         AccessibilityNodeInfo info = new AccessibilityNodeInfo();
1038         View parentView = getMockViewWithA11yAndWindowIds(1, 1);
1039         info.setSource(parentView);
1040 
1041         AccessibilityNodeInfo childInfo = new AccessibilityNodeInfo();
1042         View childView = getMockViewWithA11yAndWindowIds(2, 1);
1043         childInfo.setSource(childView);
1044 
1045         childInfo.setParent(parentView);
1046         info.addChild(childView);
1047         mAccessibilityCache.add(info);
1048         mAccessibilityCache.add(childInfo);
1049 
1050         mAccessibilityCache.clearSubTree(info);
1051 
1052         assertFalse(mAccessibilityCache.isNodeInCache(info));
1053         assertFalse(mAccessibilityCache.isNodeInCache(childInfo));
1054     }
1055 
obtainAccessibilityWindowInfo(int windowId, int layer)1056     private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
1057         AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
1058         windowInfo.setId(windowId);
1059         windowInfo.setLayer(layer);
1060         return windowInfo;
1061     }
1062 
putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer)1063     private void putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer) {
1064         AccessibilityWindowInfo windowInfo = obtainAccessibilityWindowInfo(windowId, layer);
1065         windowInfo.setDisplayId(displayId);
1066         mAccessibilityCache.addWindow(windowInfo);
1067         windowInfo.recycle();
1068     }
1069 
getNodeWithA11yAndWindowId(int a11yId, int windowId)1070     private AccessibilityNodeInfo getNodeWithA11yAndWindowId(int a11yId, int windowId) {
1071         AccessibilityNodeInfo node =
1072                 AccessibilityNodeInfo.obtain(getMockViewWithA11yAndWindowIds(a11yId, windowId));
1073         node.setConnectionId(MOCK_CONNECTION_ID);
1074         return node;
1075     }
1076 
getMockViewWithA11yAndWindowIds(int a11yId, int windowId)1077     private View getMockViewWithA11yAndWindowIds(int a11yId, int windowId) {
1078         View mockView = mock(View.class);
1079         when(mockView.getAccessibilityViewId()).thenReturn(a11yId);
1080         when(mockView.getAccessibilityWindowId()).thenReturn(windowId);
1081         doAnswer(new Answer<AccessibilityNodeInfo>() {
1082             public AccessibilityNodeInfo answer(InvocationOnMock invocation) {
1083                 return AccessibilityNodeInfo.obtain((View) invocation.getMock());
1084             }
1085         }).when(mockView).createAccessibilityNodeInfo();
1086         return mockView;
1087     }
1088 
assertEventTypeClearsNode(int eventType)1089     private void assertEventTypeClearsNode(int eventType) {
1090         assertEventTypeClearsNode(eventType, true);
1091     }
1092 
assertEventTypeClearsNode(int eventType, boolean clears)1093     private void assertEventTypeClearsNode(int eventType, boolean clears) {
1094         final int nodeId = 0xBEEF;
1095         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(nodeId, WINDOW_ID_1);
1096         long id = nodeInfo.getSourceNodeId();
1097         mAccessibilityCache.add(nodeInfo);
1098         nodeInfo.recycle();
1099         mAccessibilityCache.onAccessibilityEvent(AccessibilityEvent.obtain(eventType));
1100         AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1, id);
1101         try {
1102             if (clears) {
1103                 assertNull(cachedNode);
1104             } else {
1105                 assertNotNull(cachedNode);
1106             }
1107         } finally {
1108             if (cachedNode != null) {
1109                 cachedNode.recycle();
1110             }
1111         }
1112     }
1113 
getParentNode()1114     private AccessibilityNodeInfo getParentNode() {
1115         AccessibilityNodeInfo parentNodeInfo =
1116                 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
1117         parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
1118         return parentNodeInfo;
1119     }
1120 
getChildNode()1121     private AccessibilityNodeInfo getChildNode() {
1122         AccessibilityNodeInfo childNodeInfo =
1123                 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
1124         childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
1125         return childNodeInfo;
1126     }
1127 
assertEventClearsParentAndChild(AccessibilityEvent event)1128     private void assertEventClearsParentAndChild(AccessibilityEvent event) {
1129         AccessibilityNodeInfo parentNodeInfo = getParentNode();
1130         AccessibilityNodeInfo childNodeInfo = getChildNode();
1131         long parentId = parentNodeInfo.getSourceNodeId();
1132         long childId = childNodeInfo.getSourceNodeId();
1133         mAccessibilityCache.add(parentNodeInfo);
1134         mAccessibilityCache.add(childNodeInfo);
1135 
1136         mAccessibilityCache.onAccessibilityEvent(event);
1137         parentNodeInfo.recycle();
1138         childNodeInfo.recycle();
1139 
1140         AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId);
1141         AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId);
1142         try {
1143             assertNull(parentFromCache);
1144             assertNull(childFromCache);
1145         } finally {
1146             if (parentFromCache != null) {
1147                 parentFromCache.recycle();
1148             }
1149             if (childFromCache != null) {
1150                 childFromCache.recycle();
1151             }
1152         }
1153     }
1154 
setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows)1155     private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows) {
1156         setWindowsByDisplay(displayId, windows, SystemClock.uptimeMillis());
1157     }
1158 
setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows, long populationTimeStamp)1159     private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows,
1160             long populationTimeStamp) {
1161         SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
1162         allWindows.put(displayId, windows);
1163         mAccessibilityCache.setWindowsOnAllDisplays(allWindows, populationTimeStamp);
1164     }
1165 
getWindowsByDisplay(int displayId)1166     private List<AccessibilityWindowInfo> getWindowsByDisplay(int displayId) {
1167         final SparseArray<List<AccessibilityWindowInfo>> allWindows =
1168                 mAccessibilityCache.getWindowsOnAllDisplays();
1169 
1170         if (allWindows != null && allWindows.size() > 0) {
1171             return allWindows.get(displayId);
1172         }
1173         return null;
1174     }
1175 }
1176