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