blob: 1cb71f98a74da4d35eaed6408343dbc7e9c8b597 [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
RyanlwLin43225d12019-02-26 18:21:41 +080023import static org.junit.Assert.fail;
Phil Weaverb010b122016-08-17 17:47:48 -070024import static org.mockito.Matchers.anyBoolean;
25import static org.mockito.Matchers.anyObject;
26import static org.mockito.Mockito.doAnswer;
27import static org.mockito.Mockito.mock;
Eugene Susla74c6cba2016-12-28 14:42:54 -080028import static org.mockito.Mockito.never;
Phil Weaverc140fdc2017-11-09 15:24:17 -080029import static org.mockito.Mockito.verify;
Phil Weaverb010b122016-08-17 17:47:48 -070030import static org.mockito.Mockito.when;
31
Phil Weaverb010b122016-08-17 17:47:48 -070032import android.view.View;
Phil Weaverc140fdc2017-11-09 15:24:17 -080033
Tadashi G. Takaokab4470f22019-01-15 18:29:15 +090034import androidx.test.filters.LargeTest;
35import androidx.test.runner.AndroidJUnit4;
36
RyanlwLin43225d12019-02-26 18:21:41 +080037import com.google.common.base.Throwables;
38
Phil Weaverb010b122016-08-17 17:47:48 -070039import org.junit.After;
40import org.junit.Before;
41import org.junit.Test;
42import org.junit.runner.RunWith;
43import org.mockito.invocation.InvocationOnMock;
44import org.mockito.stubbing.Answer;
45
46import java.util.Arrays;
47import java.util.List;
Phil Weaver62d20fa2016-09-15 11:05:55 -070048import java.util.concurrent.atomic.AtomicInteger;
Phil Weaverb010b122016-08-17 17:47:48 -070049
Aurimas Liutikasbdbde552017-12-19 13:21:10 -080050@LargeTest
Phil Weaverb010b122016-08-17 17:47:48 -070051@RunWith(AndroidJUnit4.class)
52public class AccessibilityCacheTest {
Phil Weaverc140fdc2017-11-09 15:24:17 -080053 private static final int WINDOW_ID_1 = 0xBEEF;
54 private static final int WINDOW_ID_2 = 0xFACE;
55 private static final int SINGLE_VIEW_ID = 0xCAFE;
56 private static final int OTHER_VIEW_ID = 0xCAB2;
57 private static final int PARENT_VIEW_ID = 0xFED4;
58 private static final int CHILD_VIEW_ID = 0xFEED;
59 private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
60 private static final int MOCK_CONNECTION_ID = 1;
Phil Weaverb010b122016-08-17 17:47:48 -070061
62 AccessibilityCache mAccessibilityCache;
63 AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
Phil Weaverc140fdc2017-11-09 15:24:17 -080064 AtomicInteger mNumA11yNodeInfosInUse = new AtomicInteger(0);
65 AtomicInteger mNumA11yWinInfosInUse = new AtomicInteger(0);
Phil Weaverb010b122016-08-17 17:47:48 -070066
67 @Before
68 public void setUp() {
69 mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class);
70 when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true);
71 mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
Phil Weaverc140fdc2017-11-09 15:24:17 -080072 AccessibilityNodeInfo.setNumInstancesInUseCounter(mNumA11yNodeInfosInUse);
73 AccessibilityWindowInfo.setNumInstancesInUseCounter(mNumA11yWinInfosInUse);
Phil Weaverb010b122016-08-17 17:47:48 -070074 }
75
76 @After
77 public void tearDown() {
78 // Make sure we're recycling all of our window and node infos
79 mAccessibilityCache.clear();
80 AccessibilityInteractionClient.getInstance().clearCache();
Phil Weaverc140fdc2017-11-09 15:24:17 -080081 assertEquals(0, mNumA11yWinInfosInUse.get());
82 assertEquals(0, mNumA11yNodeInfosInUse.get());
Phil Weaverb010b122016-08-17 17:47:48 -070083 }
84
85 @Test
86 public void testEmptyCache_returnsNull() {
87 assertNull(mAccessibilityCache.getNode(0, 0));
88 assertNull(mAccessibilityCache.getWindows());
89 assertNull(mAccessibilityCache.getWindow(0));
90 }
91
92 @Test
93 public void testEmptyCache_clearDoesntCrash() {
94 mAccessibilityCache.clear();
95 }
96
97 @Test
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
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 mAccessibilityCache.addWindow(windowInfo);
125 // Make a copy
126 copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo);
127 windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info
128 windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1);
129 assertEquals(copyOfInfo, windowFromCache);
130 } finally {
131 windowFromCache.recycle();
132 windowInfo.recycle();
133 copyOfInfo.recycle();
134 }
135 }
136
137 @Test
138 public void addWindowThenClear_noLongerInCache() {
139 putWindowWithIdInCache(WINDOW_ID_1);
140 mAccessibilityCache.clear();
141 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
142 }
143
144 @Test
145 public void addWindowGetOtherId_returnsNull() {
146 putWindowWithIdInCache(WINDOW_ID_1);
147 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1));
148 }
149
150 @Test
151 public void addWindowThenGetWindows_returnsNull() {
152 putWindowWithIdInCache(WINDOW_ID_1);
153 assertNull(mAccessibilityCache.getWindows());
154 }
155
156 @Test
157 public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() {
158 AccessibilityWindowInfo windowInfo1 = null, windowInfo2 = null;
159 AccessibilityWindowInfo window1Out = null, window2Out = null;
160 List<AccessibilityWindowInfo> windowsOut = null;
161 try {
162 windowInfo1 = AccessibilityWindowInfo.obtain();
163 windowInfo1.setId(WINDOW_ID_1);
164 windowInfo1.setLayer(5);
165 windowInfo2 = AccessibilityWindowInfo.obtain();
166 windowInfo2.setId(WINDOW_ID_2);
167 windowInfo2.setLayer(windowInfo1.getLayer() + 1);
168 List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
169 mAccessibilityCache.setWindows(windowsIn);
170
171 windowsOut = mAccessibilityCache.getWindows();
172 window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
173 window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
174
175 assertEquals(2, windowsOut.size());
176 assertEquals(windowInfo2, windowsOut.get(0));
177 assertEquals(windowInfo1, windowsOut.get(1));
178 assertEquals(windowInfo1, window1Out);
179 assertEquals(windowInfo2, window2Out);
180 } finally {
181 window1Out.recycle();
182 window2Out.recycle();
183 windowInfo1.recycle();
184 windowInfo2.recycle();
185 for (AccessibilityWindowInfo windowInfo : windowsOut) {
186 windowInfo.recycle();
187 }
188 }
189 }
190
191 @Test
192 public void addWindowThenStateChangedEvent_noLongerInCache() {
193 putWindowWithIdInCache(WINDOW_ID_1);
194 mAccessibilityCache.onAccessibilityEvent(
195 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
196 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
197 }
198
199 @Test
200 public void addWindowThenWindowsChangedEvent_noLongerInCache() {
201 putWindowWithIdInCache(WINDOW_ID_1);
202 mAccessibilityCache.onAccessibilityEvent(
203 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED));
204 assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
205 }
206
207 @Test
208 public void addThenGetNode_returnsEquivalentNode() {
209 AccessibilityNodeInfo nodeInfo, nodeCopy = null, nodeFromCache = null;
210 try {
211 nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
212 long id = nodeInfo.getSourceNodeId();
213 nodeCopy = AccessibilityNodeInfo.obtain(nodeInfo);
214 mAccessibilityCache.add(nodeInfo);
215 nodeInfo.recycle();
216 nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
217 assertEquals(nodeCopy, nodeFromCache);
218 } finally {
219 nodeFromCache.recycle();
220 nodeCopy.recycle();
221 }
222 }
223
224 @Test
225 public void overwriteThenGetNode_returnsNewNode() {
226 final CharSequence contentDescription1 = "foo";
227 final CharSequence contentDescription2 = "bar";
228 AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null, nodeFromCache = null;
229 try {
230 nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
231 nodeInfo1.setContentDescription(contentDescription1);
232 long id = nodeInfo1.getSourceNodeId();
233 nodeInfo2 = AccessibilityNodeInfo.obtain(nodeInfo1);
234 nodeInfo2.setContentDescription(contentDescription2);
235 mAccessibilityCache.add(nodeInfo1);
236 mAccessibilityCache.add(nodeInfo2);
237 nodeFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
238 assertEquals(nodeInfo2, nodeFromCache);
239 assertEquals(contentDescription2, nodeFromCache.getContentDescription());
240 } finally {
241 nodeFromCache.recycle();
242 nodeInfo2.recycle();
243 nodeInfo1.recycle();
244 }
245 }
246
247 @Test
248 public void nodesInDifferentWindowWithSameId_areKeptSeparate() {
249 final CharSequence contentDescription1 = "foo";
250 final CharSequence contentDescription2 = "bar";
251 AccessibilityNodeInfo nodeInfo1 = null, nodeInfo2 = null,
252 node1FromCache = null, node2FromCache = null;
253 try {
254 nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
255 nodeInfo1.setContentDescription(contentDescription1);
256 long id = nodeInfo1.getSourceNodeId();
257 nodeInfo2 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_2);
258 nodeInfo2.setContentDescription(contentDescription2);
259 assertEquals(id, nodeInfo2.getSourceNodeId());
260 mAccessibilityCache.add(nodeInfo1);
261 mAccessibilityCache.add(nodeInfo2);
262 node1FromCache = mAccessibilityCache.getNode(WINDOW_ID_1, id);
263 node2FromCache = mAccessibilityCache.getNode(WINDOW_ID_2, id);
264 assertEquals(nodeInfo1, node1FromCache);
265 assertEquals(nodeInfo2, node2FromCache);
266 assertEquals(nodeInfo1.getContentDescription(), node1FromCache.getContentDescription());
267 assertEquals(nodeInfo2.getContentDescription(), node2FromCache.getContentDescription());
268 } finally {
269 node1FromCache.recycle();
270 node2FromCache.recycle();
271 nodeInfo1.recycle();
272 nodeInfo2.recycle();
273 }
274 }
275
276 @Test
277 public void addNodeThenClear_nodeIsRemoved() {
278 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
279 long id = nodeInfo.getSourceNodeId();
280 mAccessibilityCache.add(nodeInfo);
281 nodeInfo.recycle();
282 mAccessibilityCache.clear();
283 assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, id));
284 }
285
286 @Test
287 public void windowStateChangeAndWindowsChangedEvents_clearsNode() {
288 assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
289 assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
290 }
291
292 @Test
293 public void subTreeChangeEvent_clearsNodeAndChild() {
294 AccessibilityEvent event = AccessibilityEvent
295 .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
296 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
297 event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
298
299 try {
300 assertEventClearsParentAndChild(event);
301 } finally {
302 event.recycle();
303 }
304 }
305
306 @Test
Qasid Ahmad Sadiqb2499c52019-02-28 14:35:45 -0800307 public void subTreeChangeEventFromUncachedNode_clearsNodeInCache() {
308 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
309 long id = nodeInfo.getSourceNodeId();
310 mAccessibilityCache.add(nodeInfo);
311 nodeInfo.recycle();
312
313 AccessibilityEvent event = AccessibilityEvent
314 .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
315 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
316 event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
317
318 mAccessibilityCache.onAccessibilityEvent(event);
319 AccessibilityNodeInfo shouldBeNull = mAccessibilityCache.getNode(WINDOW_ID_1, id);
320 if (shouldBeNull != null) {
321 shouldBeNull.recycle();
322 }
323 assertNull(shouldBeNull);
324 }
325
326 @Test
Phil Weaverb010b122016-08-17 17:47:48 -0700327 public void scrollEvent_clearsNodeAndChild() {
328 AccessibilityEvent event = AccessibilityEvent
329 .obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
330 event.setSource(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
331 try {
332 assertEventClearsParentAndChild(event);
333 } finally {
334 event.recycle();
335 }
336 }
337
338 @Test
339 public void reparentNode_clearsOldParent() {
340 AccessibilityNodeInfo parentNodeInfo = getParentNode();
341 AccessibilityNodeInfo childNodeInfo = getChildNode();
342 long parentId = parentNodeInfo.getSourceNodeId();
343 mAccessibilityCache.add(parentNodeInfo);
344 mAccessibilityCache.add(childNodeInfo);
345
346 childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID + 1, WINDOW_ID_1));
347 mAccessibilityCache.add(childNodeInfo);
348
349 AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId);
350 try {
351 assertNull(parentFromCache);
352 } finally {
353 parentNodeInfo.recycle();
354 childNodeInfo.recycle();
355 if (parentFromCache != null) {
356 parentFromCache.recycle();
357 }
358 }
359 }
360
361 @Test
362 public void removeChildFromParent_clearsChild() {
363 AccessibilityNodeInfo parentNodeInfo = getParentNode();
364 AccessibilityNodeInfo childNodeInfo = getChildNode();
365 long childId = childNodeInfo.getSourceNodeId();
366 mAccessibilityCache.add(parentNodeInfo);
367 mAccessibilityCache.add(childNodeInfo);
368
369 AccessibilityNodeInfo parentNodeInfoWithNoChildren =
370 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
371 mAccessibilityCache.add(parentNodeInfoWithNoChildren);
372
373 AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId);
374 try {
375 assertNull(childFromCache);
376 } finally {
377 parentNodeInfoWithNoChildren.recycle();
378 parentNodeInfo.recycle();
379 childNodeInfo.recycle();
380 if (childFromCache != null) {
381 childFromCache.recycle();
382 }
383 }
384 }
385
386 @Test
387 public void nodeSourceOfA11yFocusEvent_getsRefreshed() {
388 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
389 nodeInfo.setAccessibilityFocused(false);
390 mAccessibilityCache.add(nodeInfo);
391 AccessibilityEvent event = AccessibilityEvent.obtain(
392 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
393 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
394 mAccessibilityCache.onAccessibilityEvent(event);
395 event.recycle();
396 try {
397 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
398 } finally {
399 nodeInfo.recycle();
400 }
401 }
402
403 @Test
404 public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRefreshed() {
405 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
406 nodeInfo.setAccessibilityFocused(true);
407 mAccessibilityCache.add(nodeInfo);
408 AccessibilityEvent event = AccessibilityEvent.obtain(
409 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
410 event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1));
411 mAccessibilityCache.onAccessibilityEvent(event);
412 event.recycle();
413 try {
414 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
415 } finally {
416 nodeInfo.recycle();
417 }
418 }
419
420 @Test
421 public void nodeWithA11yFocusClearsIt_refreshes() {
422 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
423 nodeInfo.setAccessibilityFocused(true);
424 mAccessibilityCache.add(nodeInfo);
425 AccessibilityEvent event = AccessibilityEvent.obtain(
426 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
427 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
428 mAccessibilityCache.onAccessibilityEvent(event);
429 event.recycle();
430 try {
431 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
432 } finally {
433 nodeInfo.recycle();
434 }
435 }
436
437 @Test
438 public void nodeSourceOfInputFocusEvent_getsRefreshed() {
439 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
440 nodeInfo.setFocused(false);
441 mAccessibilityCache.add(nodeInfo);
442 AccessibilityEvent event = AccessibilityEvent.obtain(
443 AccessibilityEvent.TYPE_VIEW_FOCUSED);
444 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
445 mAccessibilityCache.onAccessibilityEvent(event);
446 event.recycle();
447 try {
448 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
449 } finally {
450 nodeInfo.recycle();
451 }
452 }
453
454 @Test
455 public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRefreshed() {
456 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
457 nodeInfo.setFocused(true);
458 mAccessibilityCache.add(nodeInfo);
459 AccessibilityEvent event = AccessibilityEvent.obtain(
460 AccessibilityEvent.TYPE_VIEW_FOCUSED);
461 event.setSource(getMockViewWithA11yAndWindowIds(OTHER_VIEW_ID, WINDOW_ID_1));
462 mAccessibilityCache.onAccessibilityEvent(event);
463 event.recycle();
464 try {
465 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
466 } finally {
467 nodeInfo.recycle();
468 }
469 }
470
471 @Test
472 public void nodeEventSaysWasSelected_getsRefreshed() {
473 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_SELECTED,
474 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
475 }
476
477 @Test
478 public void nodeEventSaysHadTextChanged_getsRefreshed() {
479 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
480 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
481 }
482
483 @Test
484 public void nodeEventSaysWasClicked_getsRefreshed() {
485 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_CLICKED,
486 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
487 }
488
489 @Test
490 public void nodeEventSaysHadSelectionChange_getsRefreshed() {
491 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED,
492 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
493 }
494
495 @Test
496 public void nodeEventSaysHadTextContentChange_getsRefreshed() {
497 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
498 AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
499 }
500
501 @Test
502 public void nodeEventSaysHadContentDescriptionChange_getsRefreshed() {
503 assertNodeIsRefreshedWithEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
504 AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
505 }
506
Eugene Susla74c6cba2016-12-28 14:42:54 -0800507 @Test
Phil Weaverf1eb10b2018-08-14 16:25:53 -0700508 public void addNode_whenNodeBeingReplacedIsOwnGrandparentWithTwoChildren_doesntCrash() {
Phil Weaver61a1fab2017-05-03 13:55:51 -0700509 AccessibilityNodeInfo parentNodeInfo =
510 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
511 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
512 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(OTHER_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
Phil Weaverf1eb10b2018-08-14 16:25:53 -0700532 public void addNode_whenNodeBeingReplacedIsOwnGrandparentWithOneChild_doesntCrash() {
533 AccessibilityNodeInfo parentNodeInfo =
534 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
535 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
536 AccessibilityNodeInfo childNodeInfo =
537 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
538 childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
539 childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
540
541 AccessibilityNodeInfo replacementParentNodeInfo =
542 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
543 try {
544 mAccessibilityCache.add(parentNodeInfo);
545 mAccessibilityCache.add(childNodeInfo);
546 mAccessibilityCache.add(replacementParentNodeInfo);
547 } finally {
548 parentNodeInfo.recycle();
549 childNodeInfo.recycle();
550 replacementParentNodeInfo.recycle();
551 }
552 }
553
554 @Test
Eugene Susla74c6cba2016-12-28 14:42:54 -0800555 public void testCacheCriticalEventList_doesntLackEvents() {
556 for (int i = 0; i < 32; i++) {
557 int eventType = 1 << i;
558 if ((eventType & AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK) == 0) {
559 try {
560 assertEventTypeClearsNode(eventType, false);
561 verify(mAccessibilityNodeRefresher, never())
562 .refreshNode(anyObject(), anyBoolean());
563 } catch (Throwable e) {
564 throw new AssertionError(
565 "Failed for eventType: " + AccessibilityEvent.eventTypeToString(
566 eventType),
567 e);
568 }
569 }
570 }
571 }
572
Rhed Jaoca9b5262018-07-27 21:13:00 +0800573 @Test
574 public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRefreshed() {
575 AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
576 nodeInfo1.setAccessibilityFocused(true);
577 mAccessibilityCache.add(nodeInfo1);
578 AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1);
579 nodeInfo2.setAccessibilityFocused(true);
580 mAccessibilityCache.add(nodeInfo2);
581 AccessibilityEvent event = AccessibilityEvent.obtain(
582 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
583 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
584 mAccessibilityCache.onAccessibilityEvent(event);
585 event.recycle();
586 try {
587 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo1, true);
588 } finally {
589 nodeInfo1.recycle();
590 nodeInfo2.recycle();
591 }
592 }
593
RyanlwLin43225d12019-02-26 18:21:41 +0800594 @Test
595 public void addSameParentNodeWithDifferentChildNode_whenOriginalChildHasChild_doesntCrash() {
596 AccessibilityNodeInfo parentNodeInfo = getParentNode();
597 AccessibilityNodeInfo childNodeInfo = getChildNode();
598 childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID + 1, WINDOW_ID_1));
599
600 AccessibilityNodeInfo replacementParentNodeInfo =
601 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
602 replacementParentNodeInfo.addChild(
603 getMockViewWithA11yAndWindowIds(OTHER_CHILD_VIEW_ID, WINDOW_ID_1));
604 try {
605 mAccessibilityCache.add(parentNodeInfo);
606 mAccessibilityCache.add(childNodeInfo);
607 mAccessibilityCache.add(replacementParentNodeInfo);
608 } catch (IllegalStateException e) {
609 fail("recycle A11yNodeInfo twice" + Throwables.getStackTraceAsString(e));
610 } finally {
611 parentNodeInfo.recycle();
612 childNodeInfo.recycle();
613 replacementParentNodeInfo.recycle();
614 }
615 }
616
Phil Weaverb010b122016-08-17 17:47:48 -0700617 private void assertNodeIsRefreshedWithEventType(int eventType, int contentChangeTypes) {
618 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
619 mAccessibilityCache.add(nodeInfo);
620 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
621 event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
622 event.setContentChangeTypes(contentChangeTypes);
623 mAccessibilityCache.onAccessibilityEvent(event);
624 event.recycle();
625 try {
626 verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
627 } finally {
628 nodeInfo.recycle();
629 }
630 }
631
632 private void putWindowWithIdInCache(int id) {
633 AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
634 windowInfo.setId(id);
635 mAccessibilityCache.addWindow(windowInfo);
636 windowInfo.recycle();
637 }
638
639 private AccessibilityNodeInfo getNodeWithA11yAndWindowId(int a11yId, int windowId) {
640 AccessibilityNodeInfo node =
641 AccessibilityNodeInfo.obtain(getMockViewWithA11yAndWindowIds(a11yId, windowId));
642 node.setConnectionId(MOCK_CONNECTION_ID);
643 return node;
644 }
645
646 private View getMockViewWithA11yAndWindowIds(int a11yId, int windowId) {
647 View mockView = mock(View.class);
648 when(mockView.getAccessibilityViewId()).thenReturn(a11yId);
649 when(mockView.getAccessibilityWindowId()).thenReturn(windowId);
650 doAnswer(new Answer<AccessibilityNodeInfo>() {
651 public AccessibilityNodeInfo answer(InvocationOnMock invocation) {
652 return AccessibilityNodeInfo.obtain((View) invocation.getMock());
653 }
654 }).when(mockView).createAccessibilityNodeInfo();
655 return mockView;
656 }
657
658 private void assertEventTypeClearsNode(int eventType) {
Eugene Susla74c6cba2016-12-28 14:42:54 -0800659 assertEventTypeClearsNode(eventType, true);
660 }
661
662 private void assertEventTypeClearsNode(int eventType, boolean clears) {
Phil Weaverb010b122016-08-17 17:47:48 -0700663 final int nodeId = 0xBEEF;
664 AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(nodeId, WINDOW_ID_1);
665 long id = nodeInfo.getSourceNodeId();
666 mAccessibilityCache.add(nodeInfo);
667 nodeInfo.recycle();
668 mAccessibilityCache.onAccessibilityEvent(AccessibilityEvent.obtain(eventType));
Eugene Susla74c6cba2016-12-28 14:42:54 -0800669 AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1, id);
670 try {
671 if (clears) {
672 assertNull(cachedNode);
673 } else {
674 assertNotNull(cachedNode);
675 }
676 } finally {
677 if (cachedNode != null) {
678 cachedNode.recycle();
679 }
680 }
Phil Weaverb010b122016-08-17 17:47:48 -0700681 }
682
683 private AccessibilityNodeInfo getParentNode() {
684 AccessibilityNodeInfo parentNodeInfo =
685 getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
686 parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
687 return parentNodeInfo;
688 }
689
690 private AccessibilityNodeInfo getChildNode() {
691 AccessibilityNodeInfo childNodeInfo =
692 getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
693 childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
694 return childNodeInfo;
695 }
696
697 private void assertEventClearsParentAndChild(AccessibilityEvent event) {
698 AccessibilityNodeInfo parentNodeInfo = getParentNode();
699 AccessibilityNodeInfo childNodeInfo = getChildNode();
700 long parentId = parentNodeInfo.getSourceNodeId();
701 long childId = childNodeInfo.getSourceNodeId();
702 mAccessibilityCache.add(parentNodeInfo);
703 mAccessibilityCache.add(childNodeInfo);
704
705 mAccessibilityCache.onAccessibilityEvent(event);
706 parentNodeInfo.recycle();
707 childNodeInfo.recycle();
708
709 AccessibilityNodeInfo parentFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, parentId);
710 AccessibilityNodeInfo childFromCache = mAccessibilityCache.getNode(WINDOW_ID_1, childId);
711 try {
712 assertNull(parentFromCache);
713 assertNull(childFromCache);
714 } finally {
715 if (parentFromCache != null) {
716 parentFromCache.recycle();
717 }
718 if (childFromCache != null) {
719 childFromCache.recycle();
720 }
721 }
722 }
723}