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