1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.accessibilityservice.cts; 18 19 import static android.Manifest.permission.POST_NOTIFICATIONS; 20 import static android.accessibility.cts.common.InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE; 21 import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService; 22 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; 23 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; 24 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType; 25 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction; 26 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource; 27 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle; 28 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle; 29 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen; 30 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS; 31 import static android.accessibilityservice.cts.utils.RunOnMainUtils.getOnMain; 32 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; 33 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED; 34 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; 35 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP; 36 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_IN_DIRECTION; 37 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP; 38 39 import static com.google.common.truth.Truth.assertThat; 40 41 import static org.junit.Assert.assertEquals; 42 import static org.junit.Assert.assertFalse; 43 import static org.junit.Assert.assertNotNull; 44 import static org.junit.Assert.assertThrows; 45 import static org.junit.Assert.assertTrue; 46 import static org.junit.Assert.fail; 47 import static org.mockito.ArgumentMatchers.any; 48 import static org.mockito.ArgumentMatchers.argThat; 49 import static org.mockito.ArgumentMatchers.eq; 50 import static org.mockito.Mockito.inOrder; 51 import static org.mockito.Mockito.mock; 52 import static org.mockito.Mockito.timeout; 53 import static org.mockito.Mockito.verify; 54 55 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule; 56 import android.accessibility.cts.common.InstrumentedAccessibilityService; 57 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule; 58 import android.accessibility.cts.common.ShellCommandBuilder; 59 import android.accessibilityservice.AccessibilityServiceInfo; 60 import android.accessibilityservice.MagnificationConfig; 61 import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity; 62 import android.app.Activity; 63 import android.app.AlertDialog; 64 import android.app.Instrumentation; 65 import android.app.Notification; 66 import android.app.NotificationChannel; 67 import android.app.NotificationManager; 68 import android.app.PendingIntent; 69 import android.app.Service; 70 import android.app.UiAutomation; 71 import android.appwidget.AppWidgetHost; 72 import android.appwidget.AppWidgetManager; 73 import android.appwidget.AppWidgetProviderInfo; 74 import android.content.ComponentName; 75 import android.content.Context; 76 import android.content.Intent; 77 import android.content.pm.PackageManager; 78 import android.content.res.Configuration; 79 import android.content.res.Resources; 80 import android.graphics.Rect; 81 import android.graphics.Region; 82 import android.os.Parcel; 83 import android.os.Process; 84 import android.os.SystemClock; 85 import android.platform.test.annotations.AppModeFull; 86 import android.platform.test.annotations.AsbSecurityTest; 87 import android.platform.test.annotations.FlakyTest; 88 import android.platform.test.annotations.Presubmit; 89 import android.provider.Settings; 90 import android.text.TextUtils; 91 import android.util.Log; 92 import android.view.InputDevice; 93 import android.view.MotionEvent; 94 import android.view.TouchDelegate; 95 import android.view.View; 96 import android.view.Window; 97 import android.view.WindowManager; 98 import android.view.accessibility.AccessibilityEvent; 99 import android.view.accessibility.AccessibilityManager; 100 import android.view.accessibility.AccessibilityNodeInfo; 101 import android.view.accessibility.AccessibilityWindowInfo; 102 import android.widget.Button; 103 import android.widget.EditText; 104 import android.widget.ListView; 105 106 import androidx.test.InstrumentationRegistry; 107 import androidx.test.filters.MediumTest; 108 import androidx.test.rule.ActivityTestRule; 109 import androidx.test.runner.AndroidJUnit4; 110 111 import com.android.compatibility.common.util.ApiTest; 112 import com.android.compatibility.common.util.CddTest; 113 import com.android.compatibility.common.util.CtsMouseUtil; 114 import com.android.compatibility.common.util.ShellUtils; 115 import com.android.compatibility.common.util.TestUtils; 116 import com.android.sts.common.util.StsExtraBusinessLogicTestCase; 117 118 import org.junit.After; 119 import org.junit.AfterClass; 120 import org.junit.Before; 121 import org.junit.BeforeClass; 122 import org.junit.Rule; 123 import org.junit.Test; 124 import org.junit.rules.RuleChain; 125 import org.junit.runner.RunWith; 126 127 import java.time.Duration; 128 import java.util.ArrayDeque; 129 import java.util.Deque; 130 import java.util.Iterator; 131 import java.util.List; 132 import java.util.concurrent.TimeoutException; 133 import java.util.concurrent.atomic.AtomicBoolean; 134 import java.util.concurrent.atomic.AtomicInteger; 135 import java.util.concurrent.atomic.AtomicReference; 136 137 /** 138 * This class performs end-to-end testing of the accessibility feature by 139 * creating an {@link Activity} and poking around so {@link AccessibilityEvent}s 140 * are generated and their correct dispatch verified. 141 */ 142 @RunWith(AndroidJUnit4.class) 143 @CddTest(requirements = {"3.10/C-1-1,C-1-2"}) 144 @Presubmit 145 public class AccessibilityEndToEndTest extends StsExtraBusinessLogicTestCase { 146 147 private static final String LOG_TAG = "AccessibilityEndToEndTest"; 148 149 private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND = 150 "appwidget grantbind --package android.accessibilityservice.cts --user "; 151 152 private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND = 153 "appwidget revokebind --package android.accessibilityservice.cts --user "; 154 155 private static final String APP_WIDGET_PROVIDER_PACKAGE = "foo.bar.baz"; 156 157 private static final int TIMEOUT_FOR_MOTION_EVENT_INTERCEPTION_MS = 1000; 158 159 private static Instrumentation sInstrumentation; 160 private static UiAutomation sUiAutomation; 161 162 private AccessibilityEndToEndActivity mActivity; 163 164 private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule = 165 new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false); 166 167 private AccessibilityDumpOnFailureRule mDumpOnFailureRule = 168 new AccessibilityDumpOnFailureRule(); 169 170 private final InstrumentedAccessibilityServiceTestRule< 171 StubMotionInterceptingAccessibilityService> 172 mMotionInterceptingServiceRule = new InstrumentedAccessibilityServiceTestRule<>( 173 StubMotionInterceptingAccessibilityService.class, false); 174 175 @Rule 176 public final RuleChain mRuleChain = RuleChain 177 .outerRule(mActivityRule) 178 .around(mMotionInterceptingServiceRule) 179 .around(mDumpOnFailureRule); 180 181 @BeforeClass oneTimeSetup()182 public static void oneTimeSetup() throws Exception { 183 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 184 sUiAutomation = sInstrumentation.getUiAutomation(); 185 } 186 187 @AfterClass postTestTearDown()188 public static void postTestTearDown() { 189 sUiAutomation.destroy(); 190 } 191 192 @Before setUp()193 public void setUp() throws Exception { 194 sUiAutomation.adoptShellPermissionIdentity(POST_NOTIFICATIONS); 195 mActivity = launchActivityAndWaitForItToBeOnscreen( 196 sInstrumentation, sUiAutomation, mActivityRule); 197 } 198 199 @After tearDown()200 public void tearDown() throws Exception { 201 sUiAutomation.dropShellPermissionIdentity(); 202 } 203 204 @MediumTest 205 @Test 206 @ApiTest(apis = {"android.view.View#setSelected", 207 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeViewSelectedAccessibilityEvent()208 public void testTypeViewSelectedAccessibilityEvent() throws Throwable { 209 // create and populate the expected event 210 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 211 expected.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED); 212 expected.setClassName(ListView.class.getName()); 213 expected.setPackageName(mActivity.getPackageName()); 214 expected.setDisplayId(mActivity.getDisplayId()); 215 expected.getText().add(mActivity.getString(R.string.second_list_item)); 216 expected.setItemCount(2); 217 expected.setCurrentItemIndex(1); 218 expected.setEnabled(true); 219 expected.setScrollable(false); 220 expected.setFromIndex(0); 221 expected.setToIndex(1); 222 223 final ListView listView = (ListView) mActivity.findViewById(R.id.listview); 224 225 AccessibilityEvent awaitedEvent = 226 sUiAutomation.executeAndWaitForEvent( 227 new Runnable() { 228 @Override 229 public void run() { 230 // trigger the event 231 mActivity.runOnUiThread(new Runnable() { 232 @Override 233 public void run() { 234 listView.setSelection(1); 235 } 236 }); 237 }}, 238 new UiAutomation.AccessibilityEventFilter() { 239 // check the received event 240 @Override 241 public boolean accept(AccessibilityEvent event) { 242 return equalsAccessiblityEvent(event, expected); 243 } 244 }, 245 DEFAULT_TIMEOUT_MS); 246 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 247 } 248 249 @MediumTest 250 @Test 251 @ApiTest(apis = {"android.view.View#performClick", 252 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeViewClickedAccessibilityEvent()253 public void testTypeViewClickedAccessibilityEvent() throws Throwable { 254 // create and populate the expected event 255 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 256 expected.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED); 257 expected.setClassName(Button.class.getName()); 258 expected.setPackageName(mActivity.getPackageName()); 259 expected.setDisplayId(mActivity.getDisplayId()); 260 expected.getText().add(mActivity.getString(R.string.button_title)); 261 expected.setEnabled(true); 262 263 final Button button = (Button) mActivity.findViewById(R.id.button); 264 265 AccessibilityEvent awaitedEvent = 266 sUiAutomation.executeAndWaitForEvent( 267 new Runnable() { 268 @Override 269 public void run() { 270 // trigger the event 271 mActivity.runOnUiThread(new Runnable() { 272 @Override 273 public void run() { 274 button.performClick(); 275 } 276 }); 277 }}, 278 new UiAutomation.AccessibilityEventFilter() { 279 // check the received event 280 @Override 281 public boolean accept(AccessibilityEvent event) { 282 return equalsAccessiblityEvent(event, expected); 283 } 284 }, 285 DEFAULT_TIMEOUT_MS); 286 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 287 } 288 289 @MediumTest 290 @Test 291 @ApiTest(apis = {"android.view.View#performLongClick", 292 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeViewLongClickedAccessibilityEvent()293 public void testTypeViewLongClickedAccessibilityEvent() throws Throwable { 294 // create and populate the expected event 295 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 296 expected.setEventType(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 297 expected.setClassName(Button.class.getName()); 298 expected.setPackageName(mActivity.getPackageName()); 299 expected.setDisplayId(mActivity.getDisplayId()); 300 expected.getText().add(mActivity.getString(R.string.button_title)); 301 expected.setEnabled(true); 302 303 final Button button = (Button) mActivity.findViewById(R.id.button); 304 305 AccessibilityEvent awaitedEvent = 306 sUiAutomation.executeAndWaitForEvent( 307 new Runnable() { 308 @Override 309 public void run() { 310 // trigger the event 311 mActivity.runOnUiThread(new Runnable() { 312 @Override 313 public void run() { 314 button.performLongClick(); 315 } 316 }); 317 }}, 318 new UiAutomation.AccessibilityEventFilter() { 319 // check the received event 320 @Override 321 public boolean accept(AccessibilityEvent event) { 322 return equalsAccessiblityEvent(event, expected); 323 } 324 }, 325 DEFAULT_TIMEOUT_MS); 326 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 327 } 328 329 @MediumTest 330 @Test 331 @ApiTest(apis = {"android.view.View#requestFocus", 332 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeViewFocusedAccessibilityEvent()333 public void testTypeViewFocusedAccessibilityEvent() throws Throwable { 334 // create and populate the expected event 335 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 336 expected.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED); 337 expected.setClassName(Button.class.getName()); 338 expected.setPackageName(mActivity.getPackageName()); 339 expected.setDisplayId(mActivity.getDisplayId()); 340 expected.getText().add(mActivity.getString(R.string.button_title)); 341 expected.setItemCount(6); 342 expected.setCurrentItemIndex(4); 343 expected.setEnabled(true); 344 345 final Button button = (Button) mActivity.findViewById(R.id.buttonWithTooltip); 346 347 AccessibilityEvent awaitedEvent = 348 sUiAutomation.executeAndWaitForEvent( 349 () -> mActivity.runOnUiThread(button::requestFocus), 350 (event) -> equalsAccessiblityEvent(event, expected), 351 DEFAULT_TIMEOUT_MS); 352 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 353 } 354 355 @MediumTest 356 @Test 357 @ApiTest(apis = {"android.text.Editable#replace", 358 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeViewTextChangedAccessibilityEvent()359 public void testTypeViewTextChangedAccessibilityEvent() throws Throwable { 360 // focus the edit text 361 final EditText editText = (EditText) mActivity.findViewById(R.id.edittext); 362 363 AccessibilityEvent awaitedFocusEvent = 364 sUiAutomation.executeAndWaitForEvent( 365 new Runnable() { 366 @Override 367 public void run() { 368 // trigger the event 369 mActivity.runOnUiThread(new Runnable() { 370 @Override 371 public void run() { 372 editText.requestFocus(); 373 } 374 }); 375 }}, 376 new UiAutomation.AccessibilityEventFilter() { 377 // check the received event 378 @Override 379 public boolean accept(AccessibilityEvent event) { 380 return event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED; 381 } 382 }, 383 DEFAULT_TIMEOUT_MS); 384 assertNotNull("Did not receive expected focuss event.", awaitedFocusEvent); 385 386 final String beforeText = mActivity.getString(R.string.text_input_blah); 387 final String newText = mActivity.getString(R.string.text_input_blah_blah); 388 final String afterText = beforeText.substring(0, 3) + newText; 389 390 // create and populate the expected event 391 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 392 expected.setEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 393 expected.setClassName(EditText.class.getName()); 394 expected.setPackageName(mActivity.getPackageName()); 395 expected.setDisplayId(mActivity.getDisplayId()); 396 expected.getText().add(afterText); 397 expected.setBeforeText(beforeText); 398 expected.setFromIndex(3); 399 expected.setAddedCount(9); 400 expected.setRemovedCount(1); 401 expected.setEnabled(true); 402 403 AccessibilityEvent awaitedTextChangeEvent = 404 sUiAutomation.executeAndWaitForEvent( 405 new Runnable() { 406 @Override 407 public void run() { 408 // trigger the event 409 mActivity.runOnUiThread(new Runnable() { 410 @Override 411 public void run() { 412 editText.getEditableText().replace(3, 4, newText); 413 } 414 }); 415 }}, 416 new UiAutomation.AccessibilityEventFilter() { 417 // check the received event 418 @Override 419 public boolean accept(AccessibilityEvent event) { 420 return equalsAccessiblityEvent(event, expected); 421 } 422 }, 423 DEFAULT_TIMEOUT_MS); 424 assertNotNull("Did not receive expected event: " + expected, awaitedTextChangeEvent); 425 } 426 427 @MediumTest 428 @Test 429 @ApiTest(apis = {"android.view.ViewManager#addView", 430 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeWindowStateChangedAccessibilityEvent()431 public void testTypeWindowStateChangedAccessibilityEvent() throws Throwable { 432 // create and populate the expected event 433 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 434 expected.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 435 expected.setClassName(AlertDialog.class.getName()); 436 expected.setPackageName(mActivity.getPackageName()); 437 expected.setDisplayId(mActivity.getDisplayId()); 438 expected.getText().add(mActivity.getString(R.string.alert_title)); 439 expected.getText().add(mActivity.getString(R.string.alert_message)); 440 expected.setEnabled(true); 441 442 AccessibilityEvent awaitedEvent = 443 sUiAutomation.executeAndWaitForEvent( 444 new Runnable() { 445 @Override 446 public void run() { 447 // trigger the event 448 mActivity.runOnUiThread(new Runnable() { 449 @Override 450 public void run() { 451 (new AlertDialog.Builder(mActivity).setTitle(R.string.alert_title) 452 .setMessage(R.string.alert_message)).create().show(); 453 } 454 }); 455 }}, 456 new UiAutomation.AccessibilityEventFilter() { 457 // check the received event 458 @Override 459 public boolean accept(AccessibilityEvent event) { 460 return equalsAccessiblityEvent(event, expected); 461 } 462 }, 463 DEFAULT_TIMEOUT_MS); 464 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 465 } 466 467 @MediumTest 468 @Test 469 @ApiTest(apis = {"android.app.Activity#finish", 470 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeWindowsChangedAccessibilityEvent()471 public void testTypeWindowsChangedAccessibilityEvent() throws Throwable { 472 // create and populate the expected event 473 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 474 expected.setEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED); 475 expected.setDisplayId(mActivity.getDisplayId()); 476 477 // check the received event 478 AccessibilityEvent awaitedEvent = 479 sUiAutomation.executeAndWaitForEvent( 480 () -> mActivity.runOnUiThread(() -> mActivity.finish()), 481 event -> event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED 482 && equalsAccessiblityEvent(event, expected), 483 DEFAULT_TIMEOUT_MS); 484 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 485 } 486 487 @MediumTest 488 @AppModeFull 489 @SuppressWarnings("deprecation") 490 @Test 491 @ApiTest(apis = {"android.app.NotificationManager#notify", 492 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTypeNotificationStateChangedAccessibilityEvent()493 public void testTypeNotificationStateChangedAccessibilityEvent() throws Throwable { 494 // No notification UI on televisions. 495 if ((mActivity.getResources().getConfiguration().uiMode 496 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) { 497 Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" + 498 " - No notification UI on televisions."); 499 return; 500 } 501 PackageManager pm = sInstrumentation.getTargetContext().getPackageManager(); 502 if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) { 503 Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" + 504 " - Watches have different notification system."); 505 return; 506 } 507 if (pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 508 Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" + 509 " - Automotive handle notifications differently."); 510 return; 511 } 512 513 String message = mActivity.getString(R.string.notification_message); 514 515 final NotificationManager notificationManager = 516 (NotificationManager) mActivity.getSystemService(Service.NOTIFICATION_SERVICE); 517 final NotificationChannel channel = 518 new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); 519 try { 520 // create the notification to send 521 channel.enableVibration(true); 522 channel.enableLights(true); 523 channel.setBypassDnd(true); 524 notificationManager.createNotificationChannel(channel); 525 final int notificationId = 1; 526 final Notification notification = 527 new Notification.Builder(mActivity, channel.getId()) 528 .setSmallIcon(android.R.drawable.stat_notify_call_mute) 529 .setContentIntent(PendingIntent.getActivity(mActivity, 0, 530 new Intent(), 531 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)) 532 .setTicker(message) 533 .setContentTitle("") 534 .setContentText("") 535 .setPriority(Notification.PRIORITY_MAX) 536 // Mark the notification as "interruptive" by specifying a vibration 537 // pattern. This ensures it's announced properly on watch-type devices. 538 .setVibrate(new long[]{}) 539 .build(); 540 541 // create and populate the expected event 542 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 543 expected.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); 544 expected.setClassName(Notification.class.getName()); 545 expected.setPackageName(mActivity.getPackageName()); 546 expected.getText().add(message); 547 expected.setParcelableData(notification); 548 549 AccessibilityEvent awaitedEvent = 550 sUiAutomation.executeAndWaitForEvent( 551 new Runnable() { 552 @Override 553 public void run() { 554 // trigger the event 555 mActivity.runOnUiThread(new Runnable() { 556 @Override 557 public void run() { 558 // trigger the event 559 notificationManager 560 .notify(notificationId, notification); 561 mActivity.finish(); 562 } 563 }); 564 } 565 }, 566 new UiAutomation.AccessibilityEventFilter() { 567 // check the received event 568 @Override 569 public boolean accept(AccessibilityEvent event) { 570 return equalsAccessiblityEvent(event, expected); 571 } 572 }, 573 DEFAULT_TIMEOUT_MS); 574 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 575 } finally { 576 notificationManager.deleteNotificationChannel(channel.getId()); 577 } 578 } 579 580 @MediumTest 581 @Test 582 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#interrupt"}) testInterrupt_notifiesService()583 public void testInterrupt_notifiesService() { 584 sInstrumentation 585 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 586 InstrumentedAccessibilityService service = 587 enableService(InstrumentedAccessibilityService.class); 588 589 try { 590 assertFalse(service.wasOnInterruptCalled()); 591 592 mActivity.runOnUiThread(() -> { 593 AccessibilityManager accessibilityManager = (AccessibilityManager) mActivity 594 .getSystemService(Service.ACCESSIBILITY_SERVICE); 595 accessibilityManager.interrupt(); 596 }); 597 598 Object waitObject = service.getInterruptWaitObject(); 599 synchronized (waitObject) { 600 if (!service.wasOnInterruptCalled()) { 601 try { 602 waitObject.wait(DEFAULT_TIMEOUT_MS); 603 } catch (InterruptedException e) { 604 // Do nothing 605 } 606 } 607 } 608 assertTrue(service.wasOnInterruptCalled()); 609 } finally { 610 service.disableSelfAndRemove(); 611 } 612 } 613 614 @MediumTest 615 @Test 616 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getPackageName"}) testPackageNameCannotBeFaked()617 public void testPackageNameCannotBeFaked() { 618 mActivity.runOnUiThread(() -> { 619 // Set the activity to report fake package for events and nodes 620 mActivity.setReportedPackageName("foo.bar.baz"); 621 622 // Make sure node package cannot be faked 623 AccessibilityNodeInfo root = sUiAutomation 624 .getRootInActiveWindow(); 625 assertPackageName(root, mActivity.getPackageName()); 626 }); 627 628 // Make sure event package cannot be faked 629 try { 630 sUiAutomation.executeAndWaitForEvent(() -> 631 sInstrumentation.runOnMainSync(() -> 632 mActivity.findViewById(R.id.button).requestFocus()) 633 , (AccessibilityEvent event) -> 634 event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED 635 && event.getPackageName().equals(mActivity.getPackageName()) 636 , DEFAULT_TIMEOUT_MS); 637 } catch (TimeoutException e) { 638 fail("Events from fake package should be fixed to use the correct package"); 639 } 640 } 641 642 @AppModeFull 643 @MediumTest 644 @Test 645 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getPackageName"}) testPackageNameCannotBeFakedAppWidget()646 public void testPackageNameCannotBeFakedAppWidget() throws Exception { 647 if (!hasAppWidgets()) { 648 return; 649 } 650 651 sInstrumentation.runOnMainSync(() -> { 652 // Set the activity to report fake package for events and nodes 653 mActivity.setReportedPackageName(APP_WIDGET_PROVIDER_PACKAGE); 654 655 // Make sure we cannot report nodes as if from the widget package 656 AccessibilityNodeInfo root = sUiAutomation 657 .getRootInActiveWindow(); 658 assertPackageName(root, mActivity.getPackageName()); 659 }); 660 661 // Make sure we cannot send events as if from the widget package 662 try { 663 sUiAutomation.executeAndWaitForEvent(() -> 664 sInstrumentation.runOnMainSync(() -> 665 mActivity.findViewById(R.id.button).requestFocus()) 666 , (AccessibilityEvent event) -> 667 event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED 668 && event.getPackageName().equals(mActivity.getPackageName()) 669 , DEFAULT_TIMEOUT_MS); 670 } catch (TimeoutException e) { 671 fail("Should not be able to send events from a widget package if no widget hosted"); 672 } 673 674 // Create a host and start listening. 675 final AppWidgetHost host = new AppWidgetHost(sInstrumentation.getTargetContext(), 0); 676 host.deleteHost(); 677 host.startListening(); 678 679 // Well, app do not have this permission unless explicitly granted 680 // by the user. Now we will pretend for the user and grant it. 681 grantBindAppWidgetPermission(); 682 683 // Allocate an app widget id to bind. 684 final int appWidgetId = host.allocateAppWidgetId(); 685 try { 686 // Grab a provider we defined to be bound. 687 final AppWidgetProviderInfo provider = getAppWidgetProviderInfo(); 688 689 // Bind the widget. 690 final boolean widgetBound = getAppWidgetManager().bindAppWidgetIdIfAllowed( 691 appWidgetId, provider.getProfile(), provider.provider, null); 692 assertTrue(widgetBound); 693 694 // Make sure the app can use the package of a widget it hosts 695 sInstrumentation.runOnMainSync(() -> { 696 // Make sure we can report nodes as if from the widget package 697 AccessibilityNodeInfo root = sUiAutomation 698 .getRootInActiveWindow(); 699 assertPackageName(root, APP_WIDGET_PROVIDER_PACKAGE); 700 }); 701 702 // Make sure we can send events as if from the widget package 703 try { 704 sUiAutomation.executeAndWaitForEvent(() -> 705 sInstrumentation.runOnMainSync(() -> 706 mActivity.findViewById(R.id.button).performClick()) 707 , (AccessibilityEvent event) -> 708 event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED 709 && event.getPackageName().equals(APP_WIDGET_PROVIDER_PACKAGE) 710 , DEFAULT_TIMEOUT_MS); 711 } catch (TimeoutException e) { 712 fail("Should be able to send events from a widget package if widget hosted"); 713 } 714 } finally { 715 // Clean up. 716 host.deleteAppWidgetId(appWidgetId); 717 host.deleteHost(); 718 revokeBindAppWidgetPermission(); 719 } 720 } 721 722 @MediumTest 723 @Test 724 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#isHeading"}) testViewHeadingReportedToAccessibility()725 public void testViewHeadingReportedToAccessibility() throws Exception { 726 final EditText editText = (EditText) getOnMain(sInstrumentation, 727 () -> mActivity.findViewById(R.id.edittext)); 728 // Make sure the edittext was populated properly from xml 729 final boolean editTextIsHeading = getOnMain(sInstrumentation, 730 editText::isAccessibilityHeading); 731 assertTrue("isAccessibilityHeading not populated properly from xml", editTextIsHeading); 732 733 final AccessibilityNodeInfo editTextNode = sUiAutomation.getRootInActiveWindow() 734 .findAccessibilityNodeInfosByViewId( 735 "android.accessibilityservice.cts:id/edittext") 736 .get(0); 737 assertTrue("isAccessibilityHeading not reported to accessibility", 738 editTextNode.isHeading()); 739 740 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() -> 741 editText.setAccessibilityHeading(false)), 742 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 743 DEFAULT_TIMEOUT_MS); 744 editTextNode.refresh(); 745 assertFalse("isAccessibilityHeading not reported to accessibility after update", 746 editTextNode.isHeading()); 747 } 748 749 @MediumTest 750 @Test 751 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getTooltipText"}) testTooltipTextReportedToAccessibility()752 public void testTooltipTextReportedToAccessibility() { 753 final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow() 754 .findAccessibilityNodeInfosByViewId( 755 "android.accessibilityservice.cts:id/buttonWithTooltip") 756 .get(0); 757 assertEquals("Tooltip text not reported to accessibility", 758 sInstrumentation.getContext().getString(R.string.button_tooltip), 759 buttonNode.getTooltipText()); 760 } 761 762 @MediumTest 763 @Test testAccessibilityActionRetained()764 public void testAccessibilityActionRetained() throws Exception { 765 final AccessibilityNodeInfo sentInfo = new AccessibilityNodeInfo(new View(getContext())); 766 sentInfo.addAction(ACTION_SCROLL_IN_DIRECTION); 767 final Parcel parcel = Parcel.obtain(); 768 sentInfo.writeToParcelNoRecycle(parcel, 0); 769 parcel.setDataPosition(0); 770 AccessibilityNodeInfo receivedInfo = AccessibilityNodeInfo.CREATOR.createFromParcel(parcel); 771 772 assertThat(receivedInfo.getActionList()).contains(ACTION_SCROLL_IN_DIRECTION); 773 774 parcel.recycle(); 775 } 776 777 @MediumTest 778 @Test 779 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getActionList"}) testTooltipTextActionsReportedToAccessibility()780 public void testTooltipTextActionsReportedToAccessibility() throws Exception { 781 final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow() 782 .findAccessibilityNodeInfosByViewId( 783 "android.accessibilityservice.cts:id/buttonWithTooltip") 784 .get(0); 785 assertFalse(hasTooltipShowing(R.id.buttonWithTooltip)); 786 assertThat(buttonNode.getActionList()).contains(ACTION_SHOW_TOOLTIP); 787 assertThat(buttonNode.getActionList()).doesNotContain(ACTION_HIDE_TOOLTIP); 788 sUiAutomation.executeAndWaitForEvent( 789 () -> buttonNode.performAction(ACTION_SHOW_TOOLTIP.getId()), 790 filterForEventTypeWithAction( 791 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, 792 ACTION_SHOW_TOOLTIP.getId()), 793 DEFAULT_TIMEOUT_MS); 794 795 // The button should now be showing the tooltip, so it should have the option to hide it. 796 buttonNode.refresh(); 797 assertThat(buttonNode.getActionList()).contains(ACTION_HIDE_TOOLTIP); 798 assertThat(buttonNode.getActionList()).doesNotContain(ACTION_SHOW_TOOLTIP); 799 assertTrue(hasTooltipShowing(R.id.buttonWithTooltip)); 800 } 801 802 @MediumTest 803 @Test 804 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getTraversalBefore"}) testTraversalBeforeReportedToAccessibility()805 public void testTraversalBeforeReportedToAccessibility() throws Exception { 806 final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow() 807 .findAccessibilityNodeInfosByViewId( 808 "android.accessibilityservice.cts:id/buttonWithTooltip") 809 .get(0); 810 final AccessibilityNodeInfo beforeNode = buttonNode.getTraversalBefore(); 811 assertThat(beforeNode).isNotNull(); 812 assertThat(beforeNode.getViewIdResourceName()).isEqualTo( 813 "android.accessibilityservice.cts:id/edittext"); 814 815 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync( 816 () -> mActivity.findViewById(R.id.buttonWithTooltip) 817 .setAccessibilityTraversalBefore(View.NO_ID)), 818 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 819 DEFAULT_TIMEOUT_MS); 820 821 buttonNode.refresh(); 822 assertThat(buttonNode.getTraversalBefore()).isNull(); 823 } 824 825 @MediumTest 826 @Test 827 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getTraversalAfter"}) testTraversalAfterReportedToAccessibility()828 public void testTraversalAfterReportedToAccessibility() throws Exception { 829 final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow() 830 .findAccessibilityNodeInfosByViewId( 831 "android.accessibilityservice.cts:id/edittext") 832 .get(0); 833 final AccessibilityNodeInfo afterNode = editNode.getTraversalAfter(); 834 assertThat(afterNode).isNotNull(); 835 assertThat(afterNode.getViewIdResourceName()).isEqualTo( 836 "android.accessibilityservice.cts:id/buttonWithTooltip"); 837 838 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync( 839 () -> mActivity.findViewById(R.id.edittext) 840 .setAccessibilityTraversalAfter(View.NO_ID)), 841 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 842 DEFAULT_TIMEOUT_MS); 843 844 editNode.refresh(); 845 assertThat(editNode.getTraversalAfter()).isNull(); 846 } 847 848 @MediumTest 849 @Test 850 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getLabelFor"}) testLabelForReportedToAccessibility()851 public void testLabelForReportedToAccessibility() throws Exception { 852 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() -> mActivity 853 .findViewById(R.id.edittext).setLabelFor(R.id.buttonWithTooltip)), 854 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 855 DEFAULT_TIMEOUT_MS); 856 // TODO: b/78022650: This code should move above the executeAndWait event. It's here because 857 // the a11y cache doesn't get notified when labelFor changes, so the node with the 858 // labledBy isn't updated. 859 final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow() 860 .findAccessibilityNodeInfosByViewId( 861 "android.accessibilityservice.cts:id/edittext") 862 .get(0); 863 editNode.refresh(); 864 final AccessibilityNodeInfo labelForNode = editNode.getLabelFor(); 865 assertThat(labelForNode).isNotNull(); 866 // Labeled node should indicate that it is labeled by the other one 867 assertThat(labelForNode.getLabeledBy()).isEqualTo(editNode); 868 } 869 870 @MediumTest 871 @Test 872 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#performAction"}) testA11yActionTriggerMotionEventActionOutside()873 public void testA11yActionTriggerMotionEventActionOutside() throws Exception { 874 final View.OnTouchListener listener = mock(View.OnTouchListener.class); 875 final AccessibilityNodeInfo button = sUiAutomation.getRootInActiveWindow() 876 .findAccessibilityNodeInfosByViewId( 877 "android.accessibilityservice.cts:id/button") 878 .get(0); 879 final String title = sInstrumentation.getContext().getString(R.string.alert_title); 880 881 // Add a dialog that is watching outside touch 882 sUiAutomation.executeAndWaitForEvent( 883 () -> sInstrumentation.runOnMainSync(() -> { 884 final AlertDialog dialog = new AlertDialog.Builder(mActivity) 885 .setTitle(R.string.alert_title) 886 .setMessage(R.string.alert_message) 887 .create(); 888 final Window window = dialog.getWindow(); 889 window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 890 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); 891 window.getDecorView().setOnTouchListener(listener); 892 window.setTitle(title); 893 dialog.show(); 894 }), 895 (event) -> { 896 // Ensure the dialog is shown over the activity 897 final AccessibilityWindowInfo dialog = findWindowByTitle( 898 sUiAutomation, title); 899 final AccessibilityWindowInfo activity = findWindowByTitle( 900 sUiAutomation, getActivityTitle(sInstrumentation, mActivity)); 901 return (dialog != null && activity != null) 902 && (dialog.getLayer() > activity.getLayer()); 903 }, DEFAULT_TIMEOUT_MS); 904 905 // Perform an action and wait for an event 906 sUiAutomation.executeAndWaitForEvent( 907 () -> button.performAction(AccessibilityNodeInfo.ACTION_CLICK), 908 filterForEventTypeWithAction( 909 AccessibilityEvent.TYPE_VIEW_CLICKED, AccessibilityNodeInfo.ACTION_CLICK), 910 DEFAULT_TIMEOUT_MS); 911 912 // Make sure the MotionEvent.ACTION_OUTSIDE is received. 913 verify(listener, timeout(DEFAULT_TIMEOUT_MS).atLeastOnce()).onTouch(any(View.class), 914 argThat(event -> event.getActionMasked() == MotionEvent.ACTION_OUTSIDE)); 915 } 916 917 @MediumTest 918 @Test 919 @ApiTest(apis = {"android.view.accessibility.AccessibilityNodeInfo#getTouchDelegateInfo"}) testTouchDelegateInfoReportedToAccessibility()920 public void testTouchDelegateInfoReportedToAccessibility() { 921 final Button button = getOnMain(sInstrumentation, () -> mActivity.findViewById( 922 R.id.button)); 923 final View parent = (View) button.getParent(); 924 final Rect rect = new Rect(); 925 button.getHitRect(rect); 926 parent.setTouchDelegate(new TouchDelegate(rect, button)); 927 928 final AccessibilityNodeInfo nodeInfo = sUiAutomation.getRootInActiveWindow() 929 .findAccessibilityNodeInfosByViewId( 930 "android.accessibilityservice.cts:id/buttonLayout") 931 .get(0); 932 AccessibilityNodeInfo.TouchDelegateInfo targetMapInfo = 933 nodeInfo.getTouchDelegateInfo(); 934 assertNotNull("Did not receive TouchDelegate target map", targetMapInfo); 935 assertEquals("Incorrect target map size", 1, targetMapInfo.getRegionCount()); 936 assertEquals("Incorrect target map region", new Region(rect), 937 targetMapInfo.getRegionAt(0)); 938 final AccessibilityNodeInfo node = targetMapInfo.getTargetForRegion( 939 targetMapInfo.getRegionAt(0)); 940 assertEquals("Incorrect target map view", 941 "android.accessibilityservice.cts:id/button", 942 node.getViewIdResourceName()); 943 node.recycle(); 944 } 945 946 @MediumTest 947 @Test 948 @ApiTest(apis = {"android.view.View#onHoverEvent", 949 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testTouchDelegateWithEbtBetweenView_ReHoverDelegate_FocusTargetAgain()950 public void testTouchDelegateWithEbtBetweenView_ReHoverDelegate_FocusTargetAgain() 951 throws Throwable { 952 mActivity.waitForEnterAnimationComplete(); 953 954 final Resources resources = sInstrumentation.getTargetContext().getResources(); 955 final String buttonResourceName = resources.getResourceName(R.id.button); 956 final Button button = mActivity.findViewById(R.id.button); 957 final int[] buttonLocation = new int[2]; 958 button.getLocationOnScreen(buttonLocation); 959 final int buttonX = button.getWidth() / 2; 960 final int buttonY = button.getHeight() / 2; 961 final int hoverY = buttonLocation[1] + buttonY; 962 final Button buttonWithTooltip = mActivity.findViewById(R.id.buttonWithTooltip); 963 final int[] buttonWithTooltipLocation = new int[2]; 964 buttonWithTooltip.getLocationOnScreen(buttonWithTooltipLocation); 965 final int touchableSize = resources.getDimensionPixelSize( 966 R.dimen.button_touchable_width_increment_amount); 967 final int hoverRight = buttonWithTooltipLocation[0] + touchableSize / 2; 968 final int hoverLeft = buttonLocation[0] + button.getWidth() + touchableSize / 2; 969 final int hoverMiddle = (hoverLeft + hoverRight) / 2; 970 final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(button, false); 971 enableTouchExploration(true); 972 973 try { 974 // common downTime for touch explorer injected events 975 final long downTime = SystemClock.uptimeMillis(); 976 // hover through delegate, parent, 2nd view, parent and delegate again 977 sUiAutomation.executeAndWaitForEvent( 978 () -> injectHoverEvent(downTime, false, hoverLeft, hoverY), 979 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 980 buttonResourceName), DEFAULT_TIMEOUT_MS); 981 assertTrue(button.isHovered()); 982 sUiAutomation.executeAndWaitForEvent( 983 () -> { 984 injectHoverEvent(downTime, true, hoverMiddle, hoverY); 985 injectHoverEvent(downTime, true, hoverRight, hoverY); 986 injectHoverEvent(downTime, true, hoverMiddle, hoverY); 987 injectHoverEvent(downTime, true, hoverLeft, hoverY); 988 }, 989 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 990 buttonResourceName), DEFAULT_TIMEOUT_MS); 991 // delegate target has a11y focus again 992 assertTrue(button.isHovered()); 993 994 CtsMouseUtil.clearHoverListener(button); 995 View.OnHoverListener verifier = inOrder(listener).verify(listener); 996 verifier.onHover(eq(button), 997 matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY)); 998 verifier.onHover(eq(button), 999 matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY)); 1000 verifier.onHover(eq(button), 1001 matchHover(MotionEvent.ACTION_HOVER_MOVE, hoverMiddle, buttonY)); 1002 verifier.onHover(eq(button), 1003 matchHover(MotionEvent.ACTION_HOVER_EXIT, buttonX, buttonY)); 1004 verifier.onHover(eq(button), 1005 matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY)); 1006 verifier.onHover(eq(button), 1007 matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY)); 1008 } catch (TimeoutException e) { 1009 fail("Accessibility events should be received as expected " + e.getMessage()); 1010 } finally { 1011 enableTouchExploration(false); 1012 } 1013 } 1014 1015 @MediumTest 1016 @Test 1017 @ApiTest(apis = {"android.view.View#onHoverEvent", 1018 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) 1019 @FlakyTest testTouchDelegateCoverParentWithEbt_HoverChildAndBack_FocusTargetAgain()1020 public void testTouchDelegateCoverParentWithEbt_HoverChildAndBack_FocusTargetAgain() 1021 throws Throwable { 1022 mActivity.waitForEnterAnimationComplete(); 1023 1024 final Resources resources = sInstrumentation.getTargetContext().getResources(); 1025 final int touchableSize = resources.getDimensionPixelSize( 1026 R.dimen.button_touchable_width_increment_amount); 1027 final String targetResourceName = resources.getResourceName(R.id.buttonDelegated); 1028 final View textView = mActivity.findViewById(R.id.delegateText); 1029 final Button target = mActivity.findViewById(R.id.buttonDelegated); 1030 int[] location = new int[2]; 1031 textView.getLocationOnScreen(location); 1032 final int textX = location[0] + touchableSize/2; 1033 final int textY = location[1] + textView.getHeight() / 2; 1034 final int delegateX = location[0] - touchableSize/2; 1035 final int targetX = target.getWidth() / 2; 1036 final int targetY = target.getHeight() / 2; 1037 final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(target, false); 1038 enableTouchExploration(true); 1039 1040 try { 1041 final long downTime = SystemClock.uptimeMillis(); 1042 // Like switch bar, it has a text view, a button and a delegate covers parent layout. 1043 // hover the delegate, text and delegate again. 1044 sUiAutomation.executeAndWaitForEvent( 1045 () -> injectHoverEvent(downTime, false, delegateX, textY), 1046 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 1047 targetResourceName), DEFAULT_TIMEOUT_MS); 1048 assertTrue(target.isHovered()); 1049 sUiAutomation.executeAndWaitForEvent( 1050 () -> injectHoverEvent(downTime, true, textX, textY), 1051 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, 1052 targetResourceName), DEFAULT_TIMEOUT_MS); 1053 sUiAutomation.executeAndWaitForEvent( 1054 () -> injectHoverEvent(downTime, true, delegateX, textY), 1055 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 1056 targetResourceName), DEFAULT_TIMEOUT_MS); 1057 assertTrue(target.isHovered()); 1058 1059 CtsMouseUtil.clearHoverListener(target); 1060 View.OnHoverListener verifier = inOrder(listener).verify(listener); 1061 verifier.onHover(eq(target), 1062 matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY)); 1063 verifier.onHover(eq(target), 1064 matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY)); 1065 verifier.onHover(eq(target), 1066 matchHover(MotionEvent.ACTION_HOVER_MOVE, textX, textY)); 1067 verifier.onHover(eq(target), 1068 matchHover(MotionEvent.ACTION_HOVER_EXIT, targetX, targetY)); 1069 verifier.onHover(eq(target), 1070 matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY)); 1071 verifier.onHover(eq(target), 1072 matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY)); 1073 } catch (TimeoutException e) { 1074 fail("Accessibility events should be received as expected " + e.getMessage()); 1075 } finally { 1076 enableTouchExploration(false); 1077 } 1078 } 1079 1080 @Test 1081 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive"}) testAccessibilityDataSensitive_nodeMatchesViewProperty()1082 public void testAccessibilityDataSensitive_nodeMatchesViewProperty() { 1083 final InstrumentedAccessibilityService service = getServiceForA11yToolTests(true); 1084 try { 1085 final AccessibilityNodeInfo root = service.getRootInActiveWindow(); 1086 1087 final AccessibilityNodeInfo nonAdsNode = root.findAccessibilityNodeInfosByViewId( 1088 mActivity.getResources().getResourceName(R.id.containerView)).get(0); 1089 final AccessibilityNodeInfo adsNode = root.findAccessibilityNodeInfosByViewId( 1090 mActivity.getResources().getResourceName(R.id.adsView)).get(0); 1091 1092 assertThat(nonAdsNode.isAccessibilityDataSensitive()).isFalse(); 1093 assertThat(adsNode.isAccessibilityDataSensitive()).isTrue(); 1094 } finally { 1095 service.disableSelfAndRemove(); 1096 } 1097 } 1098 1099 @Test 1100 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive"}) testAccessibilityDataSensitive_visibleToAccessibilityTool()1101 public void testAccessibilityDataSensitive_visibleToAccessibilityTool() throws Throwable { 1102 // Relevant view structure: 1103 // containerView (LinearLayout, accessibilityDataSensitive=auto) 1104 // adsView (LinearLayout, accessibilityDataSensitive=true) 1105 // innerContainerView (LinearLayout, accessibilityDataSensitive=auto) 1106 // innerView (Button, accessibilityDataSensitive=auto) 1107 // Only adsView sets accessibilityDataSensitive=true in the layout XML. 1108 // Inner views should inherit true from their (grand)parent view. 1109 final StubEventCapturingAccessibilityService service = getServiceForA11yToolTests(true); 1110 try { 1111 final AccessibilityNodeInfo root = service.getRootInActiveWindow(); 1112 1113 final String containerViewName = mActivity.getResources().getResourceName( 1114 R.id.containerView); 1115 1116 final String adsViewName = mActivity.getResources().getResourceName(R.id.adsView); 1117 final String adsViewText = mActivity.findViewById( 1118 R.id.adsView).getContentDescription().toString(); 1119 1120 final String innerContainerViewName = mActivity.getResources().getResourceName( 1121 R.id.innerContainerView); 1122 final String innerContainerViewText = 1123 mActivity.findViewById( 1124 R.id.innerContainerView).getContentDescription().toString(); 1125 1126 final String innerViewName = mActivity.getResources().getResourceName(R.id.innerView); 1127 final String innerViewText = mActivity.findViewById( 1128 R.id.innerView).getContentDescription().toString(); 1129 1130 // Search for the Views' nodes using various techniques: 1131 1132 // ByViewId 1133 assertThat(root.findAccessibilityNodeInfosByViewId(adsViewName)).hasSize(1); 1134 assertThat(root.findAccessibilityNodeInfosByViewId(innerContainerViewName)).hasSize(1); 1135 assertThat(root.findAccessibilityNodeInfosByViewId(innerViewName)).hasSize(1); 1136 // ByText 1137 assertThat(root.findAccessibilityNodeInfosByText(adsViewText)).hasSize(1); 1138 assertThat(root.findAccessibilityNodeInfosByText(innerContainerViewText)).hasSize(1); 1139 assertThat(root.findAccessibilityNodeInfosByText(innerViewText)).hasSize(1); 1140 // Event propagation and findFocus 1141 service.setEventFilter( 1142 filterForEventTypeWithResource(TYPE_VIEW_ACCESSIBILITY_FOCUSED, adsViewName)); 1143 assertThat(root.findAccessibilityNodeInfosByViewId(adsViewName).get(0) 1144 .performAction(ACTION_ACCESSIBILITY_FOCUS)).isTrue(); 1145 service.waitOnEvent(DEFAULT_TIMEOUT_MS, 1146 "Expected TYPE_VIEW_ACCESSIBILITY_FOCUSED event"); 1147 assertThat(service.findFocus( 1148 AccessibilityNodeInfo.FOCUS_ACCESSIBILITY).getContentDescription()).isEqualTo( 1149 adsViewText); 1150 // Parent view's getChild() 1151 final AccessibilityNodeInfo parent = root.findAccessibilityNodeInfosByViewId( 1152 containerViewName).get(0); 1153 assertThat(parent.getChildCount()).isEqualTo(1); 1154 assertThat(parent.getChild(0)).isNotNull(); 1155 } finally { 1156 service.disableSelfAndRemove(); 1157 } 1158 } 1159 1160 @Test 1161 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive"}) testAccessibilityDataSensitive_canObserveHoverEvent()1162 public void testAccessibilityDataSensitive_canObserveHoverEvent() { 1163 final StubEventCapturingAccessibilityService service = getServiceForA11yToolTests(true); 1164 try { 1165 final long time = SystemClock.uptimeMillis(); 1166 final View view = mActivity.findViewById(R.id.innerView); 1167 final int[] viewLocation = new int[2]; 1168 view.getLocationOnScreen(viewLocation); 1169 final int x = viewLocation[0] + view.getWidth() / 2; 1170 final int y = viewLocation[1] + view.getHeight() / 2; 1171 1172 service.setEventFilter( 1173 filterForEventTypeWithResource( 1174 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 1175 sInstrumentation.getTargetContext().getResources() 1176 .getResourceName(R.id.innerView))); 1177 injectHoverEvent(time, true, x, y); 1178 service.waitOnEvent(DEFAULT_TIMEOUT_MS, "Expected TYPE_VIEW_HOVER_ENTER event"); 1179 } finally { 1180 service.disableSelfAndRemove(); 1181 } 1182 } 1183 1184 @Test 1185 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive"}) testAccessibilityDataSensitive_checkAdsProperty_topDown()1186 public void testAccessibilityDataSensitive_checkAdsProperty_topDown() { 1187 // Accessing the View#isAccessibilityDataSensitive() property causes both the View & its 1188 // parent hierarchy to cache their values. 1189 // Assert that the property is as expected when starting from the top-most view. 1190 assertThat(mActivity.findViewById(R.id.containerView).isAccessibilityDataSensitive()) 1191 .isFalse(); 1192 assertThat(mActivity.findViewById(R.id.adsView).isAccessibilityDataSensitive()).isTrue(); 1193 assertThat(mActivity.findViewById(R.id.innerContainerView).isAccessibilityDataSensitive()) 1194 .isTrue(); 1195 assertThat(mActivity.findViewById(R.id.innerView).isAccessibilityDataSensitive()).isTrue(); 1196 } 1197 1198 @Test 1199 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive"}) testAccessibilityDataSensitive_checkAdsProperty_bottomUp()1200 public void testAccessibilityDataSensitive_checkAdsProperty_bottomUp() { 1201 // Accessing the View#isAccessibilityDataSensitive() property causes both the View & its 1202 // parent hierarchy to cache their values. 1203 // Assert that the property is as expected when starting from the bottom-most view. 1204 assertThat(mActivity.findViewById(R.id.innerView).isAccessibilityDataSensitive()).isTrue(); 1205 assertThat(mActivity.findViewById(R.id.innerContainerView).isAccessibilityDataSensitive()) 1206 .isTrue(); 1207 assertThat(mActivity.findViewById(R.id.adsView).isAccessibilityDataSensitive()).isTrue(); 1208 assertThat(mActivity.findViewById(R.id.containerView).isAccessibilityDataSensitive()) 1209 .isFalse(); 1210 } 1211 1212 @Test 1213 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive", 1214 "android.view.accessibility.AccessibilityNodeInfo#findAccessibilityNodeInfosByViewId", 1215 "android.view.accessibility.AccessibilityNodeInfo#findAccessibilityNodeInfosByText", 1216 "android.view.accessibility.AccessibilityNodeInfo#getChild"}) testAccessibilityDataSensitive_hiddenFromSearches()1217 public void testAccessibilityDataSensitive_hiddenFromSearches() { 1218 final InstrumentedAccessibilityService service = getServiceForA11yToolTests(false); 1219 try { 1220 final AccessibilityNodeInfo root = service.getRootInActiveWindow(); 1221 final String adsViewName = mActivity.getResources().getResourceName(R.id.adsView); 1222 final String adsViewText = mActivity.getString(R.string.ads_desc); 1223 1224 assertThat(root.findAccessibilityNodeInfosByViewId(adsViewName)).isEmpty(); 1225 assertThat(root.findAccessibilityNodeInfosByText(adsViewText)).isEmpty(); 1226 Deque<AccessibilityNodeInfo> deque = new ArrayDeque<>(); 1227 deque.add(root); 1228 while (!deque.isEmpty()) { 1229 AccessibilityNodeInfo node = deque.removeFirst(); 1230 assertThat(node.getContentDescription()).isNotEqualTo(adsViewText); 1231 for (int i = node.getChildCount() - 1; i >= 0; i--) { 1232 deque.addLast(node.getChild(i)); 1233 } 1234 } 1235 } finally { 1236 service.disableSelfAndRemove(); 1237 } 1238 } 1239 1240 @Test 1241 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive", 1242 "android.accessibilityservice.AccessibilityService#findFocus"}) testAccessibilityDataSensitive_hiddenFromFindFocus()1243 public void testAccessibilityDataSensitive_hiddenFromFindFocus() { 1244 StubEventCapturingAccessibilityService toolService = null; 1245 InstrumentedAccessibilityService nonToolService = null; 1246 try { 1247 toolService = getServiceForA11yToolTests(true); 1248 nonToolService = getServiceForA11yToolTests(false); 1249 1250 // Set up initial focus on the ADS view. 1251 toolService.setEventFilter(filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUSED)); 1252 assertThat(mActivity.findViewById(R.id.adsView).performAccessibilityAction( 1253 ACTION_ACCESSIBILITY_FOCUS, null)).isTrue(); 1254 toolService.waitOnEvent(DEFAULT_TIMEOUT_MS, 1255 "Expected TYPE_VIEW_ACCESSIBILITY_FOCUSED event"); 1256 1257 assertThat(toolService.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)) 1258 .isNotNull(); 1259 assertThat(nonToolService.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)) 1260 .isNull(); 1261 } finally { 1262 if (toolService != null) { 1263 toolService.disableSelfAndRemove(); 1264 } 1265 if (nonToolService != null) { 1266 nonToolService.disableSelfAndRemove(); 1267 } 1268 } 1269 } 1270 1271 @Test 1272 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive", 1273 "android.view.accessibility.AccessibilityNodeInfo#findAccessibilityNodeInfosByViewId", 1274 "android.view.accessibility.AccessibilityNodeInfo#getChild"}) testAccessibilityDataSensitive_excludedFromParent()1275 public void testAccessibilityDataSensitive_excludedFromParent() { 1276 final InstrumentedAccessibilityService service = getServiceForA11yToolTests(false); 1277 try { 1278 final AccessibilityNodeInfo parentContainer = 1279 service.getRootInActiveWindow().findAccessibilityNodeInfosByViewId( 1280 mActivity.getResources().getResourceName(R.id.containerView)).get(0); 1281 1282 assertThat(parentContainer.getChildCount()).isEqualTo(0); 1283 assertThat(parentContainer.getChild(0)).isNull(); 1284 } finally { 1285 service.disableSelfAndRemove(); 1286 } 1287 } 1288 1289 @Test 1290 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive", 1291 "android.view.accessibility.AccessibilityNodeInfo#findAccessibilityNodeInfosByViewId"}) testAccessibilityDataSensitive_innerChildHidden()1292 public void testAccessibilityDataSensitive_innerChildHidden() { 1293 final InstrumentedAccessibilityService service = getServiceForA11yToolTests(false); 1294 1295 try { 1296 assertThat(service.getRootInActiveWindow().findAccessibilityNodeInfosByViewId( 1297 mActivity.getResources().getResourceName(R.id.innerView))).isEmpty(); 1298 } finally { 1299 service.disableSelfAndRemove(); 1300 } 1301 } 1302 1303 @Test 1304 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive", 1305 "android.view.accessibility.AccessibilityManager#sendAccessibilityEvent"}) testAccessibilityDataSensitive_hiddenFromEventPropagation()1306 public void testAccessibilityDataSensitive_hiddenFromEventPropagation() { 1307 final StubEventCapturingAccessibilityService service = getServiceForA11yToolTests(false); 1308 try { 1309 final View innerView = mActivity.findViewById(R.id.innerView); 1310 innerView.setOnClickListener(v -> { 1311 // empty, but necessary for performClick to return true 1312 }); 1313 assertTrue(innerView.isAccessibilityDataSensitive()); 1314 assertTrue(innerView.isClickable()); 1315 1316 service.setEventFilter(filterForEventType(TYPE_VIEW_CLICKED)); 1317 sInstrumentation.runOnMainSync(() -> assertThat(innerView.performClick()).isTrue()); 1318 assertThrows("Received TYPE_VIEW_CLICKED event from accessibilityDataSensitive view.", 1319 AssertionError.class, 1320 () -> service.waitOnEvent(DEFAULT_TIMEOUT_MS, "(expected to timeout)")); 1321 } finally { 1322 service.disableSelfAndRemove(); 1323 } 1324 } 1325 1326 @Test 1327 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive"}) testAccessibilityDataSensitive_hiddenIfFilterTouchesWhenObscured()1328 public void testAccessibilityDataSensitive_hiddenIfFilterTouchesWhenObscured() { 1329 final InstrumentedAccessibilityService service = getServiceForA11yToolTests(false); 1330 try { 1331 View containerView = mActivity.findViewById(R.id.containerView); 1332 assertThat(containerView.isAccessibilityDataSensitive()).isFalse(); 1333 assertThat(containerView.getFilterTouchesWhenObscured()).isFalse(); 1334 1335 mActivity.findViewById(R.id.containerView).setFilterTouchesWhenObscured(true); 1336 1337 assertThat(containerView.isAccessibilityDataSensitive()).isTrue(); 1338 assertThat(service.getRootInActiveWindow().findAccessibilityNodeInfosByViewId( 1339 mActivity.getResources().getResourceName(R.id.containerView))).isEmpty(); 1340 } finally { 1341 service.disableSelfAndRemove(); 1342 } 1343 } 1344 1345 @Test 1346 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive", 1347 "android.view.View#setAccessibilityDataSensitive"}) testAccessibilityDataSensitive_changingValueUpdatesChildren_noFirst()1348 public void testAccessibilityDataSensitive_changingValueUpdatesChildren_noFirst() { 1349 final InstrumentedAccessibilityService service = getServiceForA11yToolTests(false); 1350 try { 1351 final AccessibilityNodeInfo root = service.getRootInActiveWindow(); 1352 // The view starts as ADS=true as defined in the XML. 1353 View adsView = mActivity.findViewById(R.id.adsView); 1354 assertThat(adsView.isAccessibilityDataSensitive()).isTrue(); 1355 1356 // Set to NO, ensure we can find this view & all (grand)children. 1357 adsView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_NO); 1358 assertThat(adsView.isAccessibilityDataSensitive()).isFalse(); 1359 assertThat(root.findAccessibilityNodeInfosByViewId( 1360 mActivity.getResources().getResourceName(R.id.adsView))).isNotEmpty(); 1361 assertThat(root.findAccessibilityNodeInfosByViewId( 1362 mActivity.getResources().getResourceName( 1363 R.id.innerContainerView))).isNotEmpty(); 1364 assertThat(root.findAccessibilityNodeInfosByViewId( 1365 mActivity.getResources().getResourceName(R.id.innerView))).isNotEmpty(); 1366 1367 // Set back to YES, ensure this view & all (grand)children are hidden. 1368 adsView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES); 1369 assertThat(adsView.isAccessibilityDataSensitive()).isTrue(); 1370 assertThat(root.findAccessibilityNodeInfosByViewId( 1371 mActivity.getResources().getResourceName(R.id.adsView))).isEmpty(); 1372 assertThat(root.findAccessibilityNodeInfosByViewId( 1373 mActivity.getResources().getResourceName(R.id.innerContainerView))).isEmpty(); 1374 assertThat(root.findAccessibilityNodeInfosByViewId( 1375 mActivity.getResources().getResourceName(R.id.innerView))).isEmpty(); 1376 } finally { 1377 service.disableSelfAndRemove(); 1378 } 1379 } 1380 1381 @Test 1382 @ApiTest(apis = {"android.view.View#isAccessibilityDataSensitive", 1383 "android.view.View#setAccessibilityDataSensitive"}) testAccessibilityDataSensitive_changingValueUpdatesChildren_yesFirst()1384 public void testAccessibilityDataSensitive_changingValueUpdatesChildren_yesFirst() { 1385 final InstrumentedAccessibilityService service = getServiceForA11yToolTests(false); 1386 try { 1387 final AccessibilityNodeInfo root = service.getRootInActiveWindow(); 1388 // The view starts as AccessibilityDataSensitive=true as defined in the XML. 1389 View adsView = mActivity.findViewById(R.id.adsView); 1390 assertThat(adsView.isAccessibilityDataSensitive()).isTrue(); 1391 1392 // Explicitly set to YES, ensure this view & all (grand)children are hidden. 1393 adsView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES); 1394 assertThat(adsView.isAccessibilityDataSensitive()).isTrue(); 1395 assertThat(root.findAccessibilityNodeInfosByViewId( 1396 mActivity.getResources().getResourceName(R.id.adsView))).isEmpty(); 1397 assertThat(root.findAccessibilityNodeInfosByViewId( 1398 mActivity.getResources().getResourceName(R.id.innerContainerView))).isEmpty(); 1399 assertThat(root.findAccessibilityNodeInfosByViewId( 1400 mActivity.getResources().getResourceName(R.id.innerView))).isEmpty(); 1401 1402 // Set to NO, ensure we can find this view & all (grand)children. 1403 adsView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_NO); 1404 assertThat(adsView.isAccessibilityDataSensitive()).isFalse(); 1405 assertThat(root.findAccessibilityNodeInfosByViewId( 1406 mActivity.getResources().getResourceName(R.id.adsView))).isNotEmpty(); 1407 assertThat(root.findAccessibilityNodeInfosByViewId( 1408 mActivity.getResources().getResourceName( 1409 R.id.innerContainerView))).isNotEmpty(); 1410 assertThat(root.findAccessibilityNodeInfosByViewId( 1411 mActivity.getResources().getResourceName(R.id.innerView))).isNotEmpty(); 1412 } finally { 1413 service.disableSelfAndRemove(); 1414 } 1415 } 1416 1417 @Test 1418 @ApiTest(apis = { 1419 "android.view.accessibility.AccessibilityManager#isRequestFromAccessibilityTool"}) testAccessibilityDataSensitive_requestIsFromAccessibilityTool_TrueForTool()1420 public void testAccessibilityDataSensitive_requestIsFromAccessibilityTool_TrueForTool() { 1421 checkIsRequestFromAccessibilityTool(true); 1422 } 1423 1424 @Test 1425 @ApiTest(apis = { 1426 "android.view.accessibility.AccessibilityManager#isRequestFromAccessibilityTool"}) testAccessibilityDataSensitive_requestIsFromAccessibilityTool_FalseForNonTool()1427 public void testAccessibilityDataSensitive_requestIsFromAccessibilityTool_FalseForNonTool() { 1428 checkIsRequestFromAccessibilityTool(false); 1429 } 1430 checkIsRequestFromAccessibilityTool(boolean serviceIsAccessibilityTool)1431 private void checkIsRequestFromAccessibilityTool(boolean serviceIsAccessibilityTool) { 1432 final InstrumentedAccessibilityService service = 1433 getServiceForA11yToolTests(serviceIsAccessibilityTool); 1434 try { 1435 final View view = mActivity.findViewById(R.id.listview); 1436 final String viewId = mActivity.getResources().getResourceName(R.id.listview); 1437 final AccessibilityManager accessibilityManager = 1438 (AccessibilityManager) sInstrumentation.getContext().getSystemService( 1439 Service.ACCESSIBILITY_SERVICE); 1440 1441 final Object waitLock = new Object(); 1442 final AtomicReference<Boolean> fromTool = new AtomicReference<>(); 1443 view.setAccessibilityDelegate(new View.AccessibilityDelegate() { 1444 @Override 1445 public void onInitializeAccessibilityNodeInfo(View host, 1446 AccessibilityNodeInfo info) { 1447 super.onInitializeAccessibilityNodeInfo(host, info); 1448 synchronized (waitLock) { 1449 fromTool.set(accessibilityManager.isRequestFromAccessibilityTool()); 1450 waitLock.notifyAll(); 1451 } 1452 } 1453 }); 1454 1455 // Trigger node creation from the service-under-test. 1456 service.getRootInActiveWindow().findAccessibilityNodeInfosByViewId(viewId); 1457 1458 TestUtils.waitOn(waitLock, 1459 () -> fromTool.get() != null && fromTool.get() == serviceIsAccessibilityTool, 1460 DEFAULT_TIMEOUT_MS, 1461 "Expected isRequestFromAccessibilityTool to be " 1462 + serviceIsAccessibilityTool); 1463 } finally { 1464 service.disableSelfAndRemove(); 1465 } 1466 } 1467 1468 @Test 1469 @ApiTest(apis = { 1470 "android.view.accessibility.AccessibilityNodeInfo#setQueryFromAppProcessEnabled"}) testDirectAccessibilityConnection_NavigateHierarchy()1471 public void testDirectAccessibilityConnection_NavigateHierarchy() throws Throwable { 1472 View layoutView = mActivity.findViewById(R.id.buttonLayout); 1473 AccessibilityNodeInfo layoutNode = layoutView.createAccessibilityNodeInfo(); 1474 1475 assertThat(layoutNode).isNotNull(); 1476 layoutNode.setQueryFromAppProcessEnabled(layoutView.getRootView(), true); 1477 1478 // Access this node's children. 1479 assertThat(layoutNode.getChildCount()).isGreaterThan(0); 1480 for (int i = layoutNode.getChildCount() - 1; i >= 0; i--) { 1481 assertThat(layoutNode.getChild(i)).isNotNull(); 1482 } 1483 1484 // Find the root node by accessing parents going up the hierarchy. 1485 AccessibilityNodeInfo rootNode = layoutNode; 1486 while (rootNode.getParent() != null) { 1487 rootNode = rootNode.getParent(); 1488 } 1489 assertThat(rootNode).isEqualTo(layoutView.getRootView().createAccessibilityNodeInfo()); 1490 1491 // Find more nodes, starting from the root. 1492 assertThat(rootNode.findAccessibilityNodeInfosByViewId( 1493 "android.accessibilityservice.cts:id/button")).isNotEmpty(); 1494 assertThat(rootNode.findAccessibilityNodeInfosByText( 1495 mActivity.getString(R.string.button_title))).isNotEmpty(); 1496 1497 // Find and search the focus. 1498 try { 1499 // Enable touch exploration, needed for performAction(ACTION_ACCESSIBILITY_FOCUS). 1500 enableTouchExploration(true); 1501 final AccessibilityNodeInfo buttonNode = rootNode.findAccessibilityNodeInfosByViewId( 1502 "android.accessibilityservice.cts:id/button").get(0); 1503 sUiAutomation.executeAndWaitForEvent( 1504 () -> assertTrue( 1505 buttonNode.performAction( 1506 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)), 1507 filterForEventType(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED), 1508 DEFAULT_TIMEOUT_MS); 1509 assertThat(rootNode.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)).isEqualTo( 1510 buttonNode); 1511 assertThat(rootNode.focusSearch(View.FOCUS_FORWARD)).isNotNull(); 1512 } finally { 1513 enableTouchExploration(false); 1514 } 1515 } 1516 1517 @Test 1518 @ApiTest(apis = { 1519 "android.view.accessibility.AccessibilityNodeInfo#setQueryFromAppProcessEnabled"}) testDirectAccessibilityConnection_CanPerformAction()1520 public void testDirectAccessibilityConnection_CanPerformAction() { 1521 View button = mActivity.findViewById(R.id.button); 1522 AtomicBoolean clicked = new AtomicBoolean(false); 1523 button.setOnClickListener((view) -> clicked.set(true)); 1524 AccessibilityNodeInfo buttonNode = button.createAccessibilityNodeInfo(); 1525 1526 assertThat(buttonNode).isNotNull(); 1527 buttonNode.setQueryFromAppProcessEnabled(button.getRootView(), true); 1528 1529 assertThat(buttonNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)).isTrue(); 1530 assertThat(clicked.get()).isTrue(); 1531 } 1532 1533 @Test 1534 @ApiTest(apis = { 1535 "android.view.accessibility.AccessibilityNodeInfo#setQueryFromAppProcessEnabled"}) testDirectAccessibilityConnection_CanDisable()1536 public void testDirectAccessibilityConnection_CanDisable() { 1537 View layoutView = mActivity.findViewById(R.id.buttonLayout); 1538 AccessibilityNodeInfo layoutNode = layoutView.createAccessibilityNodeInfo(); 1539 assertThat(layoutNode).isNotNull(); 1540 1541 layoutNode.setQueryFromAppProcessEnabled(layoutView.getRootView(), true); 1542 assertThat(layoutNode.getParent()).isNotNull(); 1543 1544 layoutNode.setQueryFromAppProcessEnabled(layoutView.getRootView(), false); 1545 try { 1546 layoutNode.getParent(); 1547 fail("Should not be able to navigate node tree on node without any connection."); 1548 } catch (IllegalStateException e) { 1549 // expected due to undefined connection ID 1550 } 1551 } 1552 1553 @Test 1554 @ApiTest(apis = { 1555 "android.view.accessibility.AccessibilityNodeInfo#setQueryFromAppProcessEnabled"}) testDirectAccessibilityConnection_AccessibilityManagerEnabled()1556 public void testDirectAccessibilityConnection_AccessibilityManagerEnabled() { 1557 // Note: this test checks AM#hasAnyDirectConnection() as a proxy for #isEnabled because 1558 // #isEnabled is also modified by the UiAutomation used in this test. 1559 1560 View layoutView = mActivity.findViewById(R.id.buttonLayout); 1561 AccessibilityNodeInfo layoutNode = layoutView.createAccessibilityNodeInfo(); 1562 final AccessibilityManager accessibilityManager = 1563 (AccessibilityManager) sInstrumentation.getContext().getSystemService( 1564 Service.ACCESSIBILITY_SERVICE); 1565 1566 // Ensure no DirectConnection to start. 1567 assertThat(accessibilityManager.hasAnyDirectConnection()).isFalse(); 1568 1569 // Enable app-process querying, which adds a connection for this node. 1570 layoutNode.setQueryFromAppProcessEnabled(layoutView.getRootView(), true); 1571 assertThat(accessibilityManager.hasAnyDirectConnection()).isTrue(); 1572 1573 // Disable app-process querying for this node. 1574 layoutNode.setQueryFromAppProcessEnabled(layoutView.getRootView(), false); 1575 // The connection should still exist until ViewRootImpl detaches from the window, in case 1576 // other nodes in this view hierarchy use the connection. 1577 assertThat(accessibilityManager.hasAnyDirectConnection()).isTrue(); 1578 1579 // Detach the ViewRootImpl from the window by finishing the activity, then wait for the 1580 // change notification that comes from ViewRootImpl itself, after which the connection 1581 // should now be gone. 1582 final Object waitLock = new Object(); 1583 final AtomicBoolean hasAnyDirectConnection = new AtomicBoolean(true); 1584 accessibilityManager.addAccessibilityStateChangeListener( 1585 enabled -> { 1586 synchronized (waitLock) { 1587 hasAnyDirectConnection.set(accessibilityManager.hasAnyDirectConnection()); 1588 waitLock.notifyAll(); 1589 } 1590 }); 1591 mActivity.runOnUiThread(() -> mActivity.finish()); 1592 TestUtils.waitOn(waitLock, () -> !hasAnyDirectConnection.get(), DEFAULT_TIMEOUT_MS, 1593 "AccessibilityManager#hasAnyDirectConnection() still true"); 1594 } 1595 1596 @Test 1597 @ApiTest(apis = { 1598 "android.view.accessibility.AccessibilityNodeInfo#setQueryFromAppProcessEnabled"}) testDirectAccessibilityConnection_UsesCurrentWindowSpec()1599 public void testDirectAccessibilityConnection_UsesCurrentWindowSpec() throws Throwable { 1600 // Store the initial bounds of the ANI. 1601 final View layoutView = mActivity.findViewById(R.id.buttonLayout); 1602 final AccessibilityNodeInfo layoutNode = layoutView.createAccessibilityNodeInfo(); 1603 final Rect initialBounds = new Rect(); 1604 layoutNode.setQueryFromAppProcessEnabled(layoutView, true); 1605 layoutNode.getBoundsInScreen(initialBounds); 1606 1607 // Magnify the screen. 1608 final StubMagnificationAccessibilityService service = 1609 InstrumentedAccessibilityService.enableService( 1610 StubMagnificationAccessibilityService.class); 1611 try { 1612 final MagnificationConfig magnificationConfig = 1613 new MagnificationConfig.Builder().setMode(MAGNIFICATION_MODE_FULLSCREEN) 1614 .setScale(2f).build(); 1615 service.runOnServiceSync( 1616 () -> service.getMagnificationController() 1617 .setMagnificationConfig(magnificationConfig, false)); 1618 1619 // Check that the ANI bounds have changed. 1620 TestUtils.waitUntil("Failed to refresh node with updated boundsInScreen", 1621 (int) DEFAULT_TIMEOUT_MS / 1000, 1622 () -> { 1623 final Rect boundsAfterMagnification = new Rect(); 1624 layoutNode.refresh(); 1625 layoutNode.getBoundsInScreen(boundsAfterMagnification); 1626 return !boundsAfterMagnification.equals(initialBounds); 1627 }); 1628 } finally { 1629 service.disableSelfAndRemove(); 1630 } 1631 } 1632 1633 @Test 1634 @ApiTest(apis = { 1635 "android.view.accessibility.AccessibilityNodeInfo" 1636 + "#setMinDurationBetweenContentChanges", 1637 "android.view.accessibility.AccessibilityNodeInfo" 1638 + "#getMinDurationBetweenContentChanges"}) testSetMinDurationBetweenContentChanges()1639 public void testSetMinDurationBetweenContentChanges() { 1640 final View testView = mActivity.findViewById(R.id.buttonLayout); 1641 final AccessibilityNodeInfo nodeInfo = testView.createAccessibilityNodeInfo(); 1642 nodeInfo.setMinDurationBetweenContentChanges(Duration.ofMillis(200)); 1643 assertThat(nodeInfo.getMinDurationBetweenContentChanges().toMillis()).isEqualTo(200); 1644 } 1645 1646 @Test 1647 @ApiTest(apis = { 1648 "android.view.accessibility.AccessibilityNodeInfo" 1649 + "#setRequestInitialAccessibilityFocus", 1650 "android.view.accessibility.AccessibilityNodeInfo" 1651 + "#hasRequestInitialAccessibilityFocus"}) testSetRequestInitialAccessibilityFocus()1652 public void testSetRequestInitialAccessibilityFocus() { 1653 final View testView = mActivity.findViewById(R.id.buttonLayout); 1654 final AccessibilityNodeInfo nodeInfo = testView.createAccessibilityNodeInfo(); 1655 nodeInfo.setRequestInitialAccessibilityFocus(true); 1656 assertThat(nodeInfo.hasRequestInitialAccessibilityFocus()).isTrue(); 1657 } 1658 1659 1660 @AsbSecurityTest(cveBugId = {243378132}) 1661 @Test testUninstallPackage_DisablesMultipleServices()1662 public void testUninstallPackage_DisablesMultipleServices() throws Exception { 1663 final String apkPath = 1664 "/data/local/tmp/cts/content/CtsAccessibilityMultipleServicesApp.apk"; 1665 final String packageName = "foo.bar.multipleservices"; 1666 final ComponentName service1 = ComponentName.createRelative(packageName, ".StubService1"); 1667 final ComponentName service2 = ComponentName.createRelative(packageName, ".StubService2"); 1668 // Match AccessibilityManagerService#COMPONENT_NAME_SEPARATOR 1669 final String componentNameSeparator = ":"; 1670 1671 final String originalEnabledServicesSetting = getEnabledServicesSetting(); 1672 1673 try { 1674 // Install the apk in this test method, instead of as part of the target preparer, to 1675 // allow repeated --iterations of the test. 1676 assertThat(ShellUtils.runShellCommand("pm install " + apkPath)).startsWith("Success"); 1677 1678 // Enable the two services and wait until AccessibilityManager reports them as enabled. 1679 final String servicesToEnable = getEnabledServicesSetting() + componentNameSeparator 1680 + service1.flattenToShortString() + componentNameSeparator 1681 + service2.flattenToShortString(); 1682 ShellCommandBuilder.create(sInstrumentation) 1683 .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 1684 servicesToEnable) 1685 .putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1") 1686 .run(); 1687 TestUtils.waitUntil("Failed to enable 2 services from package " + packageName, 1688 (int) TIMEOUT_SERVICE_ENABLE / 1000, 1689 () -> getEnabledServices().stream().filter( 1690 info -> info.getId().startsWith(packageName)).count() == 2); 1691 1692 // Uninstall the package that contains the services. 1693 assertThat(ShellUtils.runShellCommand("pm uninstall " + packageName)).startsWith( 1694 "Success"); 1695 1696 // Ensure the uninstall removed the services from the secure setting. 1697 TestUtils.waitUntil( 1698 "Failed to disable services after uninstalling package " + packageName, 1699 (int) TIMEOUT_SERVICE_ENABLE / 1000, 1700 () -> !getEnabledServicesSetting().contains(packageName)); 1701 } finally { 1702 ShellCommandBuilder.create(sInstrumentation) 1703 .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 1704 originalEnabledServicesSetting) 1705 .run(); 1706 ShellUtils.runShellCommand("pm uninstall " + packageName); 1707 } 1708 } 1709 1710 @Test 1711 @ApiTest(apis = { 1712 "android.view.accessibility.AccessibilityNodeInfo#setContainerTitle"}) testSetContainerTitle()1713 public void testSetContainerTitle() { 1714 View testView = mActivity.findViewById(R.id.buttonLayout); 1715 AccessibilityNodeInfo nodeInfo = testView.createAccessibilityNodeInfo(); 1716 nodeInfo.setContainerTitle("Container title"); 1717 assertEquals("Container title", nodeInfo.getContainerTitle()); 1718 1719 nodeInfo.setContainerTitle(null); 1720 assertEquals(null, nodeInfo.getContainerTitle()); 1721 } 1722 1723 @Test 1724 @ApiTest(apis = {"android.accessibilityservice.AccessibilityService#onMotionEvent"}) testOnMotionEvent_interceptsEventFromRequestedSource_SetAndUnset()1725 public void testOnMotionEvent_interceptsEventFromRequestedSource_SetAndUnset() { 1726 final int requestedSource = InputDevice.SOURCE_JOYSTICK; 1727 final StubMotionInterceptingAccessibilityService service = 1728 mMotionInterceptingServiceRule.enableService(); 1729 service.setMotionEventSources(requestedSource); 1730 assertThat(service.getServiceInfo().getMotionEventSources()).isEqualTo(requestedSource); 1731 final Object waitObject = new Object(); 1732 final AtomicInteger eventCount = new AtomicInteger(0); 1733 service.setOnMotionEventListener(motionEvent -> { 1734 synchronized (waitObject) { 1735 if (motionEvent.getSource() == requestedSource) { 1736 eventCount.incrementAndGet(); 1737 } 1738 waitObject.notifyAll(); 1739 } 1740 }); 1741 1742 // Inject 2 events to the input filter. 1743 sUiAutomation.injectInputEventToInputFilter(createMotionEvent(requestedSource)); 1744 sUiAutomation.injectInputEventToInputFilter(createMotionEvent(requestedSource)); 1745 // We should find 2 events. 1746 TestUtils.waitOn(waitObject, () -> eventCount.get() == 2, 1747 TIMEOUT_FOR_MOTION_EVENT_INTERCEPTION_MS, 1748 "Service did not receive MotionEvent"); 1749 1750 // Stop listening to events for this source, then inject 1 more event to the input filter. 1751 service.setMotionEventSources(0 /* no sources */); 1752 assertThat(service.getServiceInfo().getMotionEventSources()).isEqualTo(0); 1753 sUiAutomation.injectInputEventToInputFilter(createMotionEvent(requestedSource)); 1754 // Assert we only received the original 2. 1755 try { 1756 TestUtils.waitOn(waitObject, () -> eventCount.get() == 3, 1757 TIMEOUT_FOR_MOTION_EVENT_INTERCEPTION_MS, 1758 "(expected)"); 1759 } catch (AssertionError e) { 1760 // expected 1761 } 1762 assertThat(eventCount.get()).isEqualTo(2); 1763 } 1764 1765 @Test 1766 @ApiTest(apis = {"android.accessibilityservice.AccessibilityService#onMotionEvent"}) testOnMotionEvent_ignoresEventFromDifferentSource()1767 public void testOnMotionEvent_ignoresEventFromDifferentSource() { 1768 final int requestedSource = InputDevice.SOURCE_JOYSTICK; 1769 final int actualSource = InputDevice.SOURCE_ROTARY_ENCODER; 1770 final StubMotionInterceptingAccessibilityService service = 1771 mMotionInterceptingServiceRule.enableService(); 1772 service.setMotionEventSources(requestedSource); 1773 final Object waitObject = new Object(); 1774 final AtomicBoolean foundEvent = new AtomicBoolean(false); 1775 service.setOnMotionEventListener(motionEvent -> { 1776 synchronized (waitObject) { 1777 if (motionEvent.getSource() == requestedSource) { 1778 foundEvent.set(true); 1779 } 1780 waitObject.notifyAll(); 1781 } 1782 }); 1783 1784 sUiAutomation.injectInputEventToInputFilter(createMotionEvent(actualSource)); 1785 1786 try { 1787 TestUtils.waitOn(waitObject, foundEvent::get, TIMEOUT_FOR_MOTION_EVENT_INTERCEPTION_MS, 1788 "(expected)"); 1789 } catch (AssertionError e) { 1790 // expected 1791 } 1792 assertThat(foundEvent.get()).isFalse(); 1793 } 1794 1795 @Test 1796 @ApiTest(apis = {"android.accessibilityservice.AccessibilityService#onMotionEvent"}) testOnMotionEvent_ignoresTouchscreenEventWhenTouchExplorationEnabled()1797 public void testOnMotionEvent_ignoresTouchscreenEventWhenTouchExplorationEnabled() { 1798 final int requestedSource = InputDevice.SOURCE_TOUCHSCREEN; 1799 final StubMotionInterceptingAccessibilityService motionInterceptingService = 1800 mMotionInterceptingServiceRule.enableService(); 1801 TouchExplorationStubAccessibilityService touchExplorationService = 1802 enableService(TouchExplorationStubAccessibilityService.class); 1803 try { 1804 motionInterceptingService.setMotionEventSources(requestedSource); 1805 final Object waitObject = new Object(); 1806 final AtomicBoolean foundEvent = new AtomicBoolean(false); 1807 motionInterceptingService.setOnMotionEventListener(motionEvent -> { 1808 synchronized (waitObject) { 1809 if (motionEvent.getSource() == requestedSource) { 1810 foundEvent.set(true); 1811 } 1812 waitObject.notifyAll(); 1813 } 1814 }); 1815 1816 sUiAutomation.injectInputEventToInputFilter(createMotionEvent(requestedSource)); 1817 1818 try { 1819 TestUtils.waitOn(waitObject, foundEvent::get, 1820 TIMEOUT_FOR_MOTION_EVENT_INTERCEPTION_MS, 1821 "(expected)"); 1822 } catch (AssertionError e) { 1823 // expected 1824 } 1825 assertThat(foundEvent.get()).isFalse(); 1826 } finally { 1827 touchExplorationService.disableSelfAndRemove(); 1828 } 1829 } 1830 createMotionEvent(int source)1831 private MotionEvent createMotionEvent(int source) { 1832 // Only source is used by these tests, so set other properties to valid defaults. 1833 final long eventTime = SystemClock.uptimeMillis(); 1834 final MotionEvent.PointerProperties props = new MotionEvent.PointerProperties(); 1835 props.id = 0; 1836 return MotionEvent.obtain(eventTime, 1837 eventTime, 1838 MotionEvent.ACTION_MOVE, 1839 1 /* pointerCount */, 1840 new MotionEvent.PointerProperties[]{props}, 1841 new MotionEvent.PointerCoords[]{new MotionEvent.PointerCoords()}, 1842 0 /* metaState */, 1843 0 /* buttonState */, 1844 0 /* xPrecision */, 1845 0 /* yPrecision */, 1846 1 /* deviceId */, 1847 0 /* edgeFlags */, 1848 source, 1849 0 /* flags */); 1850 } 1851 getEnabledServices()1852 private List<AccessibilityServiceInfo> getEnabledServices() { 1853 return ((AccessibilityManager) sInstrumentation.getContext().getSystemService( 1854 Context.ACCESSIBILITY_SERVICE)).getEnabledAccessibilityServiceList( 1855 FEEDBACK_ALL_MASK); 1856 } 1857 getEnabledServicesSetting()1858 private String getEnabledServicesSetting() { 1859 final String result = Settings.Secure.getString( 1860 sInstrumentation.getContext().getContentResolver(), 1861 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 1862 return result != null ? result : ""; 1863 } 1864 assertPackageName(AccessibilityNodeInfo node, String packageName)1865 private static void assertPackageName(AccessibilityNodeInfo node, String packageName) { 1866 if (node == null) { 1867 return; 1868 } 1869 assertEquals(packageName, node.getPackageName()); 1870 final int childCount = node.getChildCount(); 1871 for (int i = 0; i < childCount; i++) { 1872 AccessibilityNodeInfo child = node.getChild(i); 1873 if (child != null) { 1874 assertPackageName(child, packageName); 1875 } 1876 } 1877 } 1878 enableTouchExploration(boolean enabled)1879 private static void enableTouchExploration(boolean enabled) 1880 throws InterruptedException { 1881 final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s 1882 final Object waitObject = new Object(); 1883 final AtomicBoolean atomicBoolean = new AtomicBoolean(!enabled); 1884 AccessibilityManager.TouchExplorationStateChangeListener serviceListener = (boolean b) -> { 1885 synchronized (waitObject) { 1886 atomicBoolean.set(b); 1887 waitObject.notifyAll(); 1888 } 1889 }; 1890 final AccessibilityManager manager = 1891 (AccessibilityManager) sInstrumentation.getContext().getSystemService( 1892 Service.ACCESSIBILITY_SERVICE); 1893 manager.addTouchExplorationStateChangeListener(serviceListener); 1894 1895 final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo(); 1896 assert info != null; 1897 if (enabled) { 1898 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1899 } else { 1900 info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1901 } 1902 sUiAutomation.setServiceInfo(info); 1903 1904 final long timeoutTime = System.currentTimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE; 1905 synchronized (waitObject) { 1906 while ((enabled != atomicBoolean.get()) && (System.currentTimeMillis() < timeoutTime)) { 1907 waitObject.wait(timeoutTime - System.currentTimeMillis()); 1908 } 1909 } 1910 if (enabled) { 1911 assertTrue("Touch exploration state listener not called when services enabled", 1912 atomicBoolean.get()); 1913 assertTrue("Timed out enabling accessibility", 1914 manager.isEnabled() && manager.isTouchExplorationEnabled()); 1915 } else { 1916 assertFalse("Touch exploration state listener not called when services disabled", 1917 atomicBoolean.get()); 1918 assertFalse("Timed out disabling accessibility", 1919 manager.isEnabled() && manager.isTouchExplorationEnabled()); 1920 } 1921 manager.removeTouchExplorationStateChangeListener(serviceListener); 1922 } 1923 1924 /** 1925 * Returns a service for testing how accessibility tools or non-tools react to the 1926 * {@link View#isAccessibilityDataSensitive} property. 1927 * 1928 * @return {@link StubA11yToolAccessibilityService} when <code>isAccessibilityTool</code> is 1929 * true, otherwise returns {@link StubNonA11yToolAccessibilityService}. 1930 */ getServiceForA11yToolTests( boolean isAccessibilityTool)1931 private StubEventCapturingAccessibilityService getServiceForA11yToolTests( 1932 boolean isAccessibilityTool) { 1933 final StubEventCapturingAccessibilityService service; 1934 if (isAccessibilityTool) { 1935 service = InstrumentedAccessibilityService.enableService( 1936 StubA11yToolAccessibilityService.class); 1937 } else { 1938 service = InstrumentedAccessibilityService.enableService( 1939 StubNonA11yToolAccessibilityService.class); 1940 } 1941 final AccessibilityServiceInfo info = service.getServiceInfo(); 1942 if (info == null || info.isAccessibilityTool() != isAccessibilityTool) { 1943 service.disableSelfAndRemove(); 1944 fail("Expected service to have isAccessibilityTool=" + isAccessibilityTool); 1945 } 1946 return service; 1947 } 1948 matchHover(int action, int x, int y)1949 private static MotionEvent matchHover(int action, int x, int y) { 1950 return argThat(new CtsMouseUtil.PositionMatcher(action, x, y)); 1951 } 1952 injectHoverEvent(long downTime, boolean isFirstHoverEvent, int xOnScreen, int yOnScreen)1953 private static void injectHoverEvent(long downTime, boolean isFirstHoverEvent, 1954 int xOnScreen, int yOnScreen) { 1955 final long eventTime = isFirstHoverEvent ? SystemClock.uptimeMillis() : downTime; 1956 MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_HOVER_MOVE, 1957 xOnScreen, yOnScreen, 0); 1958 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 1959 sInstrumentation.sendPointerSync(event); 1960 event.recycle(); 1961 } 1962 getAppWidgetProviderInfo()1963 private AppWidgetProviderInfo getAppWidgetProviderInfo() { 1964 final ComponentName componentName = new ComponentName( 1965 "foo.bar.baz", "foo.bar.baz.MyAppWidgetProvider"); 1966 final List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders(); 1967 final int providerCount = providers.size(); 1968 for (int i = 0; i < providerCount; i++) { 1969 final AppWidgetProviderInfo provider = providers.get(i); 1970 if (componentName.equals(provider.provider) 1971 && Process.myUserHandle().equals(provider.getProfile())) { 1972 return provider; 1973 } 1974 } 1975 return null; 1976 } 1977 grantBindAppWidgetPermission()1978 private void grantBindAppWidgetPermission() throws Exception { 1979 ShellCommandBuilder.execShellCommand(sUiAutomation, 1980 GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND + getCurrentUser()); 1981 } 1982 revokeBindAppWidgetPermission()1983 private void revokeBindAppWidgetPermission() throws Exception { 1984 ShellCommandBuilder.execShellCommand(sUiAutomation, 1985 REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND + getCurrentUser()); 1986 } 1987 getAppWidgetManager()1988 private AppWidgetManager getAppWidgetManager() { 1989 return (AppWidgetManager) sInstrumentation.getTargetContext() 1990 .getSystemService(Context.APPWIDGET_SERVICE); 1991 } 1992 hasAppWidgets()1993 private boolean hasAppWidgets() { 1994 return sInstrumentation.getTargetContext().getPackageManager() 1995 .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS); 1996 } 1997 1998 /** 1999 * Compares all properties of the <code>first</code> and the 2000 * <code>second</code>. 2001 */ equalsAccessiblityEvent(AccessibilityEvent first, AccessibilityEvent second)2002 private boolean equalsAccessiblityEvent(AccessibilityEvent first, AccessibilityEvent second) { 2003 return first.getEventType() == second.getEventType() 2004 && first.isChecked() == second.isChecked() 2005 && first.getCurrentItemIndex() == second.getCurrentItemIndex() 2006 && first.isEnabled() == second.isEnabled() 2007 && first.getFromIndex() == second.getFromIndex() 2008 && first.getItemCount() == second.getItemCount() 2009 && first.isPassword() == second.isPassword() 2010 && first.getRemovedCount() == second.getRemovedCount() 2011 && first.isScrollable()== second.isScrollable() 2012 && first.getToIndex() == second.getToIndex() 2013 && first.getRecordCount() == second.getRecordCount() 2014 && first.getScrollX() == second.getScrollX() 2015 && first.getScrollY() == second.getScrollY() 2016 && first.getAddedCount() == second.getAddedCount() 2017 && first.getDisplayId() == second.getDisplayId() 2018 && TextUtils.equals(first.getBeforeText(), second.getBeforeText()) 2019 && TextUtils.equals(first.getClassName(), second.getClassName()) 2020 && TextUtils.equals(first.getContentDescription(), second.getContentDescription()) 2021 && equalsNotificationAsParcelableData(first, second) 2022 && equalsText(first, second); 2023 } 2024 2025 /** 2026 * Compares the {@link android.os.Parcelable} data of the 2027 * <code>first</code> and <code>second</code>. 2028 */ equalsNotificationAsParcelableData(AccessibilityEvent first, AccessibilityEvent second)2029 private boolean equalsNotificationAsParcelableData(AccessibilityEvent first, 2030 AccessibilityEvent second) { 2031 Notification firstNotification = (Notification) first.getParcelableData(); 2032 Notification secondNotification = (Notification) second.getParcelableData(); 2033 if (firstNotification == null) { 2034 return (secondNotification == null); 2035 } else if (secondNotification == null) { 2036 return false; 2037 } 2038 return TextUtils.equals(firstNotification.tickerText, secondNotification.tickerText); 2039 } 2040 2041 /** 2042 * Compares the text of the <code>first</code> and <code>second</code> text. 2043 */ equalsText(AccessibilityEvent first, AccessibilityEvent second)2044 private boolean equalsText(AccessibilityEvent first, AccessibilityEvent second) { 2045 List<CharSequence> firstText = first.getText(); 2046 List<CharSequence> secondText = second.getText(); 2047 if (firstText.size() != secondText.size()) { 2048 return false; 2049 } 2050 Iterator<CharSequence> firstIterator = firstText.iterator(); 2051 Iterator<CharSequence> secondIterator = secondText.iterator(); 2052 for (int i = 0; i < firstText.size(); i++) { 2053 if (!firstIterator.next().toString().equals(secondIterator.next().toString())) { 2054 return false; 2055 } 2056 } 2057 return true; 2058 } 2059 hasTooltipShowing(int id)2060 private boolean hasTooltipShowing(int id) { 2061 return getOnMain(sInstrumentation, () -> { 2062 final View viewWithTooltip = mActivity.findViewById(id); 2063 if (viewWithTooltip == null) { 2064 return false; 2065 } 2066 final View tooltipView = viewWithTooltip.getTooltipView(); 2067 return (tooltipView != null) && (tooltipView.getParent() != null); 2068 }); 2069 } 2070 getCurrentUser()2071 private static int getCurrentUser() { 2072 return android.os.Process.myUserHandle().getIdentifier(); 2073 } 2074 } 2075