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