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