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.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType; 20 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource; 21 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle; 22 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle; 23 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen; 24 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS; 25 import static android.accessibilityservice.cts.utils.RunOnMainUtils.getOnMain; 26 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP; 27 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP; 28 29 import static org.hamcrest.Matchers.in; 30 import static org.hamcrest.Matchers.not; 31 import static org.hamcrest.core.IsEqual.equalTo; 32 import static org.hamcrest.core.IsNull.notNullValue; 33 import static org.hamcrest.core.IsNull.nullValue; 34 import static org.junit.Assert.assertEquals; 35 import static org.junit.Assert.assertFalse; 36 import static org.junit.Assert.assertNotNull; 37 import static org.junit.Assert.assertThat; 38 import static org.junit.Assert.assertTrue; 39 import static org.junit.Assert.fail; 40 import static org.mockito.ArgumentMatchers.any; 41 import static org.mockito.ArgumentMatchers.argThat; 42 import static org.mockito.ArgumentMatchers.eq; 43 import static org.mockito.Mockito.inOrder; 44 import static org.mockito.Mockito.mock; 45 import static org.mockito.Mockito.timeout; 46 import static org.mockito.Mockito.verify; 47 48 import android.accessibility.cts.common.InstrumentedAccessibilityService; 49 import android.accessibility.cts.common.ShellCommandBuilder; 50 import android.accessibilityservice.AccessibilityServiceInfo; 51 import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity; 52 import android.app.Activity; 53 import android.app.AlertDialog; 54 import android.app.Instrumentation; 55 import android.app.Notification; 56 import android.app.NotificationChannel; 57 import android.app.NotificationManager; 58 import android.app.PendingIntent; 59 import android.app.Service; 60 import android.app.UiAutomation; 61 import android.appwidget.AppWidgetHost; 62 import android.appwidget.AppWidgetManager; 63 import android.appwidget.AppWidgetProviderInfo; 64 import android.content.ComponentName; 65 import android.content.Context; 66 import android.content.Intent; 67 import android.content.pm.PackageManager; 68 import android.content.res.Configuration; 69 import android.content.res.Resources; 70 import android.graphics.Rect; 71 import android.graphics.Region; 72 import android.os.Process; 73 import android.os.SystemClock; 74 import android.platform.test.annotations.AppModeFull; 75 import android.platform.test.annotations.Presubmit; 76 import android.test.suitebuilder.annotation.MediumTest; 77 import android.text.TextUtils; 78 import android.util.Log; 79 import android.view.InputDevice; 80 import android.view.MotionEvent; 81 import android.view.TouchDelegate; 82 import android.view.View; 83 import android.view.Window; 84 import android.view.WindowManager; 85 import android.view.accessibility.AccessibilityEvent; 86 import android.view.accessibility.AccessibilityManager; 87 import android.view.accessibility.AccessibilityNodeInfo; 88 import android.view.accessibility.AccessibilityWindowInfo; 89 import android.widget.Button; 90 import android.widget.EditText; 91 import android.widget.ListView; 92 93 import androidx.test.InstrumentationRegistry; 94 import androidx.test.rule.ActivityTestRule; 95 import androidx.test.runner.AndroidJUnit4; 96 97 import com.android.compatibility.common.util.CtsMouseUtil; 98 99 import org.junit.AfterClass; 100 import org.junit.Before; 101 import org.junit.BeforeClass; 102 import org.junit.Rule; 103 import org.junit.Test; 104 import org.junit.runner.RunWith; 105 106 import java.util.Iterator; 107 import java.util.List; 108 import java.util.concurrent.TimeoutException; 109 import java.util.concurrent.atomic.AtomicBoolean; 110 111 /** 112 * This class performs end-to-end testing of the accessibility feature by 113 * creating an {@link Activity} and poking around so {@link AccessibilityEvent}s 114 * are generated and their correct dispatch verified. 115 */ 116 @RunWith(AndroidJUnit4.class) 117 public class AccessibilityEndToEndTest { 118 119 private static final String LOG_TAG = "AccessibilityEndToEndTest"; 120 121 private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND = 122 "appwidget grantbind --package android.accessibilityservice.cts --user "; 123 124 private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND = 125 "appwidget revokebind --package android.accessibilityservice.cts --user "; 126 127 private static final String APP_WIDGET_PROVIDER_PACKAGE = "foo.bar.baz"; 128 129 private static Instrumentation sInstrumentation; 130 private static UiAutomation sUiAutomation; 131 132 private AccessibilityEndToEndActivity mActivity; 133 134 @Rule 135 public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule = 136 new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false); 137 138 @BeforeClass oneTimeSetup()139 public static void oneTimeSetup() throws Exception { 140 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 141 sUiAutomation = sInstrumentation.getUiAutomation(); 142 } 143 144 @AfterClass postTestTearDown()145 public static void postTestTearDown() { 146 sUiAutomation.destroy(); 147 } 148 149 @Before setUp()150 public void setUp() throws Exception { 151 mActivity = launchActivityAndWaitForItToBeOnscreen( 152 sInstrumentation, sUiAutomation, mActivityRule); 153 } 154 155 @MediumTest 156 @Presubmit 157 @Test testTypeViewSelectedAccessibilityEvent()158 public void testTypeViewSelectedAccessibilityEvent() throws Throwable { 159 // create and populate the expected event 160 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 161 expected.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED); 162 expected.setClassName(ListView.class.getName()); 163 expected.setPackageName(mActivity.getPackageName()); 164 expected.getText().add(mActivity.getString(R.string.second_list_item)); 165 expected.setItemCount(2); 166 expected.setCurrentItemIndex(1); 167 expected.setEnabled(true); 168 expected.setScrollable(false); 169 expected.setFromIndex(0); 170 expected.setToIndex(1); 171 172 final ListView listView = (ListView) mActivity.findViewById(R.id.listview); 173 174 AccessibilityEvent awaitedEvent = 175 sUiAutomation.executeAndWaitForEvent( 176 new Runnable() { 177 @Override 178 public void run() { 179 // trigger the event 180 mActivity.runOnUiThread(new Runnable() { 181 @Override 182 public void run() { 183 listView.setSelection(1); 184 } 185 }); 186 }}, 187 new UiAutomation.AccessibilityEventFilter() { 188 // check the received event 189 @Override 190 public boolean accept(AccessibilityEvent event) { 191 return equalsAccessiblityEvent(event, expected); 192 } 193 }, 194 DEFAULT_TIMEOUT_MS); 195 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 196 } 197 198 @MediumTest 199 @Presubmit 200 @Test testTypeViewClickedAccessibilityEvent()201 public void testTypeViewClickedAccessibilityEvent() throws Throwable { 202 // create and populate the expected event 203 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 204 expected.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED); 205 expected.setClassName(Button.class.getName()); 206 expected.setPackageName(mActivity.getPackageName()); 207 expected.getText().add(mActivity.getString(R.string.button_title)); 208 expected.setEnabled(true); 209 210 final Button button = (Button) mActivity.findViewById(R.id.button); 211 212 AccessibilityEvent awaitedEvent = 213 sUiAutomation.executeAndWaitForEvent( 214 new Runnable() { 215 @Override 216 public void run() { 217 // trigger the event 218 mActivity.runOnUiThread(new Runnable() { 219 @Override 220 public void run() { 221 button.performClick(); 222 } 223 }); 224 }}, 225 new UiAutomation.AccessibilityEventFilter() { 226 // check the received event 227 @Override 228 public boolean accept(AccessibilityEvent event) { 229 return equalsAccessiblityEvent(event, expected); 230 } 231 }, 232 DEFAULT_TIMEOUT_MS); 233 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 234 } 235 236 @MediumTest 237 @Presubmit 238 @Test testTypeViewLongClickedAccessibilityEvent()239 public void testTypeViewLongClickedAccessibilityEvent() throws Throwable { 240 // create and populate the expected event 241 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 242 expected.setEventType(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 243 expected.setClassName(Button.class.getName()); 244 expected.setPackageName(mActivity.getPackageName()); 245 expected.getText().add(mActivity.getString(R.string.button_title)); 246 expected.setEnabled(true); 247 248 final Button button = (Button) mActivity.findViewById(R.id.button); 249 250 AccessibilityEvent awaitedEvent = 251 sUiAutomation.executeAndWaitForEvent( 252 new Runnable() { 253 @Override 254 public void run() { 255 // trigger the event 256 mActivity.runOnUiThread(new Runnable() { 257 @Override 258 public void run() { 259 button.performLongClick(); 260 } 261 }); 262 }}, 263 new UiAutomation.AccessibilityEventFilter() { 264 // check the received event 265 @Override 266 public boolean accept(AccessibilityEvent event) { 267 return equalsAccessiblityEvent(event, expected); 268 } 269 }, 270 DEFAULT_TIMEOUT_MS); 271 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 272 } 273 274 @MediumTest 275 @Presubmit 276 @Test testTypeViewFocusedAccessibilityEvent()277 public void testTypeViewFocusedAccessibilityEvent() throws Throwable { 278 // create and populate the expected event 279 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 280 expected.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED); 281 expected.setClassName(Button.class.getName()); 282 expected.setPackageName(mActivity.getPackageName()); 283 expected.getText().add(mActivity.getString(R.string.button_title)); 284 expected.setItemCount(5); 285 expected.setCurrentItemIndex(3); 286 expected.setEnabled(true); 287 288 final Button button = (Button) mActivity.findViewById(R.id.buttonWithTooltip); 289 290 AccessibilityEvent awaitedEvent = 291 sUiAutomation.executeAndWaitForEvent( 292 () -> mActivity.runOnUiThread(() -> button.requestFocus()), 293 (event) -> equalsAccessiblityEvent(event, expected), 294 DEFAULT_TIMEOUT_MS); 295 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 296 } 297 298 @MediumTest 299 @Presubmit 300 @Test testTypeViewTextChangedAccessibilityEvent()301 public void testTypeViewTextChangedAccessibilityEvent() throws Throwable { 302 // focus the edit text 303 final EditText editText = (EditText) mActivity.findViewById(R.id.edittext); 304 305 AccessibilityEvent awaitedFocusEvent = 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 editText.requestFocus(); 315 } 316 }); 317 }}, 318 new UiAutomation.AccessibilityEventFilter() { 319 // check the received event 320 @Override 321 public boolean accept(AccessibilityEvent event) { 322 return event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED; 323 } 324 }, 325 DEFAULT_TIMEOUT_MS); 326 assertNotNull("Did not receive expected focuss event.", awaitedFocusEvent); 327 328 final String beforeText = mActivity.getString(R.string.text_input_blah); 329 final String newText = mActivity.getString(R.string.text_input_blah_blah); 330 final String afterText = beforeText.substring(0, 3) + newText; 331 332 // create and populate the expected event 333 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 334 expected.setEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 335 expected.setClassName(EditText.class.getName()); 336 expected.setPackageName(mActivity.getPackageName()); 337 expected.getText().add(afterText); 338 expected.setBeforeText(beforeText); 339 expected.setFromIndex(3); 340 expected.setAddedCount(9); 341 expected.setRemovedCount(1); 342 expected.setEnabled(true); 343 344 AccessibilityEvent awaitedTextChangeEvent = 345 sUiAutomation.executeAndWaitForEvent( 346 new Runnable() { 347 @Override 348 public void run() { 349 // trigger the event 350 mActivity.runOnUiThread(new Runnable() { 351 @Override 352 public void run() { 353 editText.getEditableText().replace(3, 4, newText); 354 } 355 }); 356 }}, 357 new UiAutomation.AccessibilityEventFilter() { 358 // check the received event 359 @Override 360 public boolean accept(AccessibilityEvent event) { 361 return equalsAccessiblityEvent(event, expected); 362 } 363 }, 364 DEFAULT_TIMEOUT_MS); 365 assertNotNull("Did not receive expected event: " + expected, awaitedTextChangeEvent); 366 } 367 368 @MediumTest 369 @Presubmit 370 @Test testTypeWindowStateChangedAccessibilityEvent()371 public void testTypeWindowStateChangedAccessibilityEvent() throws Throwable { 372 // create and populate the expected event 373 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 374 expected.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 375 expected.setClassName(AlertDialog.class.getName()); 376 expected.setPackageName(mActivity.getPackageName()); 377 expected.getText().add(mActivity.getString(R.string.alert_title)); 378 expected.getText().add(mActivity.getString(R.string.alert_message)); 379 expected.setEnabled(true); 380 381 AccessibilityEvent awaitedEvent = 382 sUiAutomation.executeAndWaitForEvent( 383 new Runnable() { 384 @Override 385 public void run() { 386 // trigger the event 387 mActivity.runOnUiThread(new Runnable() { 388 @Override 389 public void run() { 390 (new AlertDialog.Builder(mActivity).setTitle(R.string.alert_title) 391 .setMessage(R.string.alert_message)).create().show(); 392 } 393 }); 394 }}, 395 new UiAutomation.AccessibilityEventFilter() { 396 // check the received event 397 @Override 398 public boolean accept(AccessibilityEvent event) { 399 return equalsAccessiblityEvent(event, expected); 400 } 401 }, 402 DEFAULT_TIMEOUT_MS); 403 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 404 } 405 406 @MediumTest 407 @AppModeFull 408 @SuppressWarnings("deprecation") 409 @Presubmit 410 @Test testTypeNotificationStateChangedAccessibilityEvent()411 public void testTypeNotificationStateChangedAccessibilityEvent() throws Throwable { 412 // No notification UI on televisions. 413 if ((mActivity.getResources().getConfiguration().uiMode 414 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) { 415 Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" + 416 " - No notification UI on televisions."); 417 return; 418 } 419 PackageManager pm = sInstrumentation.getTargetContext().getPackageManager(); 420 if (pm.hasSystemFeature(pm.FEATURE_WATCH)) { 421 Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" + 422 " - Watches have different notification system."); 423 return; 424 } 425 if (pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE)) { 426 Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" + 427 " - Automotive handle notifications differently."); 428 return; 429 } 430 431 String message = mActivity.getString(R.string.notification_message); 432 433 final NotificationManager notificationManager = 434 (NotificationManager) mActivity.getSystemService(Service.NOTIFICATION_SERVICE); 435 final NotificationChannel channel = 436 new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); 437 try { 438 // create the notification to send 439 channel.enableVibration(true); 440 channel.enableLights(true); 441 channel.setBypassDnd(true); 442 notificationManager.createNotificationChannel(channel); 443 NotificationChannel created = 444 notificationManager.getNotificationChannel(channel.getId()); 445 final int notificationId = 1; 446 final Notification notification = 447 new Notification.Builder(mActivity, channel.getId()) 448 .setSmallIcon(android.R.drawable.stat_notify_call_mute) 449 .setContentIntent(PendingIntent.getActivity(mActivity, 0, 450 new Intent(), 451 PendingIntent.FLAG_CANCEL_CURRENT)) 452 .setTicker(message) 453 .setContentTitle("") 454 .setContentText("") 455 .setPriority(Notification.PRIORITY_MAX) 456 // Mark the notification as "interruptive" by specifying a vibration 457 // pattern. This ensures it's announced properly on watch-type devices. 458 .setVibrate(new long[]{}) 459 .build(); 460 461 // create and populate the expected event 462 final AccessibilityEvent expected = AccessibilityEvent.obtain(); 463 expected.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); 464 expected.setClassName(Notification.class.getName()); 465 expected.setPackageName(mActivity.getPackageName()); 466 expected.getText().add(message); 467 expected.setParcelableData(notification); 468 469 AccessibilityEvent awaitedEvent = 470 sUiAutomation.executeAndWaitForEvent( 471 new Runnable() { 472 @Override 473 public void run() { 474 // trigger the event 475 mActivity.runOnUiThread(new Runnable() { 476 @Override 477 public void run() { 478 // trigger the event 479 notificationManager 480 .notify(notificationId, notification); 481 mActivity.finish(); 482 } 483 }); 484 } 485 }, 486 new UiAutomation.AccessibilityEventFilter() { 487 // check the received event 488 @Override 489 public boolean accept(AccessibilityEvent event) { 490 return equalsAccessiblityEvent(event, expected); 491 } 492 }, 493 DEFAULT_TIMEOUT_MS); 494 assertNotNull("Did not receive expected event: " + expected, awaitedEvent); 495 } finally { 496 notificationManager.deleteNotificationChannel(channel.getId()); 497 } 498 } 499 500 @MediumTest 501 @Test testInterrupt_notifiesService()502 public void testInterrupt_notifiesService() { 503 sInstrumentation 504 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 505 InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService( 506 sInstrumentation, InstrumentedAccessibilityService.class); 507 try { 508 assertFalse(service.wasOnInterruptCalled()); 509 510 mActivity.runOnUiThread(() -> { 511 AccessibilityManager accessibilityManager = (AccessibilityManager) mActivity 512 .getSystemService(Service.ACCESSIBILITY_SERVICE); 513 accessibilityManager.interrupt(); 514 }); 515 516 Object waitObject = service.getInterruptWaitObject(); 517 synchronized (waitObject) { 518 if (!service.wasOnInterruptCalled()) { 519 try { 520 waitObject.wait(DEFAULT_TIMEOUT_MS); 521 } catch (InterruptedException e) { 522 // Do nothing 523 } 524 } 525 } 526 assertTrue(service.wasOnInterruptCalled()); 527 } finally { 528 service.disableSelfAndRemove(); 529 } 530 } 531 532 @MediumTest 533 @Test testPackageNameCannotBeFaked()534 public void testPackageNameCannotBeFaked() throws Exception { 535 mActivity.runOnUiThread(() -> { 536 // Set the activity to report fake package for events and nodes 537 mActivity.setReportedPackageName("foo.bar.baz"); 538 539 // Make sure node package cannot be faked 540 AccessibilityNodeInfo root = sUiAutomation 541 .getRootInActiveWindow(); 542 assertPackageName(root, mActivity.getPackageName()); 543 }); 544 545 // Make sure event package cannot be faked 546 try { 547 sUiAutomation.executeAndWaitForEvent(() -> 548 sInstrumentation.runOnMainSync(() -> 549 mActivity.findViewById(R.id.button).requestFocus()) 550 , (AccessibilityEvent event) -> 551 event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED 552 && event.getPackageName().equals(mActivity.getPackageName()) 553 , DEFAULT_TIMEOUT_MS); 554 } catch (TimeoutException e) { 555 fail("Events from fake package should be fixed to use the correct package"); 556 } 557 } 558 559 @AppModeFull 560 @MediumTest 561 @Presubmit 562 @Test testPackageNameCannotBeFakedAppWidget()563 public void testPackageNameCannotBeFakedAppWidget() throws Exception { 564 if (!hasAppWidgets()) { 565 return; 566 } 567 568 sInstrumentation.runOnMainSync(() -> { 569 // Set the activity to report fake package for events and nodes 570 mActivity.setReportedPackageName(APP_WIDGET_PROVIDER_PACKAGE); 571 572 // Make sure we cannot report nodes as if from the widget package 573 AccessibilityNodeInfo root = sUiAutomation 574 .getRootInActiveWindow(); 575 assertPackageName(root, mActivity.getPackageName()); 576 }); 577 578 // Make sure we cannot send events as if from the widget package 579 try { 580 sUiAutomation.executeAndWaitForEvent(() -> 581 sInstrumentation.runOnMainSync(() -> 582 mActivity.findViewById(R.id.button).requestFocus()) 583 , (AccessibilityEvent event) -> 584 event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED 585 && event.getPackageName().equals(mActivity.getPackageName()) 586 , DEFAULT_TIMEOUT_MS); 587 } catch (TimeoutException e) { 588 fail("Should not be able to send events from a widget package if no widget hosted"); 589 } 590 591 // Create a host and start listening. 592 final AppWidgetHost host = new AppWidgetHost(sInstrumentation.getTargetContext(), 0); 593 host.deleteHost(); 594 host.startListening(); 595 596 // Well, app do not have this permission unless explicitly granted 597 // by the user. Now we will pretend for the user and grant it. 598 grantBindAppWidgetPermission(); 599 600 // Allocate an app widget id to bind. 601 final int appWidgetId = host.allocateAppWidgetId(); 602 try { 603 // Grab a provider we defined to be bound. 604 final AppWidgetProviderInfo provider = getAppWidgetProviderInfo(); 605 606 // Bind the widget. 607 final boolean widgetBound = getAppWidgetManager().bindAppWidgetIdIfAllowed( 608 appWidgetId, provider.getProfile(), provider.provider, null); 609 assertTrue(widgetBound); 610 611 // Make sure the app can use the package of a widget it hosts 612 sInstrumentation.runOnMainSync(() -> { 613 // Make sure we can report nodes as if from the widget package 614 AccessibilityNodeInfo root = sUiAutomation 615 .getRootInActiveWindow(); 616 assertPackageName(root, APP_WIDGET_PROVIDER_PACKAGE); 617 }); 618 619 // Make sure we can send events as if from the widget package 620 try { 621 sUiAutomation.executeAndWaitForEvent(() -> 622 sInstrumentation.runOnMainSync(() -> 623 mActivity.findViewById(R.id.button).performClick()) 624 , (AccessibilityEvent event) -> 625 event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED 626 && event.getPackageName().equals(APP_WIDGET_PROVIDER_PACKAGE) 627 , DEFAULT_TIMEOUT_MS); 628 } catch (TimeoutException e) { 629 fail("Should be able to send events from a widget package if widget hosted"); 630 } 631 } finally { 632 // Clean up. 633 host.deleteAppWidgetId(appWidgetId); 634 host.deleteHost(); 635 revokeBindAppWidgetPermission(); 636 } 637 } 638 639 @MediumTest 640 @Presubmit 641 @Test testViewHeadingReportedToAccessibility()642 public void testViewHeadingReportedToAccessibility() throws Exception { 643 final EditText editText = (EditText) getOnMain(sInstrumentation, () -> { 644 return mActivity.findViewById(R.id.edittext); 645 }); 646 // Make sure the edittext was populated properly from xml 647 final boolean editTextIsHeading = getOnMain(sInstrumentation, () -> { 648 return editText.isAccessibilityHeading(); 649 }); 650 assertTrue("isAccessibilityHeading not populated properly from xml", editTextIsHeading); 651 652 final AccessibilityNodeInfo editTextNode = sUiAutomation.getRootInActiveWindow() 653 .findAccessibilityNodeInfosByViewId( 654 "android.accessibilityservice.cts:id/edittext") 655 .get(0); 656 assertTrue("isAccessibilityHeading not reported to accessibility", 657 editTextNode.isHeading()); 658 659 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() -> 660 editText.setAccessibilityHeading(false)), 661 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 662 DEFAULT_TIMEOUT_MS); 663 editTextNode.refresh(); 664 assertFalse("isAccessibilityHeading not reported to accessibility after update", 665 editTextNode.isHeading()); 666 } 667 668 @MediumTest 669 @Presubmit 670 @Test testTooltipTextReportedToAccessibility()671 public void testTooltipTextReportedToAccessibility() { 672 final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow() 673 .findAccessibilityNodeInfosByViewId( 674 "android.accessibilityservice.cts:id/buttonWithTooltip") 675 .get(0); 676 assertEquals("Tooltip text not reported to accessibility", 677 sInstrumentation.getContext().getString(R.string.button_tooltip), 678 buttonNode.getTooltipText()); 679 } 680 681 @MediumTest 682 @Test testTooltipTextActionsReportedToAccessibility()683 public void testTooltipTextActionsReportedToAccessibility() throws Exception { 684 final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow() 685 .findAccessibilityNodeInfosByViewId( 686 "android.accessibilityservice.cts:id/buttonWithTooltip") 687 .get(0); 688 assertFalse(hasTooltipShowing(R.id.buttonWithTooltip)); 689 assertThat(ACTION_SHOW_TOOLTIP, in(buttonNode.getActionList())); 690 assertThat(ACTION_HIDE_TOOLTIP, not(in(buttonNode.getActionList()))); 691 sUiAutomation.executeAndWaitForEvent(() -> buttonNode.performAction( 692 ACTION_SHOW_TOOLTIP.getId()), 693 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 694 DEFAULT_TIMEOUT_MS); 695 696 // The button should now be showing the tooltip, so it should have the option to hide it. 697 buttonNode.refresh(); 698 assertThat(ACTION_HIDE_TOOLTIP, in(buttonNode.getActionList())); 699 assertThat(ACTION_SHOW_TOOLTIP, not(in(buttonNode.getActionList()))); 700 assertTrue(hasTooltipShowing(R.id.buttonWithTooltip)); 701 } 702 703 @MediumTest 704 @Test testTraversalBeforeReportedToAccessibility()705 public void testTraversalBeforeReportedToAccessibility() throws Exception { 706 final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow() 707 .findAccessibilityNodeInfosByViewId( 708 "android.accessibilityservice.cts:id/buttonWithTooltip") 709 .get(0); 710 final AccessibilityNodeInfo beforeNode = buttonNode.getTraversalBefore(); 711 assertThat(beforeNode, notNullValue()); 712 assertThat(beforeNode.getViewIdResourceName(), 713 equalTo("android.accessibilityservice.cts:id/edittext")); 714 715 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync( 716 () -> mActivity.findViewById(R.id.buttonWithTooltip) 717 .setAccessibilityTraversalBefore(View.NO_ID)), 718 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 719 DEFAULT_TIMEOUT_MS); 720 721 buttonNode.refresh(); 722 assertThat(buttonNode.getTraversalBefore(), nullValue()); 723 } 724 725 @MediumTest 726 @Test testTraversalAfterReportedToAccessibility()727 public void testTraversalAfterReportedToAccessibility() throws Exception { 728 final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow() 729 .findAccessibilityNodeInfosByViewId( 730 "android.accessibilityservice.cts:id/edittext") 731 .get(0); 732 final AccessibilityNodeInfo afterNode = editNode.getTraversalAfter(); 733 assertThat(afterNode, notNullValue()); 734 assertThat(afterNode.getViewIdResourceName(), 735 equalTo("android.accessibilityservice.cts:id/buttonWithTooltip")); 736 737 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync( 738 () -> mActivity.findViewById(R.id.edittext) 739 .setAccessibilityTraversalAfter(View.NO_ID)), 740 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 741 DEFAULT_TIMEOUT_MS); 742 743 editNode.refresh(); 744 assertThat(editNode.getTraversalAfter(), nullValue()); 745 } 746 747 @MediumTest 748 @Test testLabelForReportedToAccessibility()749 public void testLabelForReportedToAccessibility() throws Exception { 750 sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() -> mActivity 751 .findViewById(R.id.edittext).setLabelFor(R.id.buttonWithTooltip)), 752 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), 753 DEFAULT_TIMEOUT_MS); 754 // TODO: b/78022650: This code should move above the executeAndWait event. It's here because 755 // the a11y cache doesn't get notified when labelFor changes, so the node with the 756 // labledBy isn't updated. 757 final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow() 758 .findAccessibilityNodeInfosByViewId( 759 "android.accessibilityservice.cts:id/edittext") 760 .get(0); 761 editNode.refresh(); 762 final AccessibilityNodeInfo labelForNode = editNode.getLabelFor(); 763 assertThat(labelForNode, notNullValue()); 764 // Labeled node should indicate that it is labeled by the other one 765 assertThat(labelForNode.getLabeledBy(), equalTo(editNode)); 766 } 767 768 @MediumTest 769 @Test testA11yActionTriggerMotionEventActionOutside()770 public void testA11yActionTriggerMotionEventActionOutside() throws Exception { 771 final View.OnTouchListener listener = mock(View.OnTouchListener.class); 772 final AccessibilityNodeInfo button = sUiAutomation.getRootInActiveWindow() 773 .findAccessibilityNodeInfosByViewId( 774 "android.accessibilityservice.cts:id/button") 775 .get(0); 776 final String title = sInstrumentation.getContext().getString(R.string.alert_title); 777 778 // Add a dialog that is watching outside touch 779 sUiAutomation.executeAndWaitForEvent( 780 () -> sInstrumentation.runOnMainSync(() -> { 781 final AlertDialog dialog = new AlertDialog.Builder(mActivity) 782 .setTitle(R.string.alert_title) 783 .setMessage(R.string.alert_message) 784 .create(); 785 final Window window = dialog.getWindow(); 786 window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 787 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); 788 window.getDecorView().setOnTouchListener(listener); 789 window.setTitle(title); 790 dialog.show(); 791 }), 792 (event) -> { 793 // Ensure the dialog is shown over the activity 794 final AccessibilityWindowInfo dialog = findWindowByTitle( 795 sUiAutomation, title); 796 final AccessibilityWindowInfo activity = findWindowByTitle( 797 sUiAutomation, getActivityTitle(sInstrumentation, mActivity)); 798 return (dialog != null && activity != null) 799 && (dialog.getLayer() > activity.getLayer()); 800 }, DEFAULT_TIMEOUT_MS); 801 802 // Perform an action and wait for an event 803 sUiAutomation.executeAndWaitForEvent( 804 () -> button.performAction(AccessibilityNodeInfo.ACTION_CLICK), 805 filterForEventType(AccessibilityEvent.TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS); 806 807 // Make sure the MotionEvent.ACTION_OUTSIDE is received. 808 verify(listener, timeout(DEFAULT_TIMEOUT_MS).atLeastOnce()).onTouch(any(View.class), 809 argThat(event -> event.getActionMasked() == MotionEvent.ACTION_OUTSIDE)); 810 } 811 812 @MediumTest 813 @Test testTouchDelegateInfoReportedToAccessibility()814 public void testTouchDelegateInfoReportedToAccessibility() { 815 final Button button = getOnMain(sInstrumentation, () -> mActivity.findViewById( 816 R.id.button)); 817 final View parent = (View) button.getParent(); 818 final Rect rect = new Rect(); 819 button.getHitRect(rect); 820 parent.setTouchDelegate(new TouchDelegate(rect, button)); 821 822 final AccessibilityNodeInfo nodeInfo = sUiAutomation.getRootInActiveWindow() 823 .findAccessibilityNodeInfosByViewId( 824 "android.accessibilityservice.cts:id/buttonLayout") 825 .get(0); 826 AccessibilityNodeInfo.TouchDelegateInfo targetMapInfo = 827 nodeInfo.getTouchDelegateInfo(); 828 assertNotNull("Did not receive TouchDelegate target map", targetMapInfo); 829 assertEquals("Incorrect target map size", 1, targetMapInfo.getRegionCount()); 830 assertEquals("Incorrect target map region", new Region(rect), 831 targetMapInfo.getRegionAt(0)); 832 final AccessibilityNodeInfo node = targetMapInfo.getTargetForRegion( 833 targetMapInfo.getRegionAt(0)); 834 assertEquals("Incorrect target map view", 835 "android.accessibilityservice.cts:id/button", 836 node.getViewIdResourceName()); 837 node.recycle(); 838 } 839 840 @MediumTest 841 @Test testTouchDelegateWithEbtBetweenView_ReHoverDelegate_FocusTargetAgain()842 public void testTouchDelegateWithEbtBetweenView_ReHoverDelegate_FocusTargetAgain() 843 throws Throwable { 844 mActivity.waitForEnterAnimationComplete(); 845 846 final Resources resources = sInstrumentation.getTargetContext().getResources(); 847 final String buttonResourceName = resources.getResourceName(R.id.button); 848 final Button button = mActivity.findViewById(R.id.button); 849 final int[] buttonLocation = new int[2]; 850 button.getLocationOnScreen(buttonLocation); 851 final int buttonX = button.getWidth() / 2; 852 final int buttonY = button.getHeight() / 2; 853 final int hoverY = buttonLocation[1] + buttonY; 854 final Button buttonWithTooltip = mActivity.findViewById(R.id.buttonWithTooltip); 855 final int touchableSize = 48; 856 final int hoverRight = buttonWithTooltip.getLeft() + touchableSize / 2; 857 final int hoverLeft = button.getRight() + touchableSize / 2; 858 final int hoverMiddle = (hoverLeft + hoverRight) / 2; 859 final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(button, false); 860 enableTouchExploration(sInstrumentation, true); 861 862 try { 863 // common downTime for touch explorer injected events 864 final long downTime = SystemClock.uptimeMillis(); 865 // hover through delegate, parent, 2nd view, parent and delegate again 866 sUiAutomation.executeAndWaitForEvent( 867 () -> injectHoverEvent(downTime, false, hoverLeft, hoverY), 868 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 869 buttonResourceName), DEFAULT_TIMEOUT_MS); 870 assertTrue(button.isHovered()); 871 sUiAutomation.executeAndWaitForEvent( 872 () -> { 873 injectHoverEvent(downTime, true, hoverMiddle, hoverY); 874 injectHoverEvent(downTime, true, hoverRight, hoverY); 875 injectHoverEvent(downTime, true, hoverMiddle, hoverY); 876 injectHoverEvent(downTime, true, hoverLeft, hoverY); 877 }, 878 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 879 buttonResourceName), DEFAULT_TIMEOUT_MS); 880 // delegate target has a11y focus again 881 assertTrue(button.isHovered()); 882 883 CtsMouseUtil.clearHoverListener(button); 884 View.OnHoverListener verifier = inOrder(listener).verify(listener); 885 verifier.onHover(eq(button), 886 matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY)); 887 verifier.onHover(eq(button), 888 matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY)); 889 verifier.onHover(eq(button), 890 matchHover(MotionEvent.ACTION_HOVER_MOVE, hoverMiddle, buttonY)); 891 verifier.onHover(eq(button), 892 matchHover(MotionEvent.ACTION_HOVER_EXIT, buttonX, buttonY)); 893 verifier.onHover(eq(button), 894 matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY)); 895 verifier.onHover(eq(button), 896 matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY)); 897 } catch (TimeoutException e) { 898 fail("Accessibility events should be received as expected " + e.getMessage()); 899 } finally { 900 enableTouchExploration(sInstrumentation, false); 901 } 902 } 903 904 @MediumTest 905 @Test testTouchDelegateCoverParentWithEbt_HoverChildAndBack_FocusTargetAgain()906 public void testTouchDelegateCoverParentWithEbt_HoverChildAndBack_FocusTargetAgain() 907 throws Throwable { 908 mActivity.waitForEnterAnimationComplete(); 909 910 final int touchableSize = 48; 911 final Resources resources = sInstrumentation.getTargetContext().getResources(); 912 final String targetResourceName = resources.getResourceName(R.id.buttonDelegated); 913 final View textView = mActivity.findViewById(R.id.delegateText); 914 final Button target = mActivity.findViewById(R.id.buttonDelegated); 915 int[] location = new int[2]; 916 textView.getLocationOnScreen(location); 917 final int textX = location[0] + touchableSize/2; 918 final int textY = location[1] + textView.getHeight() / 2; 919 final int delegateX = location[0] - touchableSize/2; 920 final int targetX = target.getWidth() / 2; 921 final int targetY = target.getHeight() / 2; 922 final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(target, false); 923 enableTouchExploration(sInstrumentation, true); 924 925 try { 926 final long downTime = SystemClock.uptimeMillis(); 927 // Like switch bar, it has a text view, a button and a delegate covers parent layout. 928 // hover the delegate, text and delegate again. 929 sUiAutomation.executeAndWaitForEvent( 930 () -> injectHoverEvent(downTime, false, delegateX, textY), 931 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 932 targetResourceName), DEFAULT_TIMEOUT_MS); 933 assertTrue(target.isHovered()); 934 sUiAutomation.executeAndWaitForEvent( 935 () -> injectHoverEvent(downTime, true, textX, textY), 936 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, 937 targetResourceName), DEFAULT_TIMEOUT_MS); 938 sUiAutomation.executeAndWaitForEvent( 939 () -> injectHoverEvent(downTime, true, delegateX, textY), 940 filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, 941 targetResourceName), DEFAULT_TIMEOUT_MS); 942 assertTrue(target.isHovered()); 943 944 CtsMouseUtil.clearHoverListener(target); 945 View.OnHoverListener verifier = inOrder(listener).verify(listener); 946 verifier.onHover(eq(target), 947 matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY)); 948 verifier.onHover(eq(target), 949 matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY)); 950 verifier.onHover(eq(target), 951 matchHover(MotionEvent.ACTION_HOVER_MOVE, textX, textY)); 952 verifier.onHover(eq(target), 953 matchHover(MotionEvent.ACTION_HOVER_EXIT, targetX, targetY)); 954 verifier.onHover(eq(target), 955 matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY)); 956 verifier.onHover(eq(target), 957 matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY)); 958 } catch (TimeoutException e) { 959 fail("Accessibility events should be received as expected " + e.getMessage()); 960 } finally { 961 enableTouchExploration(sInstrumentation, false); 962 } 963 } 964 assertPackageName(AccessibilityNodeInfo node, String packageName)965 private static void assertPackageName(AccessibilityNodeInfo node, String packageName) { 966 if (node == null) { 967 return; 968 } 969 assertEquals(packageName, node.getPackageName()); 970 final int childCount = node.getChildCount(); 971 for (int i = 0; i < childCount; i++) { 972 AccessibilityNodeInfo child = node.getChild(i); 973 if (child != null) { 974 assertPackageName(child, packageName); 975 } 976 } 977 } 978 enableTouchExploration(Instrumentation instrumentation, boolean enabled)979 private static void enableTouchExploration(Instrumentation instrumentation, boolean enabled) 980 throws InterruptedException { 981 final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s 982 final Object waitObject = new Object(); 983 final AtomicBoolean atomicBoolean = new AtomicBoolean(!enabled); 984 AccessibilityManager.TouchExplorationStateChangeListener serviceListener = (boolean b) -> { 985 synchronized (waitObject) { 986 atomicBoolean.set(b); 987 waitObject.notifyAll(); 988 } 989 }; 990 final AccessibilityManager manager = 991 (AccessibilityManager) instrumentation.getContext().getSystemService( 992 Service.ACCESSIBILITY_SERVICE); 993 manager.addTouchExplorationStateChangeListener(serviceListener); 994 995 final UiAutomation uiAutomation = instrumentation.getUiAutomation(); 996 final AccessibilityServiceInfo info = uiAutomation.getServiceInfo(); 997 assert info != null; 998 if (enabled) { 999 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1000 } else { 1001 info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1002 } 1003 uiAutomation.setServiceInfo(info); 1004 1005 final long timeoutTime = System.currentTimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE; 1006 synchronized (waitObject) { 1007 while ((enabled != atomicBoolean.get()) && (System.currentTimeMillis() < timeoutTime)) { 1008 waitObject.wait(timeoutTime - System.currentTimeMillis()); 1009 } 1010 } 1011 if (enabled) { 1012 assertTrue("Touch exploration state listener not called when services enabled", 1013 atomicBoolean.get()); 1014 assertTrue("Timed out enabling accessibility", 1015 manager.isEnabled() && manager.isTouchExplorationEnabled()); 1016 } else { 1017 assertFalse("Touch exploration state listener not called when services disabled", 1018 atomicBoolean.get()); 1019 assertFalse("Timed out disabling accessibility", 1020 manager.isEnabled() && manager.isTouchExplorationEnabled()); 1021 } 1022 manager.removeTouchExplorationStateChangeListener(serviceListener); 1023 } 1024 matchHover(int action, int x, int y)1025 private static MotionEvent matchHover(int action, int x, int y) { 1026 return argThat(new CtsMouseUtil.PositionMatcher(action, x, y)); 1027 } 1028 injectHoverEvent(long downTime, boolean isFirstHoverEvent, int xOnScreen, int yOnScreen)1029 private static void injectHoverEvent(long downTime, boolean isFirstHoverEvent, 1030 int xOnScreen, int yOnScreen) { 1031 final long eventTime = isFirstHoverEvent ? SystemClock.uptimeMillis() : downTime; 1032 MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_HOVER_MOVE, 1033 xOnScreen, yOnScreen, 0); 1034 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 1035 sUiAutomation.injectInputEvent(event, true); 1036 event.recycle(); 1037 } 1038 getAppWidgetProviderInfo()1039 private AppWidgetProviderInfo getAppWidgetProviderInfo() { 1040 final ComponentName componentName = new ComponentName( 1041 "foo.bar.baz", "foo.bar.baz.MyAppWidgetProvider"); 1042 final List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders(); 1043 final int providerCount = providers.size(); 1044 for (int i = 0; i < providerCount; i++) { 1045 final AppWidgetProviderInfo provider = providers.get(i); 1046 if (componentName.equals(provider.provider) 1047 && Process.myUserHandle().equals(provider.getProfile())) { 1048 return provider; 1049 } 1050 } 1051 return null; 1052 } 1053 grantBindAppWidgetPermission()1054 private void grantBindAppWidgetPermission() throws Exception { 1055 ShellCommandBuilder.execShellCommand(sUiAutomation, 1056 GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND + getCurrentUser()); 1057 } 1058 revokeBindAppWidgetPermission()1059 private void revokeBindAppWidgetPermission() throws Exception { 1060 ShellCommandBuilder.execShellCommand(sUiAutomation, 1061 REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND + getCurrentUser()); 1062 } 1063 getAppWidgetManager()1064 private AppWidgetManager getAppWidgetManager() { 1065 return (AppWidgetManager) sInstrumentation.getTargetContext() 1066 .getSystemService(Context.APPWIDGET_SERVICE); 1067 } 1068 hasAppWidgets()1069 private boolean hasAppWidgets() { 1070 return sInstrumentation.getTargetContext().getPackageManager() 1071 .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS); 1072 } 1073 1074 /** 1075 * Compares all properties of the <code>first</code> and the 1076 * <code>second</code>. 1077 */ equalsAccessiblityEvent(AccessibilityEvent first, AccessibilityEvent second)1078 private boolean equalsAccessiblityEvent(AccessibilityEvent first, AccessibilityEvent second) { 1079 return first.getEventType() == second.getEventType() 1080 && first.isChecked() == second.isChecked() 1081 && first.getCurrentItemIndex() == second.getCurrentItemIndex() 1082 && first.isEnabled() == second.isEnabled() 1083 && first.getFromIndex() == second.getFromIndex() 1084 && first.getItemCount() == second.getItemCount() 1085 && first.isPassword() == second.isPassword() 1086 && first.getRemovedCount() == second.getRemovedCount() 1087 && first.isScrollable()== second.isScrollable() 1088 && first.getToIndex() == second.getToIndex() 1089 && first.getRecordCount() == second.getRecordCount() 1090 && first.getScrollX() == second.getScrollX() 1091 && first.getScrollY() == second.getScrollY() 1092 && first.getAddedCount() == second.getAddedCount() 1093 && TextUtils.equals(first.getBeforeText(), second.getBeforeText()) 1094 && TextUtils.equals(first.getClassName(), second.getClassName()) 1095 && TextUtils.equals(first.getContentDescription(), second.getContentDescription()) 1096 && equalsNotificationAsParcelableData(first, second) 1097 && equalsText(first, second); 1098 } 1099 1100 /** 1101 * Compares the {@link android.os.Parcelable} data of the 1102 * <code>first</code> and <code>second</code>. 1103 */ equalsNotificationAsParcelableData(AccessibilityEvent first, AccessibilityEvent second)1104 private boolean equalsNotificationAsParcelableData(AccessibilityEvent first, 1105 AccessibilityEvent second) { 1106 Notification firstNotification = (Notification) first.getParcelableData(); 1107 Notification secondNotification = (Notification) second.getParcelableData(); 1108 if (firstNotification == null) { 1109 return (secondNotification == null); 1110 } else if (secondNotification == null) { 1111 return false; 1112 } 1113 return TextUtils.equals(firstNotification.tickerText, secondNotification.tickerText); 1114 } 1115 1116 /** 1117 * Compares the text of the <code>first</code> and <code>second</code> text. 1118 */ equalsText(AccessibilityEvent first, AccessibilityEvent second)1119 private boolean equalsText(AccessibilityEvent first, AccessibilityEvent second) { 1120 List<CharSequence> firstText = first.getText(); 1121 List<CharSequence> secondText = second.getText(); 1122 if (firstText.size() != secondText.size()) { 1123 return false; 1124 } 1125 Iterator<CharSequence> firstIterator = firstText.iterator(); 1126 Iterator<CharSequence> secondIterator = secondText.iterator(); 1127 for (int i = 0; i < firstText.size(); i++) { 1128 if (!firstIterator.next().toString().equals(secondIterator.next().toString())) { 1129 return false; 1130 } 1131 } 1132 return true; 1133 } 1134 hasTooltipShowing(int id)1135 private boolean hasTooltipShowing(int id) { 1136 return getOnMain(sInstrumentation, () -> { 1137 final View viewWithTooltip = mActivity.findViewById(id); 1138 if (viewWithTooltip == null) { 1139 return false; 1140 } 1141 final View tooltipView = viewWithTooltip.getTooltipView(); 1142 return (tooltipView != null) && (tooltipView.getParent() != null); 1143 }); 1144 } 1145 getCurrentUser()1146 private static int getCurrentUser() { 1147 return android.os.Process.myUserHandle().getIdentifier(); 1148 } 1149 } 1150