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