1 /* 2 * Copyright (C) 2021 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 com.android.server.accessibility; 18 19 20 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS; 21 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST; 22 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST; 23 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID; 24 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS; 25 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE; 26 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS; 27 import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; 28 29 import static org.junit.Assert.assertEquals; 30 import static org.mockito.ArgumentMatchers.anyInt; 31 import static org.mockito.ArgumentMatchers.anyList; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.doAnswer; 34 import static org.mockito.Mockito.never; 35 import static org.mockito.Mockito.times; 36 import static org.mockito.Mockito.verify; 37 38 import android.app.Instrumentation; 39 import android.content.Context; 40 import android.os.RemoteException; 41 import android.view.AccessibilityInteractionController; 42 import android.view.View; 43 import android.view.ViewRootImpl; 44 import android.view.WindowManager; 45 import android.view.accessibility.AccessibilityNodeIdManager; 46 import android.view.accessibility.AccessibilityNodeInfo; 47 import android.view.accessibility.AccessibilityNodeProvider; 48 import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 49 import android.widget.FrameLayout; 50 import android.widget.TextView; 51 52 import androidx.test.platform.app.InstrumentationRegistry; 53 import androidx.test.runner.AndroidJUnit4; 54 55 import org.junit.After; 56 import org.junit.Before; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.mockito.ArgumentCaptor; 60 import org.mockito.Captor; 61 import org.mockito.Mock; 62 import org.mockito.MockitoAnnotations; 63 64 import java.util.ArrayList; 65 import java.util.List; 66 67 /** 68 * Tests that verify expected node and prefetched node results when finding a view by node id. We 69 * send some requests to the controller via View methods to control message timing. 70 */ 71 @RunWith(AndroidJUnit4.class) 72 public class AccessibilityInteractionControllerNodeRequestsTest { 73 private AccessibilityInteractionController mAccessibilityInteractionController; 74 @Mock 75 private IAccessibilityInteractionConnectionCallback mMockClientCallback1; 76 @Mock 77 private IAccessibilityInteractionConnectionCallback mMockClientCallback2; 78 79 @Captor 80 private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor; 81 @Captor 82 private ArgumentCaptor<List<AccessibilityNodeInfo>> mFindInfosCaptor; 83 @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor; 84 85 private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); 86 private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1; 87 private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2; 88 89 private static final String ROOT_FRAME_LAYOUT_DESCRIPTION = "rootFrameLayout"; 90 private static final String TEXT_VIEW_1_DESCRIPTION = "textView1"; 91 private static final String TEXT_VIEW_2_DESCRIPTION = "textView2"; 92 private static final String CHILD_FRAME_DESCRIPTION = "childFrameLayout"; 93 private static final String TEXT_VIEW_3_DESCRIPTION = "textView3"; 94 private static final String TEXT_VIEW_4_DESCRIPTION = "textView4"; 95 private static final String VIRTUAL_VIEW_1_DESCRIPTION = "virtual descendant 1"; 96 private static final String VIRTUAL_VIEW_2_DESCRIPTION = "virtual descendant 2"; 97 private static final String VIRTUAL_VIEW_3_DESCRIPTION = "virtual descendant 3"; 98 99 private TestFrameLayout mRootFrameLayout; 100 private TestFrameLayout mChildFrameLayout; 101 private TestTextView mTextView1; 102 private TestTextView mTextView2; 103 private TestTextView mTextView3; 104 private TestTextView mTextView4; 105 106 private boolean mSendClient1RequestForTextAfterTextPrefetched; 107 private boolean mSendClient2RequestForTextAfterTextPrefetched; 108 private boolean mSendRequestForTextAndIncludeUnImportantViews; 109 private boolean mSendClient1RequestForRootAfterTextPrefetched; 110 private boolean mSendClient2RequestForTextAfterRootPrefetched; 111 112 private int mMockClient1InteractionId; 113 private int mMockClient2InteractionId; 114 115 @Before setUp()116 public void setUp() throws Throwable { 117 MockitoAnnotations.initMocks(this); 118 119 mInstrumentation.runOnMainSync(() -> { 120 final Context context = mInstrumentation.getTargetContext(); 121 final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); 122 123 mTextView1 = new TestTextView(context, 1, TEXT_VIEW_1_DESCRIPTION); 124 mTextView2 = new TestTextView(context, 2, TEXT_VIEW_2_DESCRIPTION); 125 mTextView3 = new TestTextView(context, 4, TEXT_VIEW_3_DESCRIPTION); 126 mTextView4 = new TestTextView(context, 5, TEXT_VIEW_4_DESCRIPTION); 127 128 mChildFrameLayout = new TestFrameLayout(context, 3, 129 CHILD_FRAME_DESCRIPTION, new ArrayList<>(List.of(mTextView4))); 130 mRootFrameLayout = new TestFrameLayout(context, 0, ROOT_FRAME_LAYOUT_DESCRIPTION, 131 new ArrayList<>( 132 List.of(mTextView1, mTextView2, mChildFrameLayout, mTextView3))); 133 134 mRootFrameLayout.addView(mTextView1); 135 mRootFrameLayout.addView(mTextView2); 136 mChildFrameLayout.addView(mTextView4); 137 mRootFrameLayout.addView(mChildFrameLayout); 138 mRootFrameLayout.addView(mTextView3); 139 140 // Layout 141 // mRootFrameLayout 142 // / | | \ 143 // mTextView1 mTextView2 mChildFrameLayout mTextView3 144 // | 145 // mTextView4 146 147 // The controller retrieves views through this manager, and registration happens on 148 // when attached to a window, which we don't have. We can simply reference 149 // RootFrameLayout with ROOT_NODE_ID. 150 AccessibilityNodeIdManager.getInstance().registerViewWithId( 151 mTextView1, mTextView1.getAccessibilityViewId()); 152 AccessibilityNodeIdManager.getInstance().registerViewWithId( 153 mTextView2, mTextView2.getAccessibilityViewId()); 154 AccessibilityNodeIdManager.getInstance().registerViewWithId( 155 mTextView3, mTextView3.getAccessibilityViewId()); 156 AccessibilityNodeIdManager.getInstance().registerViewWithId( 157 mChildFrameLayout, mChildFrameLayout.getAccessibilityViewId()); 158 AccessibilityNodeIdManager.getInstance().registerViewWithId( 159 mTextView4, mTextView4.getAccessibilityViewId()); 160 try { 161 viewRootImpl.setView(mRootFrameLayout, new WindowManager.LayoutParams(), null); 162 163 } catch (WindowManager.BadTokenException e) { 164 // activity isn't running, we will ignore BadTokenException. 165 } 166 167 mAccessibilityInteractionController = 168 new AccessibilityInteractionController(viewRootImpl); 169 }); 170 171 } 172 173 @After tearDown()174 public void tearDown() throws Throwable { 175 AccessibilityNodeIdManager.getInstance().unregisterViewWithId( 176 mTextView1.getAccessibilityViewId()); 177 AccessibilityNodeIdManager.getInstance().unregisterViewWithId( 178 mTextView2.getAccessibilityViewId()); 179 AccessibilityNodeIdManager.getInstance().unregisterViewWithId( 180 mTextView3.getAccessibilityViewId()); 181 AccessibilityNodeIdManager.getInstance().unregisterViewWithId( 182 mTextView4.getAccessibilityViewId()); 183 AccessibilityNodeIdManager.getInstance().unregisterViewWithId( 184 mChildFrameLayout.getAccessibilityViewId()); 185 } 186 187 /** 188 * Tests a basic request for the root node with prefetch flag 189 * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS_HYBRID} 190 * 191 * @throws RemoteException 192 */ 193 @Test testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants()194 public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants() 195 throws RemoteException { 196 // Request for our RootFrameLayout. 197 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 198 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 199 mInstrumentation.waitForIdleSync(); 200 201 // Verify we get RootFrameLayout. 202 verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( 203 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); 204 AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); 205 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); 206 207 verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( 208 mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); 209 // The descendants are RootFrameLayout's 5 descendants. 210 List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); 211 assertEquals(5, prefetchedNodes.size()); 212 assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); 213 assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); 214 assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); 215 assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); 216 assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(4).getContentDescription()); 217 } 218 219 /** 220 * Tests a basic request for TextView1's node with prefetch flag. 221 * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} and 222 * {@link AccessibilityNodeInfo#FLAG_PREFETCH_ANCESTORS}. 223 * 224 * @throws RemoteException 225 */ 226 @Test testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblingsAndParent()227 public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblingsAndParent() 228 throws RemoteException { 229 // Request for TextView1. 230 sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId( 231 mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID), 232 mMockClientCallback1, mMockClient1InteractionId, 233 FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS); 234 mInstrumentation.waitForIdleSync(); 235 236 // Verify we get TextView1. 237 verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( 238 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); 239 AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); 240 assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); 241 242 // Verify the prefetched sibling of TextView1 is TextView2, ChildFrameLayout, and TextView3. 243 // The predecessor is RootFrameLayout. 244 verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( 245 mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); 246 List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); 247 assertEquals(4, prefetchedNodes.size()); 248 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); 249 assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); 250 assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); 251 assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); 252 } 253 254 /** 255 * Tests a series of controller requests to prevent prefetching. 256 * Request 1: Client 1 requests the root node 257 * Request 2: When the root node is initialized in 258 * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}, 259 * Client 2 requests TestTextView1's node 260 * 261 * Request 2 on the queue prevents prefetching for Request 1. 262 * 263 * @throws RemoteException 264 */ 265 @Test testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch()266 public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch() 267 throws RemoteException { 268 mSendClient2RequestForTextAfterRootPrefetched = true; 269 mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() { 270 @Override 271 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 272 super.onInitializeAccessibilityNodeInfo(host, info); 273 final long nodeId = AccessibilityNodeInfo.makeNodeId( 274 mTextView1.getAccessibilityViewId(), 275 AccessibilityNodeProvider.HOST_VIEW_ID); 276 277 if (mSendClient2RequestForTextAfterRootPrefetched) { 278 mSendClient2RequestForTextAfterRootPrefetched = false; 279 280 // Enqueue a request when this node is found from client 2 for TextView1. 281 sendNodeRequestToController(nodeId, mMockClientCallback2, 282 mMockClient2InteractionId, 283 FLAG_PREFETCH_SIBLINGS); 284 } 285 } 286 }); 287 // Client 1 request for RootFrameLayout. 288 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 289 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 290 291 mInstrumentation.waitForIdleSync(); 292 293 // Verify client 1 gets RootFrameLayout. 294 verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( 295 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); 296 AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); 297 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); 298 299 // The second request is put in the queue in the RootFrameLayout's onInitializeA11yNodeInfo, 300 // meaning prefetching is does not occur for first request. 301 verify(mMockClientCallback1, never()) 302 .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); 303 304 // Verify client 2 gets TextView1. 305 verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult( 306 mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); 307 infoSentToService = mFindInfoCaptor.getValue(); 308 assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); 309 310 // Verify the prefetched sibling of TextView1 is TextView2 and ChildFrameLayout 311 // (FLAG_PREFETCH_SIBLINGS). The parent, RootFrameLayout, is also retrieved. Since 312 // predecessors takes priority over siblings, RootFrameLayout is the first node in the list. 313 verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult( 314 mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId)); 315 List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); 316 assertEquals(4, prefetchedNodes.size()); 317 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); 318 assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); 319 assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); 320 assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); 321 } 322 323 /** 324 * Tests a series of controller same-service requests to interrupt prefetching and satisfy a 325 * pending node request. 326 * Request 1: Request the root node 327 * Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching, 328 * request TestTextView1's node 329 * 330 * Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks 331 * if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for 332 * TestTextView1's node. Request 2 is fulfilled, so it is removed from queue and does not 333 * prefetch. TestTextView1 is removed from Request's 1's prefetched results, meaning the list 334 * is empty. 335 * 336 * @throws RemoteException 337 */ 338 @Test testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg()339 public void testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg() 340 throws RemoteException { 341 mSendClient1RequestForTextAfterTextPrefetched = true; 342 343 mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 344 @Override 345 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 346 super.onInitializeAccessibilityNodeInfo(host, info); 347 final long nodeId = AccessibilityNodeInfo.makeNodeId( 348 mTextView1.getAccessibilityViewId(), 349 AccessibilityNodeProvider.HOST_VIEW_ID); 350 351 if (mSendClient1RequestForTextAfterTextPrefetched) { 352 // Prevent a loop when processing this node's second request. 353 mSendClient1RequestForTextAfterTextPrefetched = false; 354 // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue 355 // a same-client request for TextView1. 356 sendNodeRequestToController(nodeId, mMockClientCallback1, 357 ++mMockClient1InteractionId, 358 FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS); 359 360 } 361 } 362 }); 363 // Client 1 requests RootFrameLayout. 364 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 365 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 366 367 // Flush out all messages. 368 mInstrumentation.waitForIdleSync(); 369 370 // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in 371 // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets 372 // two node results for FrameLayout and TextView1. 373 verify(mMockClientCallback1, times(2)) 374 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); 375 376 List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues(); 377 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); 378 assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription()); 379 380 // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in 381 // pending requests. The prefetched TextView1 satisfied the second request. This is removed 382 // from the first request's prefetch list, which is now empty. The second request is removed 383 // from queue. 384 verify(mMockClientCallback1, never()) 385 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), 386 eq(mMockClient1InteractionId - 1)); 387 } 388 389 /** 390 * Tests a series of controller same-service requests to interrupt prefetching and satisfy a 391 * pending node request. 392 * Request 1: Request the root node 393 * Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching, 394 * request the root node 395 * 396 * Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks 397 * if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for 398 * the root node. Request 2 is fulfilled, so it is removed from queue and does not 399 * prefetch. 400 * 401 * @throws RemoteException 402 */ 403 @Test testFindRoot_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg()404 public void testFindRoot_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg() 405 throws RemoteException { 406 mSendClient1RequestForRootAfterTextPrefetched = true; 407 408 mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 409 @Override 410 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 411 super.onInitializeAccessibilityNodeInfo(host, info); 412 final long nodeId = AccessibilityNodeInfo.makeNodeId( 413 mRootFrameLayout.getAccessibilityViewId(), 414 AccessibilityNodeProvider.HOST_VIEW_ID); 415 416 if (mSendClient1RequestForRootAfterTextPrefetched) { 417 // Prevent a loop when processing this node's second request. 418 mSendClient1RequestForRootAfterTextPrefetched = false; 419 // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a 420 // same-client request for FrameLayout. 421 sendNodeRequestToController(nodeId, mMockClientCallback1, 422 ++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 423 424 } 425 } 426 }); 427 // Client 1 requests RootFrameLayout. 428 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 429 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 430 431 // Flush out all messages. 432 mInstrumentation.waitForIdleSync(); 433 434 // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in 435 // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets 436 // two node results for FrameLayout and TextView1. 437 verify(mMockClientCallback1, times(2)) 438 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); 439 440 List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues(); 441 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); 442 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription()); 443 444 // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in 445 // pending requests. The first requested node (FrameLayout) is also checked, and this 446 // satisfies the second request. The second request is removed from queue and prefetching 447 // for this request never occurs. 448 verify(mMockClientCallback1, times(1)) 449 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), 450 eq(mMockClient1InteractionId - 1)); 451 List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); 452 assertEquals(1, prefetchedNodes.size()); 453 assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); 454 } 455 456 /** 457 * Like above, but tests a series of controller requests from different services to interrupt 458 * prefetching and satisfy a pending node request. 459 * 460 * @throws RemoteException 461 */ 462 @Test testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg()463 public void testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg() 464 throws RemoteException { 465 mSendClient2RequestForTextAfterTextPrefetched = true; 466 mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 467 @Override 468 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 469 super.onInitializeAccessibilityNodeInfo(host, info); 470 final long nodeId = AccessibilityNodeInfo.makeNodeId( 471 mTextView1.getAccessibilityViewId(), 472 AccessibilityNodeProvider.HOST_VIEW_ID); 473 474 if (mSendClient2RequestForTextAfterTextPrefetched) { 475 mSendClient2RequestForTextAfterTextPrefetched = false; 476 // TextView1 is prefetched here. Now enqueue client 2's request for 477 // TextView1. 478 sendNodeRequestToController(nodeId, mMockClientCallback2, 479 mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); 480 } 481 } 482 }); 483 // Client 1 requests RootFrameLayout. 484 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 485 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 486 487 mInstrumentation.waitForIdleSync(); 488 489 // Verify client 1 gets RootFrameLayout. 490 verify(mMockClientCallback1, times(1)) 491 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); 492 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, 493 mFindInfoCaptor.getValue().getContentDescription()); 494 495 // Verify client 1 doesn't have prefetched nodes. 496 verify(mMockClientCallback1, never()) 497 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), 498 eq(mMockClient1InteractionId)); 499 500 // Verify client 2 gets TextView1. 501 verify(mMockClientCallback2, times(1)) 502 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); 503 504 assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription()); 505 506 // The second request was removed from queue and prefetching for this client request never 507 // occurred as it was satisfied. 508 verify(mMockClientCallback2, never()) 509 .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); 510 511 } 512 513 @Test testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest()514 public void testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest() 515 throws RemoteException { 516 mSendRequestForTextAndIncludeUnImportantViews = true; 517 mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 518 @Override 519 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 520 super.onInitializeAccessibilityNodeInfo(host, info); 521 final long nodeId = AccessibilityNodeInfo.makeNodeId( 522 mTextView1.getAccessibilityViewId(), 523 AccessibilityNodeProvider.HOST_VIEW_ID); 524 525 if (mSendRequestForTextAndIncludeUnImportantViews) { 526 mSendRequestForTextAndIncludeUnImportantViews = false; 527 // TextView1 is prefetched here for client 1. Now enqueue a request from a 528 // different client that holds different fetch flags for TextView1. 529 sendNodeRequestToController(nodeId, mMockClientCallback2, 530 mMockClient2InteractionId, 531 FLAG_PREFETCH_SIBLINGS 532 | FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS 533 | FLAG_PREFETCH_ANCESTORS); 534 } 535 } 536 }); 537 538 // Mockito does not make copies of objects when called. It holds references, so 539 // the captor would point to client 2's results after all requests are processed. Verify 540 // prefetched node immediately. 541 doAnswer(invocation -> { 542 List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0); 543 assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription()); 544 return null; 545 }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(), 546 eq(mMockClient1InteractionId)); 547 548 // Client 1 requests RootFrameLayout. 549 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 550 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 551 552 mInstrumentation.waitForIdleSync(); 553 554 // Verify client 1 gets RootFrameLayout. 555 verify(mMockClientCallback1, times(1)) 556 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), 557 eq(mMockClient1InteractionId)); 558 559 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, 560 mFindInfoCaptor.getValue().getContentDescription()); 561 562 // Verify client 1 has prefetched results. The only prefetched node is TextView1 563 // (from above doAnswer). 564 verify(mMockClientCallback1, times(1)) 565 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), 566 eq(mMockClient1InteractionId)); 567 568 // Verify client 2 gets TextView1. 569 verify(mMockClientCallback2, times(1)) 570 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), 571 eq(mMockClient2InteractionId)); 572 assertEquals(TEXT_VIEW_1_DESCRIPTION, 573 mFindInfoCaptor.getValue().getContentDescription()); 574 // Verify client 2 gets TextView1's siblings and its parent as prefetched nodes. 575 verify(mMockClientCallback2, times(1)) 576 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), 577 eq(mMockClient2InteractionId)); 578 List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue(); 579 assertEquals(4, prefetchedNode.size()); 580 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNode.get(0).getContentDescription()); 581 assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(1).getContentDescription()); 582 assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNode.get(2).getContentDescription()); 583 assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNode.get(3).getContentDescription()); 584 } 585 586 /** 587 * Tests a request for 4 nodes using depth first traversal. 588 * Request 1: Request the root node. 589 * Request 2: When TextView4 is prefetched, send a request for the root node. Depth first 590 * traversal completes here. 591 * Out of the 5 descendants, the root frame's 3rd child (TextView3) should not be prefetched, 592 * since this was not reached by the df-traversal. 593 * 594 * Layout 595 * mRootFrameLayout 596 * / | | \ 597 * mTextView1 mTextView2 mChildFrameLayout *mTextView3* 598 * | 599 * mTextView4 600 * @throws RemoteException 601 */ 602 @Test testFindRootView_depthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants()603 public void testFindRootView_depthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants() 604 throws RemoteException { 605 mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 606 @Override 607 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 608 super.onInitializeAccessibilityNodeInfo(host, info); 609 final long nodeId = AccessibilityNodeInfo.makeNodeId( 610 mRootFrameLayout.getAccessibilityViewId(), 611 AccessibilityNodeProvider.HOST_VIEW_ID); 612 // This request is satisfied by first request. 613 sendNodeRequestToController(nodeId, mMockClientCallback2, 614 mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 615 616 } 617 }); 618 // Request for our RootFrameLayout. 619 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 620 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST); 621 mInstrumentation.waitForIdleSync(); 622 623 // Verify we get RootFrameLayout. 624 verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( 625 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); 626 AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); 627 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); 628 629 verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( 630 mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); 631 // Prefetch all the descendants besides TextView3. 632 List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); 633 assertEquals(4, prefetchedNodes.size()); 634 assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); 635 assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); 636 assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); 637 assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); 638 } 639 640 /** 641 * Tests a request for 4 nodes using breadth first traversal. 642 * Request 1: Request the root node 643 * Request 2: When TextView3 is prefetched, send a request for the root node. Breadth first 644 * traversal completes here. 645 * Out of the 5 descendants, the child frame's child (TextView4) should not be prefetched, since 646 * this was not reached by the bf-traversal. 647 * Layout 648 * mRootFrameLayout 649 * / | | \ 650 * mTextView1 mTextView2 mChildFrameLayout *mTextView3* 651 * | 652 * *mTextView4* 653 * @throws RemoteException 654 */ 655 @Test testFindRootView_breadthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants()656 public void testFindRootView_breadthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants() 657 throws RemoteException { 658 659 mTextView3.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 660 @Override 661 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 662 super.onInitializeAccessibilityNodeInfo(host, info); 663 final long nodeId = AccessibilityNodeInfo.makeNodeId( 664 mRootFrameLayout.getAccessibilityViewId(), 665 AccessibilityNodeProvider.HOST_VIEW_ID); 666 667 // This request is satisfied by first request. 668 sendNodeRequestToController(nodeId, mMockClientCallback2, 669 mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 670 } 671 }); 672 // Request for our RootFrameLayout. 673 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 674 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST); 675 mInstrumentation.waitForIdleSync(); 676 677 // Verify we get RootFrameLayout. 678 verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( 679 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); 680 AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); 681 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); 682 683 verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( 684 mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); 685 // Prefetch all the descendants besides TextView4. 686 List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); 687 assertEquals(4, prefetchedNodes.size()); 688 assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); 689 assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); 690 assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); 691 assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); 692 } 693 694 /** 695 * Tests a request that should not have prefetching interrupted. 696 * Request 1: Client 1 requests the root node 697 * Request 2: When the root node is initialized in 698 * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}, 699 * Client 2 requests TextView1's node 700 * 701 * Request 1 is not interrupted during prefetch, and its prefetched node satisfies Request 2. 702 * 703 * @throws RemoteException 704 */ 705 @Test testFindRootAndTextNodes_withNoInterruptStrategy_shouldSatisfySecondRequest()706 public void testFindRootAndTextNodes_withNoInterruptStrategy_shouldSatisfySecondRequest() 707 throws RemoteException { 708 mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 709 @Override 710 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 711 super.onInitializeAccessibilityNodeInfo(host, info); 712 final long nodeId = AccessibilityNodeInfo.makeNodeId( 713 mTextView1.getAccessibilityViewId(), 714 AccessibilityNodeProvider.HOST_VIEW_ID); 715 716 // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue a 717 // same-client request for RootFrameLayout. 718 sendNodeRequestToController(nodeId, mMockClientCallback2, 719 mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); 720 } 721 }); 722 723 // Client 1 request for RootFrameLayout. 724 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 725 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID 726 | FLAG_PREFETCH_UNINTERRUPTIBLE); 727 728 mInstrumentation.waitForIdleSync(); 729 730 // When the controller returns the nodes, it clears the sent list. Check immediately since 731 // the captor will be cleared. 732 doAnswer(invocation -> { 733 List<AccessibilityNodeInfo> nodes = invocation.getArgument(0); 734 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, nodes.get(0).getContentDescription()); 735 assertEquals(TEXT_VIEW_2_DESCRIPTION, nodes.get(1).getContentDescription()); 736 assertEquals(CHILD_FRAME_DESCRIPTION, nodes.get(2).getContentDescription()); 737 assertEquals(TEXT_VIEW_3_DESCRIPTION, nodes.get(3).getContentDescription()); 738 assertEquals(TEXT_VIEW_4_DESCRIPTION, nodes.get(4).getContentDescription()); 739 return null; 740 }).when(mMockClientCallback1).setFindAccessibilityNodeInfosResult( 741 anyList(), eq(mMockClient1InteractionId)); 742 743 verify(mMockClientCallback1, never()) 744 .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); 745 746 // Verify client 2 gets TextView1. 747 verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult( 748 mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); 749 AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); 750 assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); 751 } 752 753 /** 754 * Tests a request for root node where a virtual hierarchy is prefetched. 755 * 756 * Layout 757 * mRootFrameLayout 758 * / | | \ 759 * mTextView1 mTextView2 mChildFrameLayout *mTextView3* 760 * | 761 * *mTextView4* 762 * | \ 763 * virtual view 1 virtual view 2 764 * | 765 * virtual view 3 766 * @throws RemoteException 767 */ 768 @Test testFindRootView_withVirtualView()769 public void testFindRootView_withVirtualView() 770 throws RemoteException { 771 mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){ 772 @Override 773 public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) { 774 return new AccessibilityNodeProvider() { 775 @Override 776 public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { 777 if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) { 778 AccessibilityNodeInfo node = new AccessibilityNodeInfo(host); 779 node.addChild(host, 1); 780 node.addChild(host, 2); 781 node.setContentDescription(TEXT_VIEW_4_DESCRIPTION); 782 return node; 783 } else if (virtualViewId == 1) { 784 AccessibilityNodeInfo node = new AccessibilityNodeInfo( 785 host, virtualViewId); 786 node.setParent(host); 787 node.setContentDescription(VIRTUAL_VIEW_1_DESCRIPTION); 788 node.addChild(host, 3); 789 return node; 790 } else if (virtualViewId == 2 || virtualViewId == 3) { 791 AccessibilityNodeInfo node = new AccessibilityNodeInfo( 792 host, virtualViewId); 793 node.setParent(host); 794 node.setContentDescription(virtualViewId == 2 795 ? VIRTUAL_VIEW_2_DESCRIPTION 796 : VIRTUAL_VIEW_3_DESCRIPTION); 797 return node; 798 } 799 return null; 800 } 801 }; 802 } 803 }); 804 // Request for our RootFrameLayout. 805 sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, 806 mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST); 807 mInstrumentation.waitForIdleSync(); 808 809 // Verify we get RootFrameLayout. 810 verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( 811 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); 812 AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); 813 assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); 814 815 verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( 816 mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); 817 818 List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); 819 assertEquals(8, prefetchedNodes.size()); 820 assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); 821 assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); 822 assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); 823 assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); 824 825 assertEquals(VIRTUAL_VIEW_1_DESCRIPTION, prefetchedNodes.get(4).getContentDescription()); 826 assertEquals(VIRTUAL_VIEW_3_DESCRIPTION, prefetchedNodes.get(5).getContentDescription()); 827 828 assertEquals(VIRTUAL_VIEW_2_DESCRIPTION, prefetchedNodes.get(6).getContentDescription()); 829 assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(7).getContentDescription()); 830 831 832 } 833 sendNodeRequestToController(long requestedNodeId, IAccessibilityInteractionConnectionCallback callback, int interactionId, int prefetchFlags)834 private void sendNodeRequestToController(long requestedNodeId, 835 IAccessibilityInteractionConnectionCallback callback, int interactionId, 836 int prefetchFlags) { 837 final int processAndThreadId = callback == mMockClientCallback1 838 ? MOCK_CLIENT_1_THREAD_AND_PROCESS_ID 839 : MOCK_CLIENT_2_THREAD_AND_PROCESS_ID; 840 841 mAccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdClientThread( 842 requestedNodeId, 843 null, interactionId, 844 callback, prefetchFlags, 845 processAndThreadId, 846 processAndThreadId, null, null, null); 847 848 } 849 850 private class TestFrameLayout extends FrameLayout { 851 private int mA11yId; 852 private String mContentDescription; 853 ArrayList<View> mChildren; 854 TestFrameLayout(Context context, int a11yId, String contentDescription, ArrayList<View> children)855 TestFrameLayout(Context context, int a11yId, String contentDescription, 856 ArrayList<View> children) { 857 super(context); 858 mA11yId = a11yId; 859 mContentDescription = contentDescription; 860 mChildren = children; 861 } 862 863 @Override getWindowVisibility()864 public int getWindowVisibility() { 865 // We aren't attached to a window so let's pretend 866 return VISIBLE; 867 } 868 869 @Override isShown()870 public boolean isShown() { 871 // Controller check 872 return true; 873 } 874 875 @Override getAccessibilityViewId()876 public int getAccessibilityViewId() { 877 // static id doesn't reset after tests so return the same one 878 return mA11yId; 879 } 880 881 @Override addChildrenForAccessibility(ArrayList<View> outChildren)882 public void addChildrenForAccessibility(ArrayList<View> outChildren) { 883 // ViewGroup#addChildrenForAccessbility sorting logic will switch these two 884 for (View view : mChildren) { 885 outChildren.add(view); 886 } 887 } 888 889 @Override includeForAccessibility()890 public boolean includeForAccessibility() { 891 return true; 892 } 893 894 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)895 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 896 super.onInitializeAccessibilityNodeInfo(info); 897 info.setContentDescription(mContentDescription); 898 } 899 } 900 901 private class TestTextView extends TextView { 902 private int mA11yId; 903 private String mContentDescription; TestTextView(Context context, int a11yId, String contentDescription)904 TestTextView(Context context, int a11yId, String contentDescription) { 905 super(context); 906 mA11yId = a11yId; 907 mContentDescription = contentDescription; 908 } 909 910 @Override getWindowVisibility()911 public int getWindowVisibility() { 912 return VISIBLE; 913 } 914 915 @Override isShown()916 public boolean isShown() { 917 return true; 918 } 919 920 @Override getAccessibilityViewId()921 public int getAccessibilityViewId() { 922 return mA11yId; 923 } 924 925 @Override includeForAccessibility()926 public boolean includeForAccessibility() { 927 return true; 928 } 929 930 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)931 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 932 super.onInitializeAccessibilityNodeInfo(info); 933 info.setContentDescription(mContentDescription); 934 } 935 } 936 } 937