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.junit.Assert.fail; 24 import static org.mockito.Matchers.anyBoolean; 25 import static org.mockito.Matchers.anyObject; 26 import static org.mockito.Mockito.doAnswer; 27 import static org.mockito.Mockito.mock; 28 import static org.mockito.Mockito.never; 29 import static org.mockito.Mockito.verify; 30 import static org.mockito.Mockito.when; 31 32 import android.util.SparseArray; 33 import android.view.Display; 34 import android.view.View; 35 36 import androidx.test.filters.LargeTest; 37 import androidx.test.runner.AndroidJUnit4; 38 39 import com.google.common.base.Throwables; 40 41 import org.junit.After; 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.mockito.invocation.InvocationOnMock; 46 import org.mockito.stubbing.Answer; 47 48 import java.util.Arrays; 49 import java.util.List; 50 51 @LargeTest 52 @RunWith(AndroidJUnit4.class) 53 public class AccessibilityCacheTest { 54 private static final int WINDOW_ID_1 = 0xBEEF; 55 private static final int WINDOW_ID_2 = 0xFACE; 56 private static final int WINDOW_ID_3 = 0xABCD; 57 private static final int WINDOW_ID_4 = 0xDCBA; 58 private static final int SINGLE_VIEW_ID = 0xCAFE; 59 private static final int OTHER_VIEW_ID = 0xCAB2; 60 private static final int PARENT_VIEW_ID = 0xFED4; 61 private static final int CHILD_VIEW_ID = 0xFEED; 62 private static final int OTHER_CHILD_VIEW_ID = 0xACE2; 63 private static final int MOCK_CONNECTION_ID = 1; 64 private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1; 65 private static final int DEFAULT_WINDOW_LAYER = 0; 66 private static final int SPECIFIC_WINDOW_LAYER = 5; 67 68 AccessibilityCache mAccessibilityCache; 69 AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher; 70 71 @Before setUp()72 public void setUp() { 73 mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class); 74 when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true); 75 mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher); 76 } 77 78 @After tearDown()79 public void tearDown() { 80 // Make sure we're recycling all of our window and node infos. 81 mAccessibilityCache.clear(); 82 AccessibilityInteractionClient.getInstance().clearCache(); 83 } 84 85 @Test testEmptyCache_returnsNull()86 public void testEmptyCache_returnsNull() { 87 assertNull(mAccessibilityCache.getNode(0, 0)); 88 assertNull(mAccessibilityCache.getWindowsOnAllDisplays()); 89 assertNull(mAccessibilityCache.getWindow(0)); 90 } 91 92 @Test testEmptyCache_clearDoesntCrash()93 public void testEmptyCache_clearDoesntCrash() { 94 mAccessibilityCache.clear(); 95 } 96 97 @Test testEmptyCache_a11yEventsHaveNoEffect()98 public void testEmptyCache_a11yEventsHaveNoEffect() { 99 AccessibilityEvent event = AccessibilityEvent.obtain(); 100 int[] a11yEventTypes = { 101 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, 102 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, 103 AccessibilityEvent.TYPE_VIEW_FOCUSED, 104 AccessibilityEvent.TYPE_VIEW_SELECTED, 105 AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, 106 AccessibilityEvent.TYPE_VIEW_CLICKED, 107 AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, 108 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, 109 AccessibilityEvent.TYPE_VIEW_SCROLLED, 110 AccessibilityEvent.TYPE_WINDOWS_CHANGED, 111 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED}; 112 for (int i = 0; i < a11yEventTypes.length; i++) { 113 event.setEventType(a11yEventTypes[i]); 114 mAccessibilityCache.onAccessibilityEvent(event); 115 } 116 } 117 118 @Test addThenGetWindow_returnsEquivalentButNotSameWindow()119 public void addThenGetWindow_returnsEquivalentButNotSameWindow() { 120 AccessibilityWindowInfo windowInfo = null, copyOfInfo = null, windowFromCache = null; 121 try { 122 windowInfo = AccessibilityWindowInfo.obtain(); 123 windowInfo.setId(WINDOW_ID_1); 124 windowInfo.setDisplayId(Display.DEFAULT_DISPLAY); 125 mAccessibilityCache.addWindow(windowInfo); 126 // Make a copy 127 copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo); 128 windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info. 129 windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1); 130 assertEquals(copyOfInfo, windowFromCache); 131 } finally { 132 windowFromCache.recycle(); 133 windowInfo.recycle(); 134 copyOfInfo.recycle(); 135 } 136 } 137 138 @Test addWindowThenClear_noLongerInCache()139 public void addWindowThenClear_noLongerInCache() { 140 putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY, 141 DEFAULT_WINDOW_LAYER); 142 mAccessibilityCache.clear(); 143 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1)); 144 } 145 146 @Test addWindowGetOtherId_returnsNull()147 public void addWindowGetOtherId_returnsNull() { 148 putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY, 149 DEFAULT_WINDOW_LAYER); 150 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1)); 151 } 152 153 @Test addWindowThenGetWindows_returnsNull()154 public void addWindowThenGetWindows_returnsNull() { 155 putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY, 156 DEFAULT_WINDOW_LAYER); 157 assertNull(mAccessibilityCache.getWindowsOnAllDisplays()); 158 } 159 160 @Test setWindowsThenGetWindows_returnsInDecreasingLayerOrder()161 public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() { 162 AccessibilityWindowInfo windowInfo1 = null; 163 AccessibilityWindowInfo windowInfo2 = null; 164 AccessibilityWindowInfo window1Out = null; 165 AccessibilityWindowInfo window2Out = null; 166 List<AccessibilityWindowInfo> windowsOut = null; 167 try { 168 windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER); 169 windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1); 170 List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2); 171 setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn); 172 173 windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY); 174 window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1); 175 window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2); 176 177 assertEquals(2, windowsOut.size()); 178 assertEquals(windowInfo2, windowsOut.get(0)); 179 assertEquals(windowInfo1, windowsOut.get(1)); 180 assertEquals(windowInfo1, window1Out); 181 assertEquals(windowInfo2, window2Out); 182 } finally { 183 window1Out.recycle(); 184 window2Out.recycle(); 185 windowInfo1.recycle(); 186 windowInfo2.recycle(); 187 for (AccessibilityWindowInfo windowInfo : windowsOut) { 188 windowInfo.recycle(); 189 } 190 } 191 } 192 193 @Test setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder()194 public void setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder() { 195 AccessibilityWindowInfo windowInfo1 = null; 196 AccessibilityWindowInfo windowInfo2 = null; 197 AccessibilityWindowInfo window1Out = null; 198 AccessibilityWindowInfo window2Out = null; 199 AccessibilityWindowInfo window3Out = null; 200 List<AccessibilityWindowInfo> windowsOut = null; 201 try { 202 windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER); 203 windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2); 204 List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2); 205 setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn); 206 207 putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, Display.DEFAULT_DISPLAY, 208 windowInfo1.getLayer() + 1); 209 210 windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY); 211 window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1); 212 window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2); 213 window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3); 214 215 assertEquals(3, windowsOut.size()); 216 assertEquals(windowInfo2, windowsOut.get(0)); 217 assertEquals(windowInfo1, windowsOut.get(2)); 218 assertEquals(windowInfo1, window1Out); 219 assertEquals(windowInfo2, window2Out); 220 assertEquals(window3Out, windowsOut.get(1)); 221 } finally { 222 window1Out.recycle(); 223 window2Out.recycle(); 224 window3Out.recycle(); 225 windowInfo1.recycle(); 226 windowInfo2.recycle(); 227 for (AccessibilityWindowInfo windowInfo : windowsOut) { 228 windowInfo.recycle(); 229 } 230 } 231 } 232 233 @Test 234 public void setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange()235 setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange() { 236 AccessibilityWindowInfo windowInfo1 = null; 237 AccessibilityWindowInfo windowInfo2 = null; 238 AccessibilityWindowInfo window1Out = null; 239 AccessibilityWindowInfo window2Out = null; 240 List<AccessibilityWindowInfo> windowsOut = null; 241 try { 242 // Sets windows to default display. 243 windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER); 244 windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2); 245 List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2); 246 setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn); 247 // Adds one window to second display. 248 putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, SECONDARY_DISPLAY_ID, 249 windowInfo1.getLayer() + 1); 250 251 windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY); 252 window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1); 253 window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2); 254 255 assertEquals(2, windowsOut.size()); 256 assertEquals(windowInfo2, windowsOut.get(0)); 257 assertEquals(windowInfo1, windowsOut.get(1)); 258 assertEquals(windowInfo1, window1Out); 259 assertEquals(windowInfo2, window2Out); 260 } finally { 261 window1Out.recycle(); 262 window2Out.recycle(); 263 windowInfo1.recycle(); 264 windowInfo2.recycle(); 265 for (AccessibilityWindowInfo windowInfo : windowsOut) { 266 windowInfo.recycle(); 267 } 268 } 269 } 270 271 @Test setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder()272 public void setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder() { 273 AccessibilityWindowInfo windowInfo1 = null; 274 AccessibilityWindowInfo windowInfo2 = null; 275 AccessibilityWindowInfo window1Out = null; 276 AccessibilityWindowInfo window2Out = null; 277 AccessibilityWindowInfo windowInfo3 = null; 278 AccessibilityWindowInfo windowInfo4 = null; 279 AccessibilityWindowInfo window3Out = null; 280 AccessibilityWindowInfo window4Out = null; 281 List<AccessibilityWindowInfo> windowsOut1 = null; 282 List<AccessibilityWindowInfo> windowsOut2 = null; 283 try { 284 // Prepares all windows for default display. 285 windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER); 286 windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1); 287 List<AccessibilityWindowInfo> windowsIn1 = Arrays.asList(windowInfo1, windowInfo2); 288 // Prepares all windows for second display. 289 windowInfo3 = obtainAccessibilityWindowInfo(WINDOW_ID_3, windowInfo1.getLayer() + 2); 290 windowInfo4 = obtainAccessibilityWindowInfo(WINDOW_ID_4, windowInfo1.getLayer() + 3); 291 List<AccessibilityWindowInfo> windowsIn2 = Arrays.asList(windowInfo3, windowInfo4); 292 // Sets all windows of all displays into A11y cache. 293 SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>(); 294 allWindows.put(Display.DEFAULT_DISPLAY, windowsIn1); 295 allWindows.put(SECONDARY_DISPLAY_ID, windowsIn2); 296 mAccessibilityCache.setWindowsOnAllDisplays(allWindows); 297 // Gets windows at default display. 298 windowsOut1 = getWindowsByDisplay(Display.DEFAULT_DISPLAY); 299 window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1); 300 window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2); 301 302 assertEquals(2, windowsOut1.size()); 303 assertEquals(windowInfo2, windowsOut1.get(0)); 304 assertEquals(windowInfo1, windowsOut1.get(1)); 305 assertEquals(windowInfo1, window1Out); 306 assertEquals(windowInfo2, window2Out); 307 // Gets windows at seocnd display. 308 windowsOut2 = getWindowsByDisplay(SECONDARY_DISPLAY_ID); 309 window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3); 310 window4Out = mAccessibilityCache.getWindow(WINDOW_ID_4); 311 312 assertEquals(2, windowsOut2.size()); 313 assertEquals(windowInfo4, windowsOut2.get(0)); 314 assertEquals(windowInfo3, windowsOut2.get(1)); 315 assertEquals(windowInfo3, window3Out); 316 assertEquals(windowInfo4, window4Out); 317 } finally { 318 window1Out.recycle(); 319 window2Out.recycle(); 320 windowInfo1.recycle(); 321 windowInfo2.recycle(); 322 window3Out.recycle(); 323 window4Out.recycle(); 324 windowInfo3.recycle(); 325 windowInfo4.recycle(); 326 for (AccessibilityWindowInfo windowInfo : windowsOut1) { 327 windowInfo.recycle(); 328 } 329 for (AccessibilityWindowInfo windowInfo : windowsOut2) { 330 windowInfo.recycle(); 331 } 332 } 333 } 334 335 @Test addWindowThenStateChangedEvent_noLongerInCache()336 public void addWindowThenStateChangedEvent_noLongerInCache() { 337 putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY, 338 DEFAULT_WINDOW_LAYER); 339 mAccessibilityCache.onAccessibilityEvent( 340 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)); 341 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1)); 342 } 343 344 @Test addWindowThenWindowsChangedEvent_noLongerInCache()345 public void addWindowThenWindowsChangedEvent_noLongerInCache() { 346 putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY, 347 DEFAULT_WINDOW_LAYER); 348 mAccessibilityCache.onAccessibilityEvent( 349 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED)); 350 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1)); 351 } 352 353 @Test addThenGetNode_returnsEquivalentNode()354 public void addThenGetNode_returnsEquivalentNode() { 355 AccessibilityNodeInfo nodeInfo, nodeCopy = null, nodeFromCache = null; 356 try { 357 nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 358 long id = nodeInfo.getSourceNodeId(); 359 nodeCopy = AccessibilityNodeInfo.obtain(nodeInfo); 360 mAccessibilityCache.add(nodeInfo); 361 nodeInfo.recycle(); 362 nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id); 363 assertEquals(nodeCopy, nodeFromCache); 364 } finally { 365 nodeFromCache.recycle(); 366 nodeCopy.recycle(); 367 } 368 } 369 370 @Test overwriteThenGetNode_returnsNewNode()371 public void overwriteThenGetNode_returnsNewNode() { 372 final CharSequence contentDescription1 = "foo"; 373 final CharSequence contentDescription2 = "bar"; 374 AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null, nodeFromCache = null; 375 try { 376 nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 377 nodeInfo1.setContentDescription(contentDescription1); 378 long id = nodeInfo1.getSourceNodeId(); 379 nodeInfo2 = AccessibilityNodeInfo.obtain(nodeInfo1); 380 nodeInfo2.setContentDescription(contentDescription2); 381 mAccessibilityCache.add(nodeInfo1); 382 mAccessibilityCache.add(nodeInfo2); 383 nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id); 384 assertEquals(nodeInfo2, nodeFromCache); 385 assertEquals(contentDescription2, nodeFromCache.getContentDescription()); 386 } finally { 387 nodeFromCache.recycle(); 388 nodeInfo2.recycle(); 389 nodeInfo1.recycle(); 390 } 391 } 392 393 @Test nodesInDifferentWindowWithSameId_areKeptSeparate()394 public void nodesInDifferentWindowWithSameId_areKeptSeparate() { 395 final CharSequence contentDescription1 = "foo"; 396 final CharSequence contentDescription2 = "bar"; 397 AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null, 398 node1FromCache = null, node2FromCache = null; 399 try { 400 nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 401 nodeInfo1.setContentDescription(contentDescription1); 402 long id = nodeInfo1.getSourceNodeId(); 403 nodeInfo2 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_2); 404 nodeInfo2.setContentDescription(contentDescription2); 405 assertEquals(id, nodeInfo2.getSourceNodeId()); 406 mAccessibilityCache.add(nodeInfo1); 407 mAccessibilityCache.add(nodeInfo2); 408 node1FromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id); 409 node2FromCache = mAccessibilityCache.getNode(WINDOW_ID_2, id); 410 assertEquals(nodeInfo1, node1FromCache); 411 assertEquals(nodeInfo2, node2FromCache); 412 assertEquals(nodeInfo1.getContentDescription(), node1FromCache.getContentDescription()); 413 assertEquals(nodeInfo2.getContentDescription(), node2FromCache.getContentDescription()); 414 } finally { 415 node1FromCache.recycle(); 416 node2FromCache.recycle(); 417 nodeInfo1.recycle(); 418 nodeInfo2.recycle(); 419 } 420 } 421 422 @Test addNodeThenClear_nodeIsRemoved()423 public void addNodeThenClear_nodeIsRemoved() { 424 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 425 long id = nodeInfo.getSourceNodeId(); 426 mAccessibilityCache.add(nodeInfo); 427 nodeInfo.recycle(); 428 mAccessibilityCache.clear(); 429 assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, id)); 430 } 431 432 @Test windowStateChangeAndWindowsChangedEvents_clearsNode()433 public void windowStateChangeAndWindowsChangedEvents_clearsNode() { 434 assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 435 assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOWS_CHANGED); 436 } 437 438 @Test windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache()439 public void windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache() { 440 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 441 mAccessibilityCache.add(nodeInfo); 442 AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOWS_CHANGED); 443 event.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED); 444 mAccessibilityCache.onAccessibilityEvent(event); 445 AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1, 446 nodeInfo.getSourceNodeId()); 447 try { 448 assertNotNull(cachedNode); 449 } finally { 450 nodeInfo.recycle(); 451 } 452 } 453 454 @Test subTreeChangeEvent_clearsNodeAndChild()455 public void subTreeChangeEvent_clearsNodeAndChild() { 456 AccessibilityEvent event = AccessibilityEvent 457 .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 458 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); 459 event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 460 461 try { 462 assertEventClearsParentAndChild(event); 463 } finally { 464 event.recycle(); 465 } 466 } 467 468 @Test subTreeChangeEventFromUncachedNode_clearsNodeInCache()469 public void subTreeChangeEventFromUncachedNode_clearsNodeInCache() { 470 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1); 471 long id = nodeInfo.getSourceNodeId(); 472 mAccessibilityCache.add(nodeInfo); 473 nodeInfo.recycle(); 474 475 AccessibilityEvent event = AccessibilityEvent 476 .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 477 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); 478 event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 479 480 mAccessibilityCache.onAccessibilityEvent(event); 481 AccessibilityNodeInfo shouldBeNull = mAccessibilityCache.getNode(WINDOW_ID_1, id); 482 if (shouldBeNull != null) { 483 shouldBeNull.recycle(); 484 } 485 assertNull(shouldBeNull); 486 } 487 488 @Test scrollEvent_clearsNodeAndChild()489 public void scrollEvent_clearsNodeAndChild() { 490 AccessibilityEvent event = AccessibilityEvent 491 .obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); 492 event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 493 try { 494 assertEventClearsParentAndChild(event); 495 } finally { 496 event.recycle(); 497 } 498 } 499 500 @Test reparentNode_clearsOldParent()501 public void reparentNode_clearsOldParent() { 502 AccessibilityNodeInfo parentNodeInfo = getParentNode(); 503 AccessibilityNodeInfo childNodeInfo = getChildNode(); 504 long parentId = parentNodeInfo.getSourceNodeId(); 505 mAccessibilityCache.add(parentNodeInfo); 506 mAccessibilityCache.add(childNodeInfo); 507 508 childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID + 1, WINDOW_ID_1)); 509 mAccessibilityCache.add(childNodeInfo); 510 511 AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId); 512 try { 513 assertNull(parentFromCache); 514 } finally { 515 parentNodeInfo.recycle(); 516 childNodeInfo.recycle(); 517 if (parentFromCache != null) { 518 parentFromCache.recycle(); 519 } 520 } 521 } 522 523 @Test removeChildFromParent_clearsChild()524 public void removeChildFromParent_clearsChild() { 525 AccessibilityNodeInfo parentNodeInfo = getParentNode(); 526 AccessibilityNodeInfo childNodeInfo = getChildNode(); 527 long childId = childNodeInfo.getSourceNodeId(); 528 mAccessibilityCache.add(parentNodeInfo); 529 mAccessibilityCache.add(childNodeInfo); 530 531 AccessibilityNodeInfo parentNodeInfoWithNoChildren = 532 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1); 533 mAccessibilityCache.add(parentNodeInfoWithNoChildren); 534 535 AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId); 536 try { 537 assertNull(childFromCache); 538 } finally { 539 parentNodeInfoWithNoChildren.recycle(); 540 parentNodeInfo.recycle(); 541 childNodeInfo.recycle(); 542 if (childFromCache != null) { 543 childFromCache.recycle(); 544 } 545 } 546 } 547 548 @Test nodeSourceOfA11yFocusEvent_getsRefreshed()549 public void nodeSourceOfA11yFocusEvent_getsRefreshed() { 550 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 551 nodeInfo.setAccessibilityFocused(false); 552 mAccessibilityCache.add(nodeInfo); 553 AccessibilityEvent event = AccessibilityEvent.obtain( 554 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 555 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1)); 556 mAccessibilityCache.onAccessibilityEvent(event); 557 event.recycle(); 558 try { 559 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); 560 } finally { 561 nodeInfo.recycle(); 562 } 563 } 564 565 @Test nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRemoved()566 public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRemoved() { 567 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 568 nodeInfo.setAccessibilityFocused(true); 569 mAccessibilityCache.add(nodeInfo); 570 AccessibilityEvent event = AccessibilityEvent.obtain( 571 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 572 event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1)); 573 mAccessibilityCache.onAccessibilityEvent(event); 574 event.recycle(); 575 try { 576 assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID)); 577 } finally { 578 nodeInfo.recycle(); 579 } 580 } 581 582 @Test nodeWithA11yFocusClearsIt_refreshes()583 public void nodeWithA11yFocusClearsIt_refreshes() { 584 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 585 nodeInfo.setAccessibilityFocused(true); 586 mAccessibilityCache.add(nodeInfo); 587 AccessibilityEvent event = AccessibilityEvent.obtain( 588 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 589 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1)); 590 mAccessibilityCache.onAccessibilityEvent(event); 591 event.recycle(); 592 try { 593 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); 594 } finally { 595 nodeInfo.recycle(); 596 } 597 } 598 599 @Test nodeSourceOfInputFocusEvent_getsRefreshed()600 public void nodeSourceOfInputFocusEvent_getsRefreshed() { 601 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 602 nodeInfo.setFocused(false); 603 mAccessibilityCache.add(nodeInfo); 604 AccessibilityEvent event = AccessibilityEvent.obtain( 605 AccessibilityEvent.TYPE_VIEW_FOCUSED); 606 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1)); 607 mAccessibilityCache.onAccessibilityEvent(event); 608 event.recycle(); 609 try { 610 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); 611 } finally { 612 nodeInfo.recycle(); 613 } 614 } 615 616 @Test nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRemoved()617 public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRemoved() { 618 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 619 nodeInfo.setFocused(true); 620 mAccessibilityCache.add(nodeInfo); 621 AccessibilityEvent event = AccessibilityEvent.obtain( 622 AccessibilityEvent.TYPE_VIEW_FOCUSED); 623 event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1)); 624 mAccessibilityCache.onAccessibilityEvent(event); 625 event.recycle(); 626 try { 627 assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID)); 628 } finally { 629 nodeInfo.recycle(); 630 } 631 } 632 633 @Test nodeEventSaysWasSelected_getsRefreshed()634 public void nodeEventSaysWasSelected_getsRefreshed() { 635 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_SELECTED, 636 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 637 } 638 639 @Test nodeEventSaysHadTextChanged_getsRefreshed()640 public void nodeEventSaysHadTextChanged_getsRefreshed() { 641 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, 642 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 643 } 644 645 @Test nodeEventSaysWasClicked_getsRefreshed()646 public void nodeEventSaysWasClicked_getsRefreshed() { 647 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_CLICKED, 648 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 649 } 650 651 @Test nodeEventSaysHadSelectionChange_getsRefreshed()652 public void nodeEventSaysHadSelectionChange_getsRefreshed() { 653 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED, 654 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 655 } 656 657 @Test nodeEventSaysHadTextContentChange_getsRefreshed()658 public void nodeEventSaysHadTextContentChange_getsRefreshed() { 659 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, 660 AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 661 } 662 663 @Test nodeEventSaysHadContentDescriptionChange_getsRefreshed()664 public void nodeEventSaysHadContentDescriptionChange_getsRefreshed() { 665 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, 666 AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); 667 } 668 669 @Test addNode_whenNodeBeingReplacedIsOwnGrandparentWithTwoChildren_doesntCrash()670 public void addNode_whenNodeBeingReplacedIsOwnGrandparentWithTwoChildren_doesntCrash() { 671 AccessibilityNodeInfo parentNodeInfo = 672 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1); 673 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1)); 674 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(OTHER_CHILD_VIEW_ID, WINDOW_ID_1)); 675 AccessibilityNodeInfo childNodeInfo = 676 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1); 677 childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 678 childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 679 680 AccessibilityNodeInfo replacementParentNodeInfo = 681 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1); 682 try { 683 mAccessibilityCache.add(parentNodeInfo); 684 mAccessibilityCache.add(childNodeInfo); 685 mAccessibilityCache.add(replacementParentNodeInfo); 686 } finally { 687 parentNodeInfo.recycle(); 688 childNodeInfo.recycle(); 689 replacementParentNodeInfo.recycle(); 690 } 691 } 692 693 @Test addNode_whenNodeBeingReplacedIsOwnGrandparentWithOneChild_doesntCrash()694 public void addNode_whenNodeBeingReplacedIsOwnGrandparentWithOneChild_doesntCrash() { 695 AccessibilityNodeInfo parentNodeInfo = 696 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1); 697 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1)); 698 AccessibilityNodeInfo childNodeInfo = 699 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1); 700 childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 701 childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 702 703 AccessibilityNodeInfo replacementParentNodeInfo = 704 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1); 705 try { 706 mAccessibilityCache.add(parentNodeInfo); 707 mAccessibilityCache.add(childNodeInfo); 708 mAccessibilityCache.add(replacementParentNodeInfo); 709 } finally { 710 parentNodeInfo.recycle(); 711 childNodeInfo.recycle(); 712 replacementParentNodeInfo.recycle(); 713 } 714 } 715 716 @Test testCacheCriticalEventList_doesntLackEvents()717 public void testCacheCriticalEventList_doesntLackEvents() { 718 for (int i = 0; i < 32; i++) { 719 int eventType = 1 << i; 720 if ((eventType & AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK) == 0) { 721 try { 722 assertEventTypeClearsNode(eventType, false); 723 verify(mAccessibilityNodeRefresher, never()) 724 .refreshNode(anyObject(), anyBoolean()); 725 } catch (Throwable e) { 726 throw new AssertionError( 727 "Failed for eventType: " + AccessibilityEvent.eventTypeToString( 728 eventType), 729 e); 730 } 731 } 732 } 733 } 734 735 @Test addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRemoved()736 public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRemoved() { 737 AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 738 nodeInfo1.setAccessibilityFocused(true); 739 mAccessibilityCache.add(nodeInfo1); 740 AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1); 741 nodeInfo2.setAccessibilityFocused(true); 742 mAccessibilityCache.add(nodeInfo2); 743 try { 744 assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID)); 745 } finally { 746 nodeInfo1.recycle(); 747 nodeInfo2.recycle(); 748 } 749 } 750 751 @Test addSameParentNodeWithDifferentChildNode_whenOriginalChildHasChild_doesntCrash()752 public void addSameParentNodeWithDifferentChildNode_whenOriginalChildHasChild_doesntCrash() { 753 AccessibilityNodeInfo parentNodeInfo = getParentNode(); 754 AccessibilityNodeInfo childNodeInfo = getChildNode(); 755 childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID + 1, WINDOW_ID_1)); 756 757 AccessibilityNodeInfo replacementParentNodeInfo = 758 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1); 759 replacementParentNodeInfo.addChild( 760 getMockViewWithA11yAndWindowIds(OTHER_CHILD_VIEW_ID, WINDOW_ID_1)); 761 try { 762 mAccessibilityCache.add(parentNodeInfo); 763 mAccessibilityCache.add(childNodeInfo); 764 mAccessibilityCache.add(replacementParentNodeInfo); 765 } catch (IllegalStateException e) { 766 fail("recycle A11yNodeInfo twice" + Throwables.getStackTraceAsString(e)); 767 } finally { 768 parentNodeInfo.recycle(); 769 childNodeInfo.recycle(); 770 replacementParentNodeInfo.recycle(); 771 } 772 } 773 assertNodeIsRefreshedWithEventType(int eventType, int contentChangeTypes)774 private void assertNodeIsRefreshedWithEventType(int eventType, int contentChangeTypes) { 775 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); 776 mAccessibilityCache.add(nodeInfo); 777 AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 778 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1)); 779 event.setContentChangeTypes(contentChangeTypes); 780 mAccessibilityCache.onAccessibilityEvent(event); 781 event.recycle(); 782 try { 783 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); 784 } finally { 785 nodeInfo.recycle(); 786 } 787 } 788 obtainAccessibilityWindowInfo(int windowId, int layer)789 private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) { 790 AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain(); 791 windowInfo.setId(windowId); 792 windowInfo.setLayer(layer); 793 return windowInfo; 794 } 795 putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer)796 private void putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer) { 797 AccessibilityWindowInfo windowInfo = obtainAccessibilityWindowInfo(windowId, layer); 798 windowInfo.setDisplayId(displayId); 799 mAccessibilityCache.addWindow(windowInfo); 800 windowInfo.recycle(); 801 } 802 getNodeWithA11yAndWindowId(int a11yId, int windowId)803 private AccessibilityNodeInfo getNodeWithA11yAndWindowId(int a11yId, int windowId) { 804 AccessibilityNodeInfo node = 805 AccessibilityNodeInfo.obtain(getMockViewWithA11yAndWindowIds(a11yId, windowId)); 806 node.setConnectionId(MOCK_CONNECTION_ID); 807 return node; 808 } 809 getMockViewWithA11yAndWindowIds(int a11yId, int windowId)810 private View getMockViewWithA11yAndWindowIds(int a11yId, int windowId) { 811 View mockView = mock(View.class); 812 when(mockView.getAccessibilityViewId()).thenReturn(a11yId); 813 when(mockView.getAccessibilityWindowId()).thenReturn(windowId); 814 doAnswer(new Answer<AccessibilityNodeInfo>() { 815 public AccessibilityNodeInfo answer(InvocationOnMock invocation) { 816 return AccessibilityNodeInfo.obtain((View) invocation.getMock()); 817 } 818 }).when(mockView).createAccessibilityNodeInfo(); 819 return mockView; 820 } 821 assertEventTypeClearsNode(int eventType)822 private void assertEventTypeClearsNode(int eventType) { 823 assertEventTypeClearsNode(eventType, true); 824 } 825 assertEventTypeClearsNode(int eventType, boolean clears)826 private void assertEventTypeClearsNode(int eventType, boolean clears) { 827 final int nodeId = 0xBEEF; 828 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(nodeId, WINDOW_ID_1); 829 long id = nodeInfo.getSourceNodeId(); 830 mAccessibilityCache.add(nodeInfo); 831 nodeInfo.recycle(); 832 mAccessibilityCache.onAccessibilityEvent(AccessibilityEvent.obtain(eventType)); 833 AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1, id); 834 try { 835 if (clears) { 836 assertNull(cachedNode); 837 } else { 838 assertNotNull(cachedNode); 839 } 840 } finally { 841 if (cachedNode != null) { 842 cachedNode.recycle(); 843 } 844 } 845 } 846 getParentNode()847 private AccessibilityNodeInfo getParentNode() { 848 AccessibilityNodeInfo parentNodeInfo = 849 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1); 850 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1)); 851 return parentNodeInfo; 852 } 853 getChildNode()854 private AccessibilityNodeInfo getChildNode() { 855 AccessibilityNodeInfo childNodeInfo = 856 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1); 857 childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1)); 858 return childNodeInfo; 859 } 860 assertEventClearsParentAndChild(AccessibilityEvent event)861 private void assertEventClearsParentAndChild(AccessibilityEvent event) { 862 AccessibilityNodeInfo parentNodeInfo = getParentNode(); 863 AccessibilityNodeInfo childNodeInfo = getChildNode(); 864 long parentId = parentNodeInfo.getSourceNodeId(); 865 long childId = childNodeInfo.getSourceNodeId(); 866 mAccessibilityCache.add(parentNodeInfo); 867 mAccessibilityCache.add(childNodeInfo); 868 869 mAccessibilityCache.onAccessibilityEvent(event); 870 parentNodeInfo.recycle(); 871 childNodeInfo.recycle(); 872 873 AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId); 874 AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId); 875 try { 876 assertNull(parentFromCache); 877 assertNull(childFromCache); 878 } finally { 879 if (parentFromCache != null) { 880 parentFromCache.recycle(); 881 } 882 if (childFromCache != null) { 883 childFromCache.recycle(); 884 } 885 } 886 } 887 setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows)888 private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows) { 889 SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>(); 890 allWindows.put(displayId, windows); 891 mAccessibilityCache.setWindowsOnAllDisplays(allWindows); 892 } 893 getWindowsByDisplay(int displayId)894 private List<AccessibilityWindowInfo> getWindowsByDisplay(int displayId) { 895 final SparseArray<List<AccessibilityWindowInfo>> allWindows = 896 mAccessibilityCache.getWindowsOnAllDisplays(); 897 898 if (allWindows != null && allWindows.size() > 0) { 899 return allWindows.get(displayId); 900 } 901 return null; 902 } 903 } 904