AccessibilityCacheTest.java revision 62d20fabf239e26e7dc6bbb2fbf0b1118a51aff0
1/*
2 * Copyright (C) 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
17package com.android.server.accessibility;
18
19import static junit.framework.Assert.assertEquals;
20import static junit.framework.Assert.assertNull;
21import static org.mockito.Matchers.anyBoolean;
22import static org.mockito.Matchers.anyObject;
23import static org.mockito.Mockito.doAnswer;
24import static org.mockito.Mockito.mock;
25import static org.mockito.Mockito.verify;
26import static org.mockito.Mockito.when;
27
28import android.support.test.runner.AndroidJUnit4;
29import android.view.accessibility.AccessibilityCache;
30import android.view.accessibility.AccessibilityEvent;
31import android.view.accessibility.AccessibilityInteractionClient;
32import android.view.accessibility.AccessibilityNodeInfo;
33import android.view.accessibility.AccessibilityWindowInfo;
34import android.view.View;
35import org.junit.After;
36import org.junit.Before;
37import org.junit.Test;
38import org.junit.runner.RunWith;
39import org.mockito.invocation.InvocationOnMock;
40import org.mockito.stubbing.Answer;
41
42import java.util.Arrays;
43import java.util.List;
44import java.util.concurrent.atomic.AtomicInteger;
45
46
47@RunWith(AndroidJUnit4.class)
48public class AccessibilityCacheTest {
49    private static int WINDOW_ID_1 = 0xBEEF;
50    private static int WINDOW_ID_2 = 0xFACE;
51    private static int SINGLE_VIEW_ID = 0xCAFE;
52    private static int OTHER_VIEW_ID = 0xCAB2;
53    private static int PARENT_VIEW_ID = 0xFED4;
54    private static int CHILD_VIEW_ID = 0xFEED;
55    private static int MOCK_CONNECTION_ID = 1;
56
57    AccessibilityCache mAccessibilityCache;
58    AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
59    AtomicInteger numA11yNodeInfosInUse = new AtomicInteger(0);
60    AtomicInteger numA11yWinInfosInUse = new AtomicInteger(0);
61
62    @Before
63    public void setUp() {
64        mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class);
65        when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true);
66        mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
67        AccessibilityNodeInfo.setNumInstancesInUseCounter(numA11yNodeInfosInUse);
68        AccessibilityWindowInfo.setNumInstancesInUseCounter(numA11yWinInfosInUse);
69    }
70
71    @After
72    public void tearDown() {
73        // Make sure we're recycling all of our window and node infos
74        mAccessibilityCache.clear();
75        AccessibilityInteractionClient.getInstance().clearCache();
76        assertEquals(0, numA11yWinInfosInUse.get());
77        assertEquals(0, numA11yNodeInfosInUse.get());
78    }
79
80    @Test
81    public void testEmptyCache_returnsNull() {
82        assertNull(mAccessibilityCache.getNode(0, 0));
83        assertNull(mAccessibilityCache.getWindows());
84        assertNull(mAccessibilityCache.getWindow(0));
85    }
86
87    @Test
88    public void testEmptyCache_clearDoesntCrash() {
89        mAccessibilityCache.clear();
90    }
91
92    @Test
93    public void testEmptyCache_a11yEventsHaveNoEffect() {
94        AccessibilityEvent event = AccessibilityEvent.obtain();
95        int[] a11yEventTypes = {
96                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
97                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
98                AccessibilityEvent.TYPE_VIEW_FOCUSED,
99                AccessibilityEvent.TYPE_VIEW_SELECTED,
100                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
101                AccessibilityEvent.TYPE_VIEW_CLICKED,
102                AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
103                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
104                AccessibilityEvent.TYPE_VIEW_SCROLLED,
105                AccessibilityEvent.TYPE_WINDOWS_CHANGED,
106                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED};
107        for (int i = 0; i < a11yEventTypes.length; i++) {
108            event.setEventType(a11yEventTypes[i]);
109            mAccessibilityCache.onAccessibilityEvent(event);
110        }
111    }
112
113    @Test
114    public void addThenGetWindow_returnsEquivalentButNotSameWindow() {
115        AccessibilityWindowInfo windowInfo = null, copyOfInfo = null, windowFromCache = null;
116        try {
117            windowInfo = AccessibilityWindowInfo.obtain();
118            windowInfo.setId(WINDOW_ID_1);
119            mAccessibilityCache.addWindow(windowInfo);
120            // Make a copy
121            copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo);
122            windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info
123            windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1);
124            assertEquals(copyOfInfo, windowFromCache);
125        } finally {
126            windowFromCache.recycle();
127            windowInfo.recycle();
128            copyOfInfo.recycle();
129        }
130    }
131
132    @Test
133    public void addWindowThenClear_noLongerInCache() {
134        putWindowWithIdInCache(WINDOW_ID_1);
135        mAccessibilityCache.clear();
136        assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
137    }
138
139    @Test
140    public void addWindowGetOtherId_returnsNull() {
141        putWindowWithIdInCache(WINDOW_ID_1);
142        assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1));
143    }
144
145    @Test
146    public void addWindowThenGetWindows_returnsNull() {
147        putWindowWithIdInCache(WINDOW_ID_1);
148        assertNull(mAccessibilityCache.getWindows());
149    }
150
151    @Test
152    public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() {
153        AccessibilityWindowInfo windowInfo1 = null, windowInfo2 = null;
154        AccessibilityWindowInfo window1Out = null, window2Out = null;
155        List<AccessibilityWindowInfo> windowsOut = null;
156        try {
157            windowInfo1 = AccessibilityWindowInfo.obtain();
158            windowInfo1.setId(WINDOW_ID_1);
159            windowInfo1.setLayer(5);
160            windowInfo2 = AccessibilityWindowInfo.obtain();
161            windowInfo2.setId(WINDOW_ID_2);
162            windowInfo2.setLayer(windowInfo1.getLayer() + 1);
163            List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
164            mAccessibilityCache.setWindows(windowsIn);
165
166            windowsOut = mAccessibilityCache.getWindows();
167            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
168            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
169
170            assertEquals(2, windowsOut.size());
171            assertEquals(windowInfo2, windowsOut.get(0));
172            assertEquals(windowInfo1, windowsOut.get(1));
173            assertEquals(windowInfo1, window1Out);
174            assertEquals(windowInfo2, window2Out);
175        } finally {
176            window1Out.recycle();
177            window2Out.recycle();
178            windowInfo1.recycle();
179            windowInfo2.recycle();
180            for (AccessibilityWindowInfo windowInfo : windowsOut) {
181                windowInfo.recycle();
182            }
183        }
184    }
185
186    @Test
187    public void addWindowThenStateChangedEvent_noLongerInCache() {
188        putWindowWithIdInCache(WINDOW_ID_1);
189        mAccessibilityCache.onAccessibilityEvent(
190                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
191        assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
192    }
193
194    @Test
195    public void addWindowThenWindowsChangedEvent_noLongerInCache() {
196        putWindowWithIdInCache(WINDOW_ID_1);
197        mAccessibilityCache.onAccessibilityEvent(
198                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED));
199        assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
200    }
201
202    @Test
203    public void addThenGetNode_returnsEquivalentNode() {
204        AccessibilityNodeInfo nodeInfo, nodeCopy = null, nodeFromCache = null;
205        try {
206            nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
207            long id = nodeInfo.getSourceNodeId();
208            nodeCopy = AccessibilityNodeInfo.obtain(nodeInfo);
209            mAccessibilityCache.add(nodeInfo);
210            nodeInfo.recycle();
211            nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
212            assertEquals(nodeCopy, nodeFromCache);
213        } finally {
214            nodeFromCache.recycle();
215            nodeCopy.recycle();
216        }
217    }
218
219    @Test
220    public void overwriteThenGetNode_returnsNewNode() {
221        final CharSequence contentDescription1 = "foo";
222        final CharSequence contentDescription2 = "bar";
223        AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null, nodeFromCache = null;
224        try {
225            nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
226            nodeInfo1.setContentDescription(contentDescription1);
227            long id = nodeInfo1.getSourceNodeId();
228            nodeInfo2 = AccessibilityNodeInfo.obtain(nodeInfo1);
229            nodeInfo2.setContentDescription(contentDescription2);
230            mAccessibilityCache.add(nodeInfo1);
231            mAccessibilityCache.add(nodeInfo2);
232            nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
233            assertEquals(nodeInfo2, nodeFromCache);
234            assertEquals(contentDescription2, nodeFromCache.getContentDescription());
235        } finally {
236            nodeFromCache.recycle();
237            nodeInfo2.recycle();
238            nodeInfo1.recycle();
239        }
240    }
241
242    @Test
243    public void nodesInDifferentWindowWithSameId_areKeptSeparate() {
244        final CharSequence contentDescription1 = "foo";
245        final CharSequence contentDescription2 = "bar";
246        AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null,
247                node1FromCache = null, node2FromCache = null;
248        try {
249            nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
250            nodeInfo1.setContentDescription(contentDescription1);
251            long id = nodeInfo1.getSourceNodeId();
252            nodeInfo2 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_2);
253            nodeInfo2.setContentDescription(contentDescription2);
254            assertEquals(id, nodeInfo2.getSourceNodeId());
255            mAccessibilityCache.add(nodeInfo1);
256            mAccessibilityCache.add(nodeInfo2);
257            node1FromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
258            node2FromCache = mAccessibilityCache.getNode(WINDOW_ID_2, id);
259            assertEquals(nodeInfo1, node1FromCache);
260            assertEquals(nodeInfo2, node2FromCache);
261            assertEquals(nodeInfo1.getContentDescription(), node1FromCache.getContentDescription());
262            assertEquals(nodeInfo2.getContentDescription(), node2FromCache.getContentDescription());
263        } finally {
264            node1FromCache.recycle();
265            node2FromCache.recycle();
266            nodeInfo1.recycle();
267            nodeInfo2.recycle();
268        }
269    }
270
271    @Test
272    public void addNodeThenClear_nodeIsRemoved() {
273        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
274        long id = nodeInfo.getSourceNodeId();
275        mAccessibilityCache.add(nodeInfo);
276        nodeInfo.recycle();
277        mAccessibilityCache.clear();
278        assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, id));
279    }
280
281    @Test
282    public void windowStateChangeAndWindowsChangedEvents_clearsNode() {
283        assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
284        assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
285    }
286
287    @Test
288    public void subTreeChangeEvent_clearsNodeAndChild() {
289        AccessibilityEvent event = AccessibilityEvent
290                .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
291        event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
292        event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
293
294        try {
295            assertEventClearsParentAndChild(event);
296        } finally {
297            event.recycle();
298        }
299    }
300
301    @Test
302    public void scrollEvent_clearsNodeAndChild() {
303        AccessibilityEvent event = AccessibilityEvent
304                .obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
305        event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
306        try {
307            assertEventClearsParentAndChild(event);
308        } finally {
309            event.recycle();
310        }
311    }
312
313    @Test
314    public void reparentNode_clearsOldParent() {
315        AccessibilityNodeInfo parentNodeInfo = getParentNode();
316        AccessibilityNodeInfo childNodeInfo = getChildNode();
317        long parentId = parentNodeInfo.getSourceNodeId();
318        mAccessibilityCache.add(parentNodeInfo);
319        mAccessibilityCache.add(childNodeInfo);
320
321        childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID + 1, WINDOW_ID_1));
322        mAccessibilityCache.add(childNodeInfo);
323
324        AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId);
325        try {
326            assertNull(parentFromCache);
327        } finally {
328            parentNodeInfo.recycle();
329            childNodeInfo.recycle();
330            if (parentFromCache != null) {
331                parentFromCache.recycle();
332            }
333        }
334    }
335
336    @Test
337    public void removeChildFromParent_clearsChild() {
338        AccessibilityNodeInfo parentNodeInfo = getParentNode();
339        AccessibilityNodeInfo childNodeInfo = getChildNode();
340        long childId = childNodeInfo.getSourceNodeId();
341        mAccessibilityCache.add(parentNodeInfo);
342        mAccessibilityCache.add(childNodeInfo);
343
344        AccessibilityNodeInfo parentNodeInfoWithNoChildren =
345                getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
346        mAccessibilityCache.add(parentNodeInfoWithNoChildren);
347
348        AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId);
349        try {
350            assertNull(childFromCache);
351        } finally {
352            parentNodeInfoWithNoChildren.recycle();
353            parentNodeInfo.recycle();
354            childNodeInfo.recycle();
355            if (childFromCache != null) {
356                childFromCache.recycle();
357            }
358        }
359    }
360
361    @Test
362    public void nodeSourceOfA11yFocusEvent_getsRefreshed() {
363        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
364        nodeInfo.setAccessibilityFocused(false);
365        mAccessibilityCache.add(nodeInfo);
366        AccessibilityEvent event = AccessibilityEvent.obtain(
367                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
368        event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
369        mAccessibilityCache.onAccessibilityEvent(event);
370        event.recycle();
371        try {
372            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
373        } finally {
374            nodeInfo.recycle();
375        }
376    }
377
378    @Test
379    public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRefreshed() {
380        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
381        nodeInfo.setAccessibilityFocused(true);
382        mAccessibilityCache.add(nodeInfo);
383        AccessibilityEvent event = AccessibilityEvent.obtain(
384                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
385        event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1));
386        mAccessibilityCache.onAccessibilityEvent(event);
387        event.recycle();
388        try {
389            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
390        } finally {
391            nodeInfo.recycle();
392        }
393    }
394
395    @Test
396    public void nodeWithA11yFocusClearsIt_refreshes() {
397        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
398        nodeInfo.setAccessibilityFocused(true);
399        mAccessibilityCache.add(nodeInfo);
400        AccessibilityEvent event = AccessibilityEvent.obtain(
401                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
402        event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
403        mAccessibilityCache.onAccessibilityEvent(event);
404        event.recycle();
405        try {
406            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
407        } finally {
408            nodeInfo.recycle();
409        }
410    }
411
412    @Test
413    public void nodeSourceOfInputFocusEvent_getsRefreshed() {
414        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
415        nodeInfo.setFocused(false);
416        mAccessibilityCache.add(nodeInfo);
417        AccessibilityEvent event = AccessibilityEvent.obtain(
418                AccessibilityEvent.TYPE_VIEW_FOCUSED);
419        event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
420        mAccessibilityCache.onAccessibilityEvent(event);
421        event.recycle();
422        try {
423            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
424        } finally {
425            nodeInfo.recycle();
426        }
427    }
428
429    @Test
430    public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRefreshed() {
431        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
432        nodeInfo.setFocused(true);
433        mAccessibilityCache.add(nodeInfo);
434        AccessibilityEvent event = AccessibilityEvent.obtain(
435                AccessibilityEvent.TYPE_VIEW_FOCUSED);
436        event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1));
437        mAccessibilityCache.onAccessibilityEvent(event);
438        event.recycle();
439        try {
440            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
441        } finally {
442            nodeInfo.recycle();
443        }
444    }
445
446    @Test
447    public void nodeEventSaysWasSelected_getsRefreshed() {
448        assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_SELECTED,
449                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
450    }
451
452    @Test
453    public void nodeEventSaysHadTextChanged_getsRefreshed() {
454        assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
455                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
456    }
457
458    @Test
459    public void nodeEventSaysWasClicked_getsRefreshed() {
460        assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_CLICKED,
461                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
462    }
463
464    @Test
465    public void nodeEventSaysHadSelectionChange_getsRefreshed() {
466        assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
467                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
468    }
469
470    @Test
471    public void nodeEventSaysHadTextContentChange_getsRefreshed() {
472        assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
473                AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
474    }
475
476    @Test
477    public void nodeEventSaysHadContentDescriptionChange_getsRefreshed() {
478        assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
479                AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
480    }
481
482    private void assertNodeIsRefreshedWithEventType(int eventType, int contentChangeTypes) {
483        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
484        mAccessibilityCache.add(nodeInfo);
485        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
486        event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
487        event.setContentChangeTypes(contentChangeTypes);
488        mAccessibilityCache.onAccessibilityEvent(event);
489        event.recycle();
490        try {
491            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
492        } finally {
493            nodeInfo.recycle();
494        }
495    }
496
497    private void putWindowWithIdInCache(int id) {
498        AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
499        windowInfo.setId(id);
500        mAccessibilityCache.addWindow(windowInfo);
501        windowInfo.recycle();
502    }
503
504    private AccessibilityNodeInfo getNodeWithA11yAndWindowId(int a11yId, int windowId) {
505        AccessibilityNodeInfo node =
506                AccessibilityNodeInfo.obtain(getMockViewWithA11yAndWindowIds(a11yId, windowId));
507        node.setConnectionId(MOCK_CONNECTION_ID);
508        return node;
509    }
510
511    private View getMockViewWithA11yAndWindowIds(int a11yId, int windowId) {
512        View mockView = mock(View.class);
513        when(mockView.getAccessibilityViewId()).thenReturn(a11yId);
514        when(mockView.getAccessibilityWindowId()).thenReturn(windowId);
515        doAnswer(new Answer<AccessibilityNodeInfo>() {
516            public AccessibilityNodeInfo answer(InvocationOnMock invocation) {
517                return AccessibilityNodeInfo.obtain((View) invocation.getMock());
518            }
519        }).when(mockView).createAccessibilityNodeInfo();
520        return mockView;
521    }
522
523    private void assertEventTypeClearsNode(int eventType) {
524        final int nodeId = 0xBEEF;
525        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(nodeId, WINDOW_ID_1);
526        long id = nodeInfo.getSourceNodeId();
527        mAccessibilityCache.add(nodeInfo);
528        nodeInfo.recycle();
529        mAccessibilityCache.onAccessibilityEvent(AccessibilityEvent.obtain(eventType));
530        assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, id));
531    }
532
533    private AccessibilityNodeInfo getParentNode() {
534        AccessibilityNodeInfo parentNodeInfo =
535                getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
536        parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
537        return parentNodeInfo;
538    }
539
540    private AccessibilityNodeInfo getChildNode() {
541        AccessibilityNodeInfo childNodeInfo =
542                getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
543        childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
544        return childNodeInfo;
545    }
546
547    private void assertEventClearsParentAndChild(AccessibilityEvent event) {
548        AccessibilityNodeInfo parentNodeInfo = getParentNode();
549        AccessibilityNodeInfo childNodeInfo = getChildNode();
550        long parentId = parentNodeInfo.getSourceNodeId();
551        long childId = childNodeInfo.getSourceNodeId();
552        mAccessibilityCache.add(parentNodeInfo);
553        mAccessibilityCache.add(childNodeInfo);
554
555        mAccessibilityCache.onAccessibilityEvent(event);
556        parentNodeInfo.recycle();
557        childNodeInfo.recycle();
558
559        AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId);
560        AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId);
561        try {
562            assertNull(parentFromCache);
563            assertNull(childFromCache);
564        } finally {
565            if (parentFromCache != null) {
566                parentFromCache.recycle();
567            }
568            if (childFromCache != null) {
569                childFromCache.recycle();
570            }
571        }
572    }
573}
574