1 /* 2 * Copyright (C) 2022 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.ADD_TRUSTED_DISPLAY; 20 import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE; 21 import static android.Manifest.permission.MANAGE_ACCESSIBILITY; 22 import static android.Manifest.permission.WAKE_LOCK; 23 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; 24 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_AUDIBLE; 25 import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; 26 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType; 27 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWaitForAll; 28 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes; 29 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitleWithList; 30 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle; 31 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen; 32 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen; 33 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.supportsMultiDisplay; 34 import static android.accessibilityservice.cts.utils.AsyncUtils.await; 35 import static android.accessibilityservice.cts.utils.GestureUtils.click; 36 import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture; 37 import static android.accessibilityservice.cts.utils.MultiProcessUtils.ACCESSIBILITY_SERVICE_STATE; 38 import static android.accessibilityservice.cts.utils.MultiProcessUtils.EXTRA_ENABLED; 39 import static android.accessibilityservice.cts.utils.MultiProcessUtils.EXTRA_ENABLED_SERVICES; 40 import static android.accessibilityservice.cts.utils.MultiProcessUtils.SEPARATE_PROCESS_ACTIVITY_TITLE; 41 import static android.accessibilityservice.cts.utils.MultiProcessUtils.TOUCH_EXPLORATION_STATE; 42 import static android.accessibilityservice.cts.utils.WindowCreationUtils.TOP_WINDOW_TITLE; 43 import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES; 44 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; 45 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; 46 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; 47 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED; 48 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; 49 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED; 50 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED; 51 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACTIVE; 52 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED; 53 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_FOCUSED; 54 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; 55 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT; 56 57 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 58 import static com.android.compatibility.common.util.TestUtils.waitOn; 59 60 import static com.google.common.truth.Truth.assertThat; 61 62 import static org.hamcrest.CoreMatchers.allOf; 63 import static org.junit.Assert.assertThrows; 64 import static org.junit.Assume.assumeFalse; 65 import static org.junit.Assume.assumeTrue; 66 67 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule; 68 import android.accessibility.cts.common.InstrumentedAccessibilityService; 69 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule; 70 import android.accessibility.cts.common.ShellCommandBuilder; 71 import android.accessibilityservice.AccessibilityService; 72 import android.accessibilityservice.AccessibilityServiceInfo; 73 import android.accessibilityservice.GestureDescription; 74 import android.accessibilityservice.cts.activities.AccessibilityKeyEventTestActivity; 75 import android.accessibilityservice.cts.activities.NonProxyActivity; 76 import android.accessibilityservice.cts.activities.ProxyDisplayActivity; 77 import android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils; 78 import android.accessibilityservice.cts.utils.DisplayUtils; 79 import android.accessibilityservice.cts.utils.WindowCreationUtils; 80 import android.app.Activity; 81 import android.app.ActivityManager; 82 import android.app.ActivityOptions; 83 import android.app.Instrumentation; 84 import android.app.PendingIntent; 85 import android.app.RemoteAction; 86 import android.app.UiAutomation; 87 import android.app.role.RoleManager; 88 import android.companion.virtual.VirtualDeviceManager; 89 import android.companion.virtual.VirtualDeviceParams; 90 import android.content.BroadcastReceiver; 91 import android.content.Context; 92 import android.content.Intent; 93 import android.content.IntentFilter; 94 import android.content.pm.PackageManager; 95 import android.graphics.Color; 96 import android.graphics.PixelFormat; 97 import android.graphics.PointF; 98 import android.graphics.Rect; 99 import android.graphics.drawable.Icon; 100 import android.hardware.display.VirtualDisplay; 101 import android.media.ImageReader; 102 import android.os.SystemClock; 103 import android.platform.test.annotations.AppModeFull; 104 import android.platform.test.annotations.Presubmit; 105 import android.provider.Settings; 106 import android.util.DisplayMetrics; 107 import android.util.SparseArray; 108 import android.util.TypedValue; 109 import android.view.Display; 110 import android.view.InputDevice; 111 import android.view.KeyEvent; 112 import android.view.MotionEvent; 113 import android.view.View; 114 import android.view.WindowManager; 115 import android.view.accessibility.AccessibilityDisplayProxy; 116 import android.view.accessibility.AccessibilityEvent; 117 import android.view.accessibility.AccessibilityManager; 118 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; 119 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; 120 import android.view.accessibility.AccessibilityNodeInfo; 121 import android.view.accessibility.AccessibilityWindowInfo; 122 import android.virtualdevice.cts.common.FakeAssociationRule; 123 import android.widget.Button; 124 import android.widget.EditText; 125 126 import androidx.annotation.NonNull; 127 import androidx.test.platform.app.InstrumentationRegistry; 128 import androidx.test.rule.ActivityTestRule; 129 import androidx.test.runner.AndroidJUnit4; 130 131 import com.android.compatibility.common.util.ApiTest; 132 import com.android.compatibility.common.util.PollingCheck; 133 import com.android.compatibility.common.util.SystemUtil; 134 135 import org.junit.After; 136 import org.junit.AfterClass; 137 import org.junit.Before; 138 import org.junit.BeforeClass; 139 import org.junit.Rule; 140 import org.junit.Test; 141 import org.junit.rules.RuleChain; 142 import org.junit.runner.RunWith; 143 144 import java.util.ArrayList; 145 import java.util.List; 146 import java.util.concurrent.CompletableFuture; 147 import java.util.concurrent.CountDownLatch; 148 import java.util.concurrent.Executor; 149 import java.util.concurrent.Executors; 150 import java.util.concurrent.TimeUnit; 151 import java.util.concurrent.TimeoutException; 152 import java.util.concurrent.atomic.AtomicBoolean; 153 import java.util.function.Consumer; 154 155 /** Ensure AccessibilityDisplayProxy APIs work. 156 * 157 * <p> 158 * Note: AccessibilityDisplayProxy is in android.view.accessibility since apps take advantage of it. 159 * AccessibilityDisplayProxyTest is in android.accessibilityservice.cts, not 160 * android.view.accessibility.cts, since the proxy behaves likes an a11y service and this package 161 * gives access to a suite of helpful utils for testing service-like behavior. 162 * <p> 163 * This class tests a variety of interactions: 164 * <ul> 165 * <li> When consumers of accessibility data for a display registers or unregisters an 166 * AccessibilityDisplayProxy. They must have the MANAGE_ACCESSIBILITY or 167 * CREATE_VIRTUAL_DEVICE permission. 168 * <li> When AccessibilityDisplayProxy uses its class APIs. 169 * <li> When a service and proxy are active at the same time. An AccessibilityServices should 170 * not have access to a proxy-ed display's data and vice versa. 171 * <li> When a service and proxy are active and want accessibility focus. These should be 172 * separate focuses. 173 * <li> When apps are notified of changes to accessibility state. An app belonging to a 174 * virtual device that has a registered proxy should maintain different a11y state than that on 175 * the phone. There is one AccessibilityManager per process, so to test these notifications we 176 * must have different processes (associated with different devices). So we spin up a separate 177 * app outside of the instrumentation process. This prevents direct access to the app's 178 * components like Activities, so we test state changes via AccessibilityEvents. In the 179 * long-term, we should be able to modify these tests and access the Activities directly. 180 * </ul> 181 * 182 * Note: NonProxyActivity and NonProxySeparateAppActivity both exist because NonProxyActivity 183 * belongs to the instrumentation process, meaning its AccessibilityManager will reflect the same 184 * state as the proxy. Accessibility focus separation tests can use NonProxyActivity since touch 185 * exploration is required for both the proxy and the service. NonProxySeparateAppActivity is 186 * needed to test different AccessibilityManager states. 187 * TODO(271639633): Test a11y is enabled/disabled, potentially focus appearance changes and event 188 * types changes for the non-proxy app. (This will be easier in the long term when a11y managers are 189 * split within processes and we can directly access them here.) 190 */ 191 @RunWith(AndroidJUnit4.class) 192 @AppModeFull(reason = "VirtualDeviceManager cannot be accessed by instant apps") 193 @Presubmit 194 public class AccessibilityDisplayProxyTest { 195 private static final int INVALID_DISPLAY_ID = 10000; 196 private static final int TIMEOUT_MS = 5000; 197 198 private static final int NON_INTERACTIVE_UI_TIMEOUT = 100; 199 private static final int INTERACTIVE_UI_TIMEOUT = 200; 200 private static final int NOTIFICATION_TIMEOUT = 100; 201 private static final String PACKAGE_1 = "package 1"; 202 private static final String PACKAGE_2 = "package 2"; 203 private static final int NON_PROXY_SERVICE_TIMEOUT = 20000; 204 205 private static final String SEPARATE_PROCESS_PACKAGE_NAME = "foo.bar.proxy"; 206 private static final String SEPARATE_PROCESS_ACTIVITY = ".NonProxySeparateAppActivity"; 207 208 private static final float MIN_SCREEN_WIDTH_MM = 40.0f; 209 private static final int TEST_SYSTEM_ACTION_ID = 1000; 210 public static final String INSTRUMENTED_STREAM_ROLE_PACKAGE_NAME = 211 "android.accessibilityservice.cts"; 212 213 private static Instrumentation sInstrumentation; 214 private static UiAutomation sUiAutomation; 215 private static String sEnabledServices; 216 private static RoleManager sRoleManager; 217 // The manager representing the app registering/unregistering the proxy. 218 private AccessibilityManager mA11yManager; 219 220 // This is technically the same manager as mA11yManager, since there is one manager per process, 221 // but add separation for readability. 222 private AccessibilityManager mProxyActivityA11yManager; 223 private MyA11yProxy mA11yProxy; 224 private int mVirtualDisplayId; 225 private VirtualDisplay mVirtualDisplay; 226 private AccessibilityKeyEventTestActivity mProxiedVirtualDisplayActivity; 227 private CharSequence mProxiedVirtualDisplayActivityTitle; 228 229 // Activity used for checking accessibility and input focus behavior. An activity in a separate 230 // process is not required, since touch exploration is enabled for both the proxy and non-proxy 231 // i.e. the AccessibilityManagers have the same state. 232 private AccessibilityKeyEventTestActivity mNonProxiedConcurrentActivity; 233 234 private Intent mSeparateProcessActivityIntent = new Intent(Intent.ACTION_MAIN) 235 .setClassName(SEPARATE_PROCESS_PACKAGE_NAME, 236 SEPARATE_PROCESS_PACKAGE_NAME + SEPARATE_PROCESS_ACTIVITY) 237 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);; 238 239 // Virtual Device variables. 240 private VirtualDeviceManager mVirtualDeviceManager; 241 private VirtualDeviceManager.VirtualDevice mVirtualDevice; 242 243 private ImageReader mImageReader; 244 private static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS = 245 new VirtualDeviceParams.Builder().build(); 246 @Rule 247 public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule(); 248 249 private ListenerChangeBroadcastReceiver mReceiver = 250 new ListenerChangeBroadcastReceiver(); 251 252 private InstrumentedAccessibilityServiceTestRule<StubProxyConcurrentAccessibilityService> 253 mNonProxyServiceRule = new InstrumentedAccessibilityServiceTestRule<>( 254 StubProxyConcurrentAccessibilityService.class, false); 255 256 private final ActivityTestRule<NonProxyActivity> mNonProxyActivityRule = 257 new ActivityTestRule<>(NonProxyActivity.class, false, false); 258 259 private AccessibilityDumpOnFailureRule mDumpOnFailureRule = 260 new AccessibilityDumpOnFailureRule(); 261 262 @Rule 263 public final RuleChain mRuleChain = RuleChain 264 .outerRule(mNonProxyServiceRule) 265 .around(mNonProxyActivityRule) 266 .around(mDumpOnFailureRule); 267 268 @BeforeClass oneTimeSetup()269 public static void oneTimeSetup() { 270 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 271 // Save enabled accessibility services before disabling them so they can be re-enabled after 272 // the test. 273 sEnabledServices = Settings.Secure.getString( 274 sInstrumentation.getContext().getContentResolver(), 275 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 276 // Disable all services before enabling Accessibility service to prevent flakiness 277 // that depends on which services are enabled. 278 InstrumentedAccessibilityService.disableAllServices(); 279 280 sUiAutomation = 281 sInstrumentation.getUiAutomation(FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 282 final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo(); 283 info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; 284 sUiAutomation.setServiceInfo(info); 285 sRoleManager = sInstrumentation.getContext().getSystemService(RoleManager.class); 286 runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(true)); 287 } 288 289 @AfterClass postTestTearDown()290 public static void postTestTearDown() { 291 ShellCommandBuilder.create(sInstrumentation) 292 .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, sEnabledServices) 293 .run(); 294 runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(false)); 295 } 296 297 @Before setUp()298 public void setUp() throws Exception { 299 final Context context = sInstrumentation.getContext(); 300 assumeTrue(supportsMultiDisplay(context)); 301 final PackageManager packageManager = context.getPackageManager(); 302 assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)); 303 // TODO(b/261155110): Re-enable tests once freeform mode is supported in Virtual Display. 304 assumeFalse("Skipping test: VirtualDisplay window policy doesn't support freeform.", 305 packageManager.hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)); 306 mA11yManager = context.getSystemService(AccessibilityManager.class); 307 mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class); 308 mVirtualDisplay = createVirtualDeviceAndLaunchVirtualDisplay(); 309 assertThat(mVirtualDisplay).isNotNull(); 310 mVirtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId(); 311 final List<AccessibilityServiceInfo> infos = new ArrayList<>(); 312 final AccessibilityServiceInfo proxyInfo = new AccessibilityServiceInfo(); 313 proxyInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; 314 proxyInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; 315 infos.add(proxyInfo); 316 mA11yProxy = new MyA11yProxy(mVirtualDisplayId, Executors.newSingleThreadExecutor(), infos); 317 mProxiedVirtualDisplayActivity = launchActivityOnVirtualDisplay( 318 mVirtualDisplay.getDisplay().getDisplayId()); 319 mProxiedVirtualDisplayActivityTitle = getActivityTitle(sInstrumentation, 320 mProxiedVirtualDisplayActivity); 321 mProxyActivityA11yManager = 322 mProxiedVirtualDisplayActivity.getSystemService(AccessibilityManager.class); 323 addAppStreamingRole(); 324 } 325 326 @After tearDown()327 public void tearDown() throws TimeoutException { 328 sUiAutomation.adoptShellPermissionIdentity( 329 MANAGE_ACCESSIBILITY, CREATE_VIRTUAL_DEVICE, WAKE_LOCK); 330 if (mA11yProxy != null) { 331 mA11yManager.unregisterDisplayProxy(mA11yProxy); 332 } 333 if (mProxiedVirtualDisplayActivity != null) { 334 mProxiedVirtualDisplayActivity.runOnUiThread( 335 () -> mProxiedVirtualDisplayActivity.finish()); 336 } 337 if (mVirtualDisplay != null) { 338 mVirtualDisplay.release(); 339 } 340 if (mVirtualDevice != null) { 341 mVirtualDevice.close(); 342 } 343 if (mImageReader != null) { 344 mImageReader.close(); 345 } 346 if (mNonProxiedConcurrentActivity != null) { 347 mNonProxiedConcurrentActivity.runOnUiThread( 348 () -> mNonProxiedConcurrentActivity.finish()); 349 } 350 removeAppStreamingRole(); 351 } 352 353 @Test 354 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterDisplayProxy_withPermission_successfullyRegisters()355 public void testRegisterDisplayProxy_withPermission_successfullyRegisters() { 356 removeAppStreamingRole(); 357 runWithShellPermissionIdentity(sUiAutomation, 358 () -> assertThat(mA11yManager.registerDisplayProxy(mA11yProxy)).isTrue(), 359 CREATE_VIRTUAL_DEVICE, MANAGE_ACCESSIBILITY); 360 } 361 362 @Test 363 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterDisplayProxy_withStreamingRole_successfullyRegisters()364 public void testRegisterDisplayProxy_withStreamingRole_successfullyRegisters() { 365 runWithShellPermissionIdentity(sUiAutomation, 366 () -> assertThat(mA11yManager.registerDisplayProxy(mA11yProxy)).isTrue(), 367 CREATE_VIRTUAL_DEVICE); 368 } 369 370 @Test 371 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterDisplayProxy_withoutPermission_throwsSecurityException()372 public void testRegisterDisplayProxy_withoutPermission_throwsSecurityException() { 373 assertThrows(SecurityException.class, () -> 374 mA11yManager.registerDisplayProxy(mA11yProxy)); 375 } 376 377 @Test 378 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterDisplayProxy_withoutA11yPermissionOrRole_throwsSecurityException()379 public void testRegisterDisplayProxy_withoutA11yPermissionOrRole_throwsSecurityException() { 380 removeAppStreamingRole(); 381 runWithShellPermissionIdentity(sUiAutomation, 382 () -> assertThrows(SecurityException.class, () -> 383 mA11yManager.registerDisplayProxy(mA11yProxy)), CREATE_VIRTUAL_DEVICE); 384 } 385 386 @Test 387 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterDisplayProxy_withoutDevicePermission_throwsSecurityException()388 public void testRegisterDisplayProxy_withoutDevicePermission_throwsSecurityException() { 389 runWithShellPermissionIdentity(sUiAutomation, 390 () -> assertThrows(SecurityException.class, () -> 391 mA11yManager.registerDisplayProxy(mA11yProxy)), MANAGE_ACCESSIBILITY); 392 } 393 394 @Test 395 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterDisplayProxy_alreadyProxied_throwsIllegalArgumentException()396 public void testRegisterDisplayProxy_alreadyProxied_throwsIllegalArgumentException() { 397 runWithShellPermissionIdentity(sUiAutomation, 398 () -> assertThat(mA11yManager.registerDisplayProxy(mA11yProxy)).isTrue()); 399 400 runWithShellPermissionIdentity(sUiAutomation, () -> 401 assertThrows(IllegalArgumentException.class, () -> 402 mA11yManager.registerDisplayProxy(mA11yProxy))); 403 } 404 405 @Test 406 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterAccessibilityProxy_withDefaultDisplay_throwsSecurityException()407 public void testRegisterAccessibilityProxy_withDefaultDisplay_throwsSecurityException() { 408 final MyA11yProxy invalidProxy = new MyA11yProxy( 409 Display.DEFAULT_DISPLAY, Executors.newSingleThreadExecutor(), new ArrayList<>()); 410 try { 411 runWithShellPermissionIdentity(sUiAutomation, () -> 412 assertThrows(SecurityException.class, () -> 413 mA11yManager.registerDisplayProxy(invalidProxy))); 414 } finally { 415 runWithShellPermissionIdentity(sUiAutomation, () -> 416 mA11yManager.unregisterDisplayProxy(invalidProxy)); 417 } 418 } 419 420 @Test 421 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testRegisterAccessibilityProxy_withNonDeviceDisplay_throwsSecurityException()422 public void testRegisterAccessibilityProxy_withNonDeviceDisplay_throwsSecurityException() 423 throws Exception { 424 try (DisplayUtils.VirtualDisplaySession displaySession = 425 new DisplayUtils.VirtualDisplaySession()) { 426 final int virtualDisplayId = 427 displaySession.createDisplayWithDefaultDisplayMetricsAndWait( 428 sInstrumentation.getContext(), false).getDisplayId(); 429 // Launches an activity on virtual display to guarantee the display is tracked by 430 // accessibility. 431 final Activity activityOnVirtualDisplay = 432 launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(sInstrumentation, 433 sUiAutomation, 434 NonProxyActivity.class, 435 virtualDisplayId); 436 437 final MyA11yProxy invalidProxy = new MyA11yProxy( 438 virtualDisplayId, Executors.newSingleThreadExecutor(), new ArrayList<>()); 439 try { 440 runWithShellPermissionIdentity(sUiAutomation, () -> 441 assertThrows(SecurityException.class, () -> 442 mA11yManager.registerDisplayProxy(invalidProxy))); 443 } finally { 444 runWithShellPermissionIdentity(sUiAutomation, () -> 445 mA11yManager.unregisterDisplayProxy(invalidProxy)); 446 if (activityOnVirtualDisplay != null) { 447 activityOnVirtualDisplay.finish(); 448 } 449 } 450 } 451 } 452 453 @Test 454 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#unregisterDisplayProxy"}) testUnregisterDisplayProxy_withoutDevicePermission_throwsSecurityException()455 public void testUnregisterDisplayProxy_withoutDevicePermission_throwsSecurityException() { 456 runWithShellPermissionIdentity(sUiAutomation, 457 () -> assertThrows(SecurityException.class, () -> 458 mA11yManager.unregisterDisplayProxy(mA11yProxy)), MANAGE_ACCESSIBILITY); 459 } 460 461 @Test 462 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#unregisterDisplayProxy"}) testUnregisterDisplayProxy_withoutA11yPermissionOrRole_throwsSecurityException()463 public void testUnregisterDisplayProxy_withoutA11yPermissionOrRole_throwsSecurityException() { 464 removeAppStreamingRole(); 465 runWithShellPermissionIdentity(sUiAutomation, 466 () -> assertThrows(SecurityException.class, () -> 467 mA11yManager.unregisterDisplayProxy(mA11yProxy)), CREATE_VIRTUAL_DEVICE); 468 } 469 470 @Test 471 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#unregisterDisplayProxy"}) testUnregisterDisplayProxy_withPermission_successfullyUnregisters()472 public void testUnregisterDisplayProxy_withPermission_successfullyUnregisters() { 473 removeAppStreamingRole(); 474 runWithShellPermissionIdentity(sUiAutomation, () -> { 475 assertThat(mA11yManager.registerDisplayProxy(mA11yProxy)).isTrue(); 476 assertThat(mA11yManager.unregisterDisplayProxy(mA11yProxy)).isTrue(); 477 }, CREATE_VIRTUAL_DEVICE, MANAGE_ACCESSIBILITY); 478 } 479 480 @Test 481 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#registerDisplayProxy"}) testUnregisterAccessibilityProxy_withStreamingRole_successfullyUnRegisters()482 public void testUnregisterAccessibilityProxy_withStreamingRole_successfullyUnRegisters() { 483 runWithShellPermissionIdentity(sUiAutomation, () -> { 484 assertThat(mA11yManager.registerDisplayProxy(mA11yProxy)).isTrue(); 485 assertThat(mA11yManager.unregisterDisplayProxy(mA11yProxy)).isTrue(); 486 }, CREATE_VIRTUAL_DEVICE); 487 } 488 489 @Test 490 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager#unregisterDisplayProxy"}) testUnregisterDisplayProxy_withPermission_failsToUnregister()491 public void testUnregisterDisplayProxy_withPermission_failsToUnregister() { 492 final MyA11yProxy invalidProxy = new MyA11yProxy( 493 INVALID_DISPLAY_ID, Executors.newSingleThreadExecutor(), new ArrayList<>()); 494 runWithShellPermissionIdentity(sUiAutomation, () -> { 495 assertThat(mA11yManager.unregisterDisplayProxy(invalidProxy)).isFalse(); 496 }); 497 } 498 499 @Test 500 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#getDisplayId"}) testGetDisplayId_always_returnsId()501 public void testGetDisplayId_always_returnsId() { 502 assertThat(mA11yProxy.getDisplayId()).isEqualTo(mVirtualDisplayId); 503 } 504 505 @Test 506 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onAccessibilityEvent"}) testOnAccessibilityEvent_clickButton_proxyReceivesClickEvent()507 public void testOnAccessibilityEvent_clickButton_proxyReceivesClickEvent() { 508 registerProxyAndWaitForConnection(); 509 // Create and populate the expected event 510 AccessibilityEvent clickEvent = getProxyClickAccessibilityEvent(); 511 512 mA11yProxy.setEventFilter(getClickEventFilter(clickEvent)); 513 514 final Button button = mProxiedVirtualDisplayActivity.findViewById(R.id.button); 515 mProxiedVirtualDisplayActivity.runOnUiThread(() -> button.performClick()); 516 517 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mReceivedEvent.get(), TIMEOUT_MS, 518 "Click event received"); 519 } 520 521 @Test 522 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onAccessibilityEvent"}) testPerformSystemAction_keyEventsDispatchedToLastNonProxyDisplay()523 public void testPerformSystemAction_keyEventsDispatchedToLastNonProxyDisplay() 524 throws Exception { 525 final StubProxyConcurrentAccessibilityService service = 526 mNonProxyServiceRule.enableService(); 527 try { 528 registerProxyAndWaitForConnection(); 529 mProxiedVirtualDisplayActivity.setExpectedKeyCode(KeyEvent.KEYCODE_DPAD_UP); 530 531 // The proxy activity should not receive the key event. 532 sUiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_DPAD_UP); 533 assertThrows(AssertionError.class, () -> PollingCheck.waitFor(() 534 -> mProxiedVirtualDisplayActivity.mReceivedKeyCode)); 535 536 mNonProxiedConcurrentActivity = launchProxyConcurrentActivityOnDefaultDisplay(service); 537 mNonProxiedConcurrentActivity.setExpectedKeyCode(KeyEvent.KEYCODE_DPAD_UP); 538 // The non-proxy activity on the default display should receive the key event. 539 sUiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_DPAD_UP); 540 PollingCheck.waitFor(() -> mNonProxiedConcurrentActivity.mReceivedKeyCode); 541 } finally { 542 service.disableSelfAndRemove(); 543 } 544 } 545 546 @Test testPerformSystemAction_topFocusDisplayIsLastNonProxyDisplay()547 public void testPerformSystemAction_topFocusDisplayIsLastNonProxyDisplay() 548 throws TimeoutException { 549 registerProxyAndWaitForConnection(); 550 // Make sure the virtual display is top-focused. 551 setTopFocusedDisplayIfNeeded(mVirtualDisplayId, mProxiedVirtualDisplayActivity, 552 mA11yProxy.getWindows()); 553 // Create a new system action. 554 final Intent i = new Intent("test").setPackage( 555 sInstrumentation.getContext().getPackageName()); 556 final PendingIntent p = PendingIntent.getBroadcast(sInstrumentation.getContext(), 0, i, 557 PendingIntent.FLAG_IMMUTABLE); 558 final RemoteAction testAction = 559 new RemoteAction(Icon.createWithContentUri("content://test"), 560 "test", "test", p); 561 mA11yManager.registerSystemAction(testAction, TEST_SYSTEM_ACTION_ID); 562 563 sUiAutomation.executeAndWaitForEvent( 564 () -> sUiAutomation.performGlobalAction(TEST_SYSTEM_ACTION_ID), 565 (event) -> displayFocused(event, 0), TIMEOUT_MS); 566 567 mA11yManager.unregisterSystemAction(TEST_SYSTEM_ACTION_ID); 568 } 569 570 @Test testTriggerTouchExploration_topFocusDisplayIsLastNonProxyDisplay()571 public void testTriggerTouchExploration_topFocusDisplayIsLastNonProxyDisplay() 572 throws TimeoutException { 573 final PackageManager pm = sInstrumentation.getContext().getPackageManager(); 574 assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) 575 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH)); 576 577 final StubProxyConcurrentAccessibilityService service = 578 mNonProxyServiceRule.enableService(); 579 try { 580 registerProxyAndWaitForConnection(); 581 // Make sure that the proxy display is the top-focused display. 582 setTopFocusedDisplayIfNeeded(mVirtualDisplayId, mProxiedVirtualDisplayActivity, 583 mA11yProxy.getWindows()); 584 585 final AccessibilityWindowInfo window = findWindowByTitleWithList( 586 mProxiedVirtualDisplayActivityTitle, mA11yProxy.getWindows()); 587 // Validity check: activity window exists. 588 assertThat(window).isNotNull(); 589 590 final Rect areaOfActivityWindowOnDisplay = new Rect(); 591 window.getBoundsInScreen(areaOfActivityWindowOnDisplay); 592 // Validity check: find window size, check that it is big enough for gestures. 593 final WindowManager windowManager = sInstrumentation.getContext().getSystemService( 594 WindowManager.class); 595 final DisplayMetrics metrics = new DisplayMetrics(); 596 windowManager.getDefaultDisplay().getRealMetrics(metrics); 597 assumeTrue(areaOfActivityWindowOnDisplay.width() > TypedValue.applyDimension( 598 TypedValue.COMPLEX_UNIT_MM, MIN_SCREEN_WIDTH_MM, metrics)); 599 sUiAutomation.executeAndWaitForEvent(() -> { 600 final int xOnScreen = 601 areaOfActivityWindowOnDisplay.centerX(); 602 final int yOnScreen = 603 areaOfActivityWindowOnDisplay.centerY(); 604 final GestureDescription.Builder builder = 605 new GestureDescription.Builder().addStroke( 606 click(new PointF(xOnScreen, yOnScreen))); 607 await(dispatchGesture(service, builder.build())); 608 }, (event) -> displayFocused(event, Display.DEFAULT_DISPLAY), TIMEOUT_MS); 609 } finally { 610 service.disableSelfAndRemove(); 611 } 612 } 613 614 @Test testOnAccessibilityEvent_clickButton_serviceDoesNotReceiveClickEvent()615 public void testOnAccessibilityEvent_clickButton_serviceDoesNotReceiveClickEvent() { 616 final StubProxyConcurrentAccessibilityService service = 617 mNonProxyServiceRule.enableService(); 618 try { 619 registerProxyAndWaitForConnection(); 620 // Create and populate the expected event. 621 AccessibilityEvent clickEvent = getProxyClickAccessibilityEvent(); 622 service.setEventFilter(getClickEventFilter(clickEvent)); 623 624 final Button button = mProxiedVirtualDisplayActivity.findViewById(R.id.button); 625 mProxiedVirtualDisplayActivity.runOnUiThread(() -> button.performClick()); 626 assertThrows(AssertionError.class, () -> 627 service.waitOnEvent(TIMEOUT_MS, 628 "Expected event was not received within " + TIMEOUT_MS + " ms")); 629 } finally { 630 service.disableSelfAndRemove(); 631 } 632 } 633 634 @Test 635 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#getWindows"}) testGetWindows_always_proxyReceivesWindowsOnDisplay()636 public void testGetWindows_always_proxyReceivesWindowsOnDisplay() { 637 registerProxyAndWaitForConnection(); 638 assertVirtualDisplayActivityExistsToProxy(); 639 } 640 641 @Test 642 @ApiTest(apis = {"android.accessibilityservice.AccessibilityService#getWindows"}) testGetWindows_always_serviceDoesNotGetProxyWindows()643 public void testGetWindows_always_serviceDoesNotGetProxyWindows() { 644 final StubProxyConcurrentAccessibilityService service = 645 mNonProxyServiceRule.enableService(); 646 try { 647 registerProxyAndWaitForConnection(); 648 SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays = 649 service.getWindowsOnAllDisplays(); 650 assertThat(windowsOnAllDisplays.contains(mVirtualDisplayId)).isFalse(); 651 } finally { 652 service.disableSelfAndRemove(); 653 } 654 } 655 656 @Test 657 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#findFocus(int)"}) testGetFocus_always_proxyGetsAccessibilityFocus()658 public void testGetFocus_always_proxyGetsAccessibilityFocus() throws TimeoutException { 659 registerProxyAndEnableTouchExploration(); 660 661 final EditText proxyEditText = mProxiedVirtualDisplayActivity.findViewById(R.id.edit_text); 662 setAccessibilityFocus(proxyEditText); 663 664 final AccessibilityNodeInfo a11yFocusedNode = mA11yProxy.findFocus( 665 AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 666 assertThat(a11yFocusedNode).isEqualTo(proxyEditText.createAccessibilityNodeInfo()); 667 } 668 669 @Test 670 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#findFocus(int)"}) testGetFocus_serviceSetsAccessibilityFocus_proxyGetsNullFocus()671 public void testGetFocus_serviceSetsAccessibilityFocus_proxyGetsNullFocus() throws Exception { 672 final StubProxyConcurrentAccessibilityService service = 673 mNonProxyServiceRule.enableService(); 674 try { 675 registerProxyAndEnableTouchExploration(); 676 // Launch an activity on the default display. 677 mNonProxiedConcurrentActivity = launchProxyConcurrentActivityOnDefaultDisplay(service); 678 final EditText serviceEditText = mNonProxiedConcurrentActivity.findViewById( 679 R.id.editText); 680 setAccessibilityFocus(serviceEditText); 681 682 final AccessibilityNodeInfo a11yFocusedNode = service.findFocus( 683 AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 684 assertThat(a11yFocusedNode).isEqualTo(serviceEditText.createAccessibilityNodeInfo()); 685 assertThat(mA11yProxy.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)).isNull(); 686 } finally { 687 service.disableSelfAndRemove(); 688 } 689 } 690 @Test 691 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#findFocus(int)"}) testGetFocus_proxySetsAccessibilityFocus_serviceGetsNullFocus()692 public void testGetFocus_proxySetsAccessibilityFocus_serviceGetsNullFocus() throws Exception { 693 final StubProxyConcurrentAccessibilityService service = 694 mNonProxyServiceRule.enableService(); 695 try { 696 registerProxyAndEnableTouchExploration(); 697 698 final EditText proxyEditText = 699 mProxiedVirtualDisplayActivity.findViewById(R.id.edit_text); 700 setAccessibilityFocus(proxyEditText); 701 702 final AccessibilityNodeInfo a11yFocusedNode = mA11yProxy.findFocus( 703 AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 704 assertThat(a11yFocusedNode).isEqualTo(proxyEditText.createAccessibilityNodeInfo()); 705 706 assertThat(service.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)).isNull(); 707 } finally { 708 service.disableSelfAndRemove(); 709 } 710 } 711 712 @Test 713 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onAccessibilityEvent"}) testOnA11yEvent_moveFocusWithinWindow_proxyDoesNotGetWindowEvent()714 public void testOnA11yEvent_moveFocusWithinWindow_proxyDoesNotGetWindowEvent() { 715 registerProxyAndEnableTouchExploration(); 716 717 final EditText editText = mProxiedVirtualDisplayActivity.findViewById(R.id.edit_text); 718 // Avoid using setAccessibilityFocus/waiting for events on UiAutomation's thread, since 719 // the WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED event from the EditText may be captured by the 720 // filter below. 721 setInitialAccessibilityFocusAndWaitForProxyEvents(editText); 722 723 mA11yProxy.setEventFilter( 724 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); 725 final Button button = mProxiedVirtualDisplayActivity.findViewById(R.id.button); 726 mProxiedVirtualDisplayActivity.runOnUiThread(() -> button.performAccessibilityAction( 727 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)); 728 729 assertThrows("A WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED event was received.", 730 AssertionError.class, () -> 731 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mReceivedEvent.get(), 732 TIMEOUT_MS, "(expected to timeout)")); 733 assertThat(button.isAccessibilityFocused()).isTrue(); 734 } 735 736 @Test 737 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onAccessibilityEvent"}) testOnA11yEvent_setInitialFocus_proxyGetsWindowEvent()738 public void testOnA11yEvent_setInitialFocus_proxyGetsWindowEvent() { 739 registerProxyAndEnableTouchExploration(); 740 741 mA11yProxy.setEventFilter( 742 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); 743 final EditText editText = mProxiedVirtualDisplayActivity.findViewById(R.id.edit_text); 744 mProxiedVirtualDisplayActivity.runOnUiThread(() -> editText.performAccessibilityAction( 745 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)); 746 747 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mReceivedEvent.get(), TIMEOUT_MS, 748 "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED received"); 749 } 750 751 @Test 752 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onAccessibilityEvent"}) testOnA11yEvent_moveFocusBetweenWindows_proxyGetsWindowEvent()753 public void testOnA11yEvent_moveFocusBetweenWindows_proxyGetsWindowEvent() throws Exception { 754 registerProxyAndEnableTouchExploration(); 755 756 final EditText editText = mProxiedVirtualDisplayActivity.findViewById(R.id.edit_text); 757 setInitialAccessibilityFocusAndWaitForProxyEvents(editText); 758 759 final View topWindowView = showTopWindowAndWaitForItToShowUp(); 760 final AccessibilityWindowInfo topWindow = findWindowByTitleWithList(TOP_WINDOW_TITLE, 761 mA11yProxy.getWindows()); 762 assertThat(topWindow).isNotNull(); 763 764 mA11yProxy.setEventFilter(filterWaitForAll( 765 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED), 766 filterForEventType(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED))); 767 final AccessibilityNodeInfo buttonNode = 768 topWindow.getRoot().findAccessibilityNodeInfosByText( 769 sInstrumentation.getContext().getString(R.string.button1)).get(0); 770 771 mProxiedVirtualDisplayActivity.runOnUiThread(() -> buttonNode.performAction( 772 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)); 773 774 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mReceivedEvent.get(), TIMEOUT_MS, 775 "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED received"); 776 777 WindowCreationUtils.removeWindow(sUiAutomation, sInstrumentation, 778 mProxiedVirtualDisplayActivity, topWindowView); 779 } 780 781 @Test 782 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#findFocus(int)"}) testGetFocus_serviceAndProxySetA11yFocus_serviceAndProxyGetSeparateFocus()783 public void testGetFocus_serviceAndProxySetA11yFocus_serviceAndProxyGetSeparateFocus() 784 throws Exception { 785 final StubProxyConcurrentAccessibilityService service = 786 mNonProxyServiceRule.enableService(); 787 try { 788 registerProxyAndEnableTouchExploration(); 789 // TODO(268752827): Investigate why the proxy window is invisible to to accessibility 790 // services nce the activity on the default display is launched. (Launching the default 791 // display activity will cause the windows of the virtual display to be cleared from 792 // A11yWindowManager.) 793 794 final EditText proxyEditText = 795 mProxiedVirtualDisplayActivity.findViewById(R.id.edit_text); 796 setAccessibilityFocus(proxyEditText); 797 798 final AccessibilityNodeInfo proxyA11yFocusedNode = 799 mA11yProxy.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 800 assertThat(proxyA11yFocusedNode).isEqualTo(proxyEditText.createAccessibilityNodeInfo()); 801 802 // Launch an activity on the default display. 803 mNonProxiedConcurrentActivity = launchProxyConcurrentActivityOnDefaultDisplay(service); 804 805 final EditText serviceEditText = 806 mNonProxiedConcurrentActivity.findViewById(R.id.editText); 807 setAccessibilityFocus(serviceEditText); 808 final AccessibilityNodeInfo a11yFocusedNode = service.findFocus( 809 AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 810 811 assertThat(a11yFocusedNode).isEqualTo(serviceEditText.createAccessibilityNodeInfo()); 812 } finally { 813 service.disableSelfAndRemove(); 814 } 815 } 816 817 @Test 818 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#findFocus(int)"}) testGetFocus_proxySetsInputFocus_proxyGetsInputFocus()819 public void testGetFocus_proxySetsInputFocus_proxyGetsInputFocus() throws TimeoutException { 820 registerProxyAndWaitForConnection(); 821 822 // Make sure that the proxy display is the top-focused display. 823 setTopFocusedDisplayIfNeeded(mVirtualDisplayId, mProxiedVirtualDisplayActivity, 824 mA11yProxy.getWindows()); 825 826 final EditText editText = mProxiedVirtualDisplayActivity.findViewById(R.id.edit_text); 827 setInputFocusIfNeeded(editText); 828 829 final AccessibilityNodeInfo inputFocus = mA11yProxy.findFocus( 830 AccessibilityNodeInfo.FOCUS_INPUT); 831 assertThat(inputFocus).isEqualTo(editText.createAccessibilityNodeInfo()); 832 } 833 834 @Test 835 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#findFocus(int)"}) testGetFocus_serviceSetsInputFocus_proxyDoesNotGetServiceInputFocus()836 public void testGetFocus_serviceSetsInputFocus_proxyDoesNotGetServiceInputFocus() 837 throws Exception { 838 final StubProxyConcurrentAccessibilityService service = 839 mNonProxyServiceRule.enableService(); 840 try { 841 registerProxyAndWaitForConnection(); 842 // Launch an activity on the default display. 843 mNonProxiedConcurrentActivity = launchProxyConcurrentActivityOnDefaultDisplay(service); 844 // Make sure that the default display is the top-focused display. 845 setTopFocusedDisplayIfNeeded(Display.DEFAULT_DISPLAY, mNonProxiedConcurrentActivity, 846 service.getWindows()); 847 848 final EditText editText = mNonProxiedConcurrentActivity.findViewById(R.id.editText); 849 setInputFocusIfNeeded(editText); 850 851 final AccessibilityNodeInfo inputFocus = service.findFocus( 852 AccessibilityNodeInfo.FOCUS_INPUT); 853 assertThat(inputFocus).isEqualTo(editText.createAccessibilityNodeInfo()); 854 assertThat(mA11yProxy.findFocus(AccessibilityNodeInfo.FOCUS_INPUT)).isNull(); 855 } finally { 856 service.disableSelfAndRemove(); 857 } 858 } 859 860 @Test 861 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onAccessibilityEvent"}) testOnA11yEvent_touchProxyDisplay_proxyDoesNotReceiveInteractionEvent()862 public void testOnA11yEvent_touchProxyDisplay_proxyDoesNotReceiveInteractionEvent() { 863 registerProxyAndWaitForConnection(); 864 mA11yProxy.setEventFilter(filterForEventType( 865 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START)); 866 867 // Try to trigger touch exploration, but fail. 868 final MotionEvent downEvent = getDownMotionEvent(mProxiedVirtualDisplayActivityTitle, 869 mA11yProxy.getWindows(), mVirtualDisplayId); 870 sUiAutomation.injectInputEventToInputFilter(downEvent); 871 872 assertThrows("The TYPE_TOUCH_INTERACTION_START event was received for a display" 873 + " with disabled input.", AssertionError.class, () -> 874 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mReceivedEvent.get(), 875 TIMEOUT_MS, "(expected to timeout)")); 876 } 877 878 @Test 879 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onAccessibilityEvent"}) testOnA11yEvent_touchDefaultDisplay_serviceReceivesInteractionEvent()880 public void testOnA11yEvent_touchDefaultDisplay_serviceReceivesInteractionEvent() 881 throws Exception { 882 final StubProxyConcurrentAccessibilityService service = 883 mNonProxyServiceRule.enableService(); 884 try { 885 registerProxyAndWaitForConnection(); 886 // Launch an activity on the default display. 887 mNonProxiedConcurrentActivity = launchProxyConcurrentActivityOnDefaultDisplay(service); 888 service.setEventFilter(filterForEventType( 889 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START)); 890 891 // Trigger touch exploration. 892 final MotionEvent downEvent = getDownMotionEvent(getActivityTitle(sInstrumentation, 893 mNonProxiedConcurrentActivity), service.getWindows(), 894 mNonProxiedConcurrentActivity.getDisplayId()); 895 sUiAutomation.injectInputEventToInputFilter(downEvent); 896 897 service.waitOnEvent(TIMEOUT_MS, 898 "Expected event was not received within " + TIMEOUT_MS + " ms"); 899 } finally { 900 service.disableSelfAndRemove(); 901 } 902 } 903 getDownMotionEvent(CharSequence activityTitle, List<AccessibilityWindowInfo> windows, int displayId)904 private static MotionEvent getDownMotionEvent(CharSequence activityTitle, 905 List<AccessibilityWindowInfo> windows, int displayId) { 906 final Rect areaOfActivityWindowOnDisplay = new Rect(); 907 final AccessibilityWindowInfo window = findWindowByTitleWithList(activityTitle, windows); 908 // Validity check: activity window exists. 909 assertThat(window).isNotNull(); 910 911 window.getBoundsInScreen(areaOfActivityWindowOnDisplay); 912 final int xOnScreen = 913 areaOfActivityWindowOnDisplay.centerX(); 914 final int yOnScreen = 915 areaOfActivityWindowOnDisplay.centerY(); 916 final long downEventTime = SystemClock.uptimeMillis(); 917 final MotionEvent downEvent = MotionEvent.obtain(downEventTime, 918 downEventTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 0); 919 downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); 920 downEvent.setDisplayId(displayId); 921 return downEvent; 922 } 923 924 @Test 925 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 926 + ".AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged"}) onAccessibilityServicesStateChanged_registerProxy_notifiesProxiedApp()927 public void onAccessibilityServicesStateChanged_registerProxy_notifiesProxiedApp() { 928 // Test that the proxy activity is notified that the services state is changed after 929 // proxy registration. 930 registerProxyWithTestServiceInfoAndWaitForServicesStateChange(); 931 } 932 933 @Test 934 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 935 + ".AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged"}) onAccessibilityServicesStateChanged_updateProxyEnabledList_notifiesProxiedApp()936 public void onAccessibilityServicesStateChanged_updateProxyEnabledList_notifiesProxiedApp() { 937 // Test that the proxy activity is notified that the services state is changed after 938 // a proxy update of installed and enabled services. 939 registerProxyAndWaitForConnection(); 940 final MyAccessibilityServicesStateChangeListener listener = 941 new MyAccessibilityServicesStateChangeListener(INTERACTIVE_UI_TIMEOUT, 942 NON_INTERACTIVE_UI_TIMEOUT); 943 mProxyActivityA11yManager.addAccessibilityServicesStateChangeListener(listener); 944 try { 945 mA11yProxy.setInstalledAndEnabledServices(getTestAccessibilityServiceInfoAsList()); 946 waitOn(listener.mWaitObject, () -> listener.mAtomicBoolean.get(), TIMEOUT_MS, 947 "Services state listener should be called when proxy installed and" 948 + "enabled services are updated"); 949 assertTestAccessibilityServiceInfo( 950 mProxyActivityA11yManager.getInstalledAccessibilityServiceList()); 951 assertTestAccessibilityServiceInfo( 952 mProxyActivityA11yManager.getEnabledAccessibilityServiceList( 953 FEEDBACK_ALL_MASK)); 954 } finally { 955 mProxyActivityA11yManager.removeAccessibilityServicesStateChangeListener(listener); 956 } 957 } 958 959 @Test 960 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 961 + ".TouchExplorationStateChangeListener#onTouchExplorationStateChanged"}) onTouchExplorationStateChanged_enableProxyTouchExploration_notifiesProxiedApp()962 public void onTouchExplorationStateChanged_enableProxyTouchExploration_notifiesProxiedApp() { 963 // Test that the proxy activity is notified that touch exploration is enabled after 964 // proxy registration. 965 registerProxyAndEnableTouchExploration(); 966 } 967 968 @Test 969 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 970 + ".AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged"}) testOnA11yServicesStateChanged_registerUnregisterProxy_resetsProxiedAppState()971 public void testOnA11yServicesStateChanged_registerUnregisterProxy_resetsProxiedAppState() { 972 // Tests that if a proxy is unregistered, the proxy app is updated to the non-proxy service 973 // state. 974 final StubProxyConcurrentAccessibilityService service = 975 mNonProxyServiceRule.enableService(); 976 try { 977 // Set up the service info with timeouts. 978 final AccessibilityServiceInfo nonProxyInfo = service.getServiceInfo(); 979 nonProxyInfo.setInteractiveUiTimeoutMillis(NON_PROXY_SERVICE_TIMEOUT); 980 nonProxyInfo.setNonInteractiveUiTimeoutMillis(NON_PROXY_SERVICE_TIMEOUT); 981 service.setServiceInfo(nonProxyInfo); 982 // Register a proxy with different timeouts and make sure the activity is updated. 983 registerProxyWithTestServiceInfoAndWaitForServicesStateChange(); 984 985 // When the proxy is unregistered, the a11y manager should be updated with the 986 // StubProxyConcurrentAccessibilityService's timeouts. 987 final MyAccessibilityServicesStateChangeListener listener = 988 new MyAccessibilityServicesStateChangeListener(NON_PROXY_SERVICE_TIMEOUT, 989 NON_PROXY_SERVICE_TIMEOUT); 990 mProxyActivityA11yManager.addAccessibilityServicesStateChangeListener(listener); 991 try { 992 runWithShellPermissionIdentity(sUiAutomation, () -> 993 mA11yManager.unregisterDisplayProxy(mA11yProxy)); 994 waitOn(listener.mWaitObject, () -> listener.mAtomicBoolean.get(), TIMEOUT_MS, 995 "Services state change listener should be called when proxy is" 996 + " unregistered"); 997 // The service infos should no longer reflect the proxy and instead reflect 998 // StubProxyConcurrentAccessibilityService. 999 final List<AccessibilityServiceInfo> installedServices = 1000 mProxyActivityA11yManager.getInstalledAccessibilityServiceList(); 1001 final List<AccessibilityServiceInfo> enabledServices = 1002 mProxyActivityA11yManager.getEnabledAccessibilityServiceList( 1003 FEEDBACK_ALL_MASK); 1004 boolean installedServicesHasConcurrentService = false; 1005 for (AccessibilityServiceInfo installedInfo : installedServices) { 1006 if (installedInfo.equals(nonProxyInfo)) { 1007 installedServicesHasConcurrentService = true; 1008 break; 1009 } 1010 } 1011 assertThat(installedServicesHasConcurrentService).isTrue(); 1012 assertThat(enabledServices.get(0)).isEqualTo(nonProxyInfo); 1013 } finally { 1014 mProxyActivityA11yManager.removeAccessibilityServicesStateChangeListener(listener); 1015 } 1016 } finally { 1017 service.disableSelfAndRemove(); 1018 } 1019 } 1020 1021 @Test 1022 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 1023 + ".TouchExplorationStateChangeListener#onTouchExplorationStateChanged"}) testOnTouchExplorationStateChanged_registerUnregisterProxy_resetsProxiedAppState()1024 public void testOnTouchExplorationStateChanged_registerUnregisterProxy_resetsProxiedAppState() { 1025 registerProxyAndEnableTouchExploration(); 1026 final MyTouchExplorationStateChangeListener listener = 1027 new MyTouchExplorationStateChangeListener(/* initialState */ true); 1028 mProxyActivityA11yManager.addTouchExplorationStateChangeListener(listener); 1029 try { 1030 // Test that touch exploration is disabled for the app when the proxy is unregistered. 1031 runWithShellPermissionIdentity(sUiAutomation, () -> 1032 mA11yManager.unregisterDisplayProxy(mA11yProxy)); 1033 waitOn(listener.mWaitObject, () -> !listener.mAtomicBoolean.get(), TIMEOUT_MS, 1034 "The touch exploration listener should be notified when the proxy is" 1035 + " unregistered"); 1036 assertThat(mProxyActivityA11yManager.isTouchExplorationEnabled()).isFalse(); 1037 } finally { 1038 mProxyActivityA11yManager.removeTouchExplorationStateChangeListener(listener); 1039 } 1040 } 1041 1042 @Test 1043 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 1044 + ".AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged"}) testOnA11yServicesStateChanged_updateServiceTimeout_doesNotNotifyProxiedApp()1045 public void testOnA11yServicesStateChanged_updateServiceTimeout_doesNotNotifyProxiedApp() { 1046 // Note: A service has to be enabled before registering a proxy, otherwise the installed 1047 // list won't contain this service and enableService() fails. This is because the 1048 // instrumentation process would be associated with the proxy device id and the proxy's 1049 // services (A11yManager#getInstalledAccessibilityServiceList would belong to the proxy). 1050 final StubProxyConcurrentAccessibilityService service = 1051 mNonProxyServiceRule.enableService(); 1052 try { 1053 registerProxyWithTestServiceInfoAndWaitForServicesStateChange(); 1054 final MyAccessibilityServicesStateChangeListener listener = 1055 new MyAccessibilityServicesStateChangeListener(NON_PROXY_SERVICE_TIMEOUT, 1056 NON_PROXY_SERVICE_TIMEOUT); 1057 mProxyActivityA11yManager.addAccessibilityServicesStateChangeListener(listener); 1058 try { 1059 final AccessibilityServiceInfo nonProxyInfo = service.getServiceInfo(); 1060 nonProxyInfo.setInteractiveUiTimeoutMillis(NON_PROXY_SERVICE_TIMEOUT); 1061 nonProxyInfo.setNonInteractiveUiTimeoutMillis(NON_PROXY_SERVICE_TIMEOUT); 1062 service.setServiceInfo(nonProxyInfo); 1063 1064 assertThrows("The A11yManager of the proxy-ed app was notified of a" 1065 + " non-proxy service change.", AssertionError.class, 1066 () -> waitOn(listener.mWaitObject, () -> listener.mAtomicBoolean.get(), 1067 TIMEOUT_MS, "(expected to timeout)")); 1068 // Verify the activity has the proxy timeouts. 1069 assertThat(mProxyActivityA11yManager.getRecommendedTimeoutMillis( 1070 /* originalTimeout */0, 1071 FLAG_CONTENT_CONTROLS)).isEqualTo(INTERACTIVE_UI_TIMEOUT); 1072 assertThat(mProxyActivityA11yManager.getRecommendedTimeoutMillis( 1073 /* originalTimeout */ 0, 1074 FLAG_CONTENT_TEXT)).isEqualTo(NON_INTERACTIVE_UI_TIMEOUT); 1075 } finally { 1076 mProxyActivityA11yManager.removeAccessibilityServicesStateChangeListener(listener); 1077 } 1078 1079 } finally { 1080 service.disableSelfAndRemove(); 1081 } 1082 } 1083 1084 @Test 1085 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 1086 + ".TouchExplorationStateChangeListener#onTouchExplorationStateChanged"}) testOnTouchExplorationChanged_updateServiceTouchState_doesNotNotifyProxiedApp()1087 public void testOnTouchExplorationChanged_updateServiceTouchState_doesNotNotifyProxiedApp() { 1088 final StubProxyConcurrentAccessibilityService service = 1089 mNonProxyServiceRule.enableService(); 1090 try { 1091 registerProxyAndWaitForConnection(); 1092 final MyTouchExplorationStateChangeListener listener = 1093 new MyTouchExplorationStateChangeListener(/* initialState */ false); 1094 mProxyActivityA11yManager.addTouchExplorationStateChangeListener(listener); 1095 try { 1096 final AccessibilityServiceInfo info = service.getServiceInfo(); 1097 info.flags &= 1098 ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1099 service.setServiceInfo(info); 1100 assertThrows("A proxy-ed app with touch exploration enabled was notified" 1101 + " of a non-proxy service disabling touch exploration.", 1102 AssertionError.class, () -> 1103 waitOn(listener.mWaitObject, () -> listener.mAtomicBoolean.get(), 1104 TIMEOUT_MS, "(expected to timeout)")); 1105 } finally { 1106 mProxyActivityA11yManager.removeTouchExplorationStateChangeListener(listener); 1107 } 1108 } finally { 1109 // Reset touch exploration for the non-proxy service. 1110 final AccessibilityServiceInfo info = service.getServiceInfo(); 1111 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1112 service.setServiceInfo(info); 1113 service.disableSelfAndRemove(); 1114 } 1115 } 1116 1117 @Test 1118 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 1119 + ".AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged"}) testOnA11yServicesStateChanged_enableService_notifiesNonProxiedApp()1120 public void testOnA11yServicesStateChanged_enableService_notifiesNonProxiedApp() 1121 throws TimeoutException, InterruptedException { 1122 // Verify that enabling a non-proxy service will update the non-proxy AccessibilityManager. 1123 // On the service state change, the activity will emit a broadcast with an intent of action 1124 // ACCESSIBILITY_SERVICE_STATE. 1125 String enabledServices = null; 1126 try { 1127 registerBroadcastReceiverForAction(ACCESSIBILITY_SERVICE_STATE); 1128 startActivityInSeparateProcess(); 1129 final CountDownLatch serviceEnabled = new CountDownLatch(1); 1130 mReceiver.setLatchAndExpectedServiceResult(serviceEnabled, ACCESSIBILITY_SERVICE_STATE, 1131 StubProxyConcurrentAccessibilityService.class.getSimpleName()); 1132 enabledServices = 1133 Settings.Secure.getString( 1134 sInstrumentation.getContext().getContentResolver(), 1135 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 1136 mNonProxyServiceRule.enableServiceWithoutWait(); 1137 1138 assertThat(serviceEnabled.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 1139 } finally { 1140 sInstrumentation.getContext().unregisterReceiver(mReceiver); 1141 if (mNonProxyServiceRule.getService() != null) { 1142 mNonProxyServiceRule.getService().disableSelfAndRemove(); 1143 } else if (enabledServices != null) { 1144 ShellCommandBuilder.create(sInstrumentation) 1145 .putSecureSetting( 1146 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 1147 enabledServices) 1148 .run(); 1149 } 1150 stopSeparateProcess(); 1151 1152 } 1153 } 1154 1155 @Test 1156 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 1157 + ".TouchExplorationStateChangeListener#onTouchExplorationStateChanged"}) testOnTouchExplorationStateChanged_updateServiceTouchState_notifiesNonProxiedApp()1158 public void testOnTouchExplorationStateChanged_updateServiceTouchState_notifiesNonProxiedApp() 1159 throws TimeoutException, InterruptedException { 1160 // Verify that enabling and disabling touch exploration for a non-proxy a11y service will 1161 // update the non-proxy AccessibilityManager. 1162 // On touch exploration state change, the activity will emit a broadcast with 1163 // an intent of action TOUCH_EXPLORATION_STATE. 1164 try { 1165 registerBroadcastReceiverForAction(TOUCH_EXPLORATION_STATE); 1166 startActivityInSeparateProcess(); 1167 1168 final CountDownLatch touchExplorationEnabled = new CountDownLatch(1); 1169 mReceiver.setLatchAndExpectedEnabledResult(touchExplorationEnabled, 1170 TOUCH_EXPLORATION_STATE, 1171 true); 1172 1173 mNonProxyServiceRule.enableServiceWithoutWait(); 1174 1175 assertThat(touchExplorationEnabled.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 1176 1177 final InstrumentedAccessibilityService a11yService = mNonProxyServiceRule.getService(); 1178 final AccessibilityServiceInfo info = a11yService.getServiceInfo(); 1179 info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1180 final CountDownLatch touchExplorationDisabled = new CountDownLatch(1); 1181 mReceiver.setLatchAndExpectedEnabledResult(touchExplorationDisabled, 1182 TOUCH_EXPLORATION_STATE, 1183 false); 1184 1185 a11yService.setServiceInfo(info); 1186 1187 assertThat(touchExplorationDisabled.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 1188 } finally { 1189 sInstrumentation.getContext().unregisterReceiver(mReceiver); 1190 final InstrumentedAccessibilityService service = mNonProxyServiceRule.getService(); 1191 if (service != null) { 1192 // Reset touch exploration for the non-proxy service. 1193 final AccessibilityServiceInfo info = service.getServiceInfo(); 1194 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1195 service.setServiceInfo(info); 1196 service.disableSelfAndRemove(); 1197 } 1198 stopSeparateProcess(); 1199 } 1200 } 1201 @Test 1202 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 1203 + ".AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged"}) testOnA11yServicesStateChanged_registerProxy_doesNotNotifyNonProxiedApp()1204 public void testOnA11yServicesStateChanged_registerProxy_doesNotNotifyNonProxiedApp() 1205 throws TimeoutException, InterruptedException { 1206 try { 1207 registerBroadcastReceiverForAction(ACCESSIBILITY_SERVICE_STATE); 1208 startActivityInSeparateProcess(); 1209 1210 final CountDownLatch servicesChanged = new CountDownLatch(1); 1211 mReceiver.setLatchAndExpectedAction(servicesChanged, ACCESSIBILITY_SERVICE_STATE); 1212 1213 runWithShellPermissionIdentity(sUiAutomation, 1214 () -> mA11yManager.registerDisplayProxy(mA11yProxy)); 1215 1216 assertThat(servicesChanged.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isFalse(); 1217 1218 } finally { 1219 sInstrumentation.getContext().unregisterReceiver(mReceiver); 1220 stopSeparateProcess(); 1221 } 1222 } 1223 1224 @Test 1225 @ApiTest(apis = {"android.view.accessibility.AccessibilityManager" 1226 + ".TouchExplorationStateChangeListener#onTouchExplorationStateChanged"}) testOnTouchExplorationStateChanged_registerProxy_doesNotNotifyNonProxiedApp()1227 public void testOnTouchExplorationStateChanged_registerProxy_doesNotNotifyNonProxiedApp() 1228 throws TimeoutException, InterruptedException { 1229 // A service has touch exploration enabled by default. 1230 final StubProxyConcurrentAccessibilityService service = 1231 mNonProxyServiceRule.enableService(); 1232 try { 1233 registerBroadcastReceiverForAction(TOUCH_EXPLORATION_STATE); 1234 assertThat((service.getServiceInfo().flags 1235 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0).isTrue(); 1236 startActivityInSeparateProcess(); 1237 1238 final CountDownLatch touchExplorationEnabled = new CountDownLatch(1); 1239 mReceiver.setLatchAndExpectedEnabledResult(touchExplorationEnabled, 1240 TOUCH_EXPLORATION_STATE, true); 1241 1242 runWithShellPermissionIdentity(sUiAutomation, 1243 () -> mA11yManager.registerDisplayProxy(mA11yProxy)); 1244 1245 assertThat(touchExplorationEnabled.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isFalse(); 1246 } finally { 1247 sInstrumentation.getContext().unregisterReceiver(mReceiver); 1248 service.disableSelfAndRemove(); 1249 stopSeparateProcess(); 1250 } 1251 } 1252 1253 @Test 1254 @ApiTest(apis = { 1255 "android.view.accessibility.AccessibilityDisplayProxy#setInstalledAndEnabledServices", 1256 "android.view.accessibility.AccessibilityDisplayProxy#getInstalledAndEnabledServices"}) testGetInstalledAndEnabledAccessibilityServices_proxySetsList_getsList()1257 public void testGetInstalledAndEnabledAccessibilityServices_proxySetsList_getsList() { 1258 mA11yProxy = new MyA11yProxy(mVirtualDisplayId, Executors.newSingleThreadExecutor(), 1259 new ArrayList<>()); 1260 registerProxyAndWaitForConnection(); 1261 1262 mA11yProxy.setInstalledAndEnabledServices(getTestAccessibilityServiceInfoAsList()); 1263 1264 assertTestAccessibilityServiceInfo(mA11yProxy.getInstalledAndEnabledServices()); 1265 } 1266 1267 @Test 1268 @ApiTest(apis = { 1269 "android.view.accessibility.AccessibilityDisplayProxy#getInstalledAndEnabledServices"}) testGetInstalledAndEnabledAccessibilityServices_registerProxyWithList_getsList()1270 public void testGetInstalledAndEnabledAccessibilityServices_registerProxyWithList_getsList() { 1271 mA11yProxy = new MyA11yProxy(mVirtualDisplayId, Executors.newSingleThreadExecutor(), 1272 getTestAccessibilityServiceInfoAsList()); 1273 registerProxyAndWaitForConnection(); 1274 1275 assertTestAccessibilityServiceInfo(mA11yProxy.getInstalledAndEnabledServices()); 1276 } 1277 1278 @Test 1279 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#interrupt"}) testInterrupt_always_causesInterrupt()1280 public void testInterrupt_always_causesInterrupt() { 1281 registerProxyAndWaitForConnection(); 1282 1283 mProxyActivityA11yManager.interrupt(); 1284 1285 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mInterrupted.get(), TIMEOUT_MS, 1286 "Proxy interrupted"); 1287 } 1288 1289 @Test 1290 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#onProxyConnected"}) testOnProxyConnected_always_connectsProxy()1291 public void testOnProxyConnected_always_connectsProxy() { 1292 registerProxyAndWaitForConnection(); 1293 } 1294 registerProxyAndWaitForConnection()1295 private void registerProxyAndWaitForConnection() { 1296 runWithShellPermissionIdentity(sUiAutomation, 1297 () -> mA11yManager.registerDisplayProxy(mA11yProxy)); 1298 1299 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mConnected.get(), TIMEOUT_MS, 1300 "Proxy connected"); 1301 } 1302 1303 @Test 1304 @ApiTest(apis = {"android.view.accessibility.AccessibilityDisplayProxy#setFocusAppearance"}) testSetFocusAppearance_always_updatesAppWithAppearance()1305 public void testSetFocusAppearance_always_updatesAppWithAppearance() { 1306 registerProxyAndEnableTouchExploration(); 1307 // This test verifies that the proxy can set the user's focus appearance, which affects all 1308 // apps. Ideally this should only affect the apps that are proxy-ed. 1309 // TODO(264594384): Test that a non-proxy activity does not get a changed focus appearance. 1310 final int width = mProxyActivityA11yManager.getAccessibilityFocusStrokeWidth(); 1311 final int color = mProxyActivityA11yManager.getAccessibilityFocusColor(); 1312 final int updatedWidth = width + 10; 1313 final int updatedColor = color == Color.BLUE ? Color.RED : Color.BLUE; 1314 1315 try { 1316 setFocusAppearanceDataAndCheckItCorrect(mA11yProxy, updatedWidth, updatedColor); 1317 } finally { 1318 setFocusAppearanceDataAndCheckItCorrect(mA11yProxy, width, color); 1319 } 1320 } 1321 setFocusAppearanceDataAndCheckItCorrect(AccessibilityDisplayProxy proxy, int focusStrokeWidthValue, int focusColorValue)1322 private void setFocusAppearanceDataAndCheckItCorrect(AccessibilityDisplayProxy proxy, 1323 int focusStrokeWidthValue, int focusColorValue) { 1324 proxy.setAccessibilityFocusAppearance(focusStrokeWidthValue, 1325 focusColorValue); 1326 // Checks if the color and the stroke values from AccessibilityManager are 1327 // updated as expected. 1328 PollingCheck.waitFor(()-> 1329 mProxyActivityA11yManager.getAccessibilityFocusStrokeWidth() 1330 == focusStrokeWidthValue 1331 && mProxyActivityA11yManager.getAccessibilityFocusColor() 1332 == focusColorValue); 1333 } 1334 registerProxyWithTestServiceInfoAndWaitForServicesStateChange()1335 private void registerProxyWithTestServiceInfoAndWaitForServicesStateChange() { 1336 mA11yProxy = new MyA11yProxy(mVirtualDisplayId, Executors.newSingleThreadExecutor(), 1337 getTestAccessibilityServiceInfoAsList()); 1338 1339 // Test that the A11yManager is notified when the timeouts have successfully propagated. 1340 final MyAccessibilityServicesStateChangeListener listener = 1341 new MyAccessibilityServicesStateChangeListener(INTERACTIVE_UI_TIMEOUT, 1342 NON_INTERACTIVE_UI_TIMEOUT); 1343 mProxyActivityA11yManager.addAccessibilityServicesStateChangeListener(listener); 1344 try { 1345 runWithShellPermissionIdentity(sUiAutomation, 1346 () -> mA11yManager.registerDisplayProxy(mA11yProxy)); 1347 waitOn(listener.mWaitObject, () -> listener.mAtomicBoolean.get(), TIMEOUT_MS, 1348 "Proxy AccessibilityServicesStateChangeListener called when proxy" 1349 + " is registered"); 1350 // Make sure the installed and enabled services are correct. 1351 assertTestAccessibilityServiceInfo( 1352 mProxyActivityA11yManager.getInstalledAccessibilityServiceList()); 1353 assertTestAccessibilityServiceInfo( 1354 mProxyActivityA11yManager.getEnabledAccessibilityServiceList( 1355 FEEDBACK_ALL_MASK)); 1356 } finally { 1357 mProxyActivityA11yManager.removeAccessibilityServicesStateChangeListener(listener); 1358 } 1359 } 1360 getProxyClickAccessibilityEvent()1361 private AccessibilityEvent getProxyClickAccessibilityEvent() { 1362 final AccessibilityEvent clickEvent = new AccessibilityEvent(); 1363 clickEvent.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED); 1364 clickEvent.setClassName(Button.class.getName()); 1365 clickEvent.setDisplayId(mA11yProxy.getDisplayId()); 1366 clickEvent.getText().add(mProxiedVirtualDisplayActivity.getString(R.string.button_title)); 1367 return clickEvent; 1368 } 1369 getClickEventFilter( AccessibilityEvent clickEvent)1370 private UiAutomation.AccessibilityEventFilter getClickEventFilter( 1371 AccessibilityEvent clickEvent) { 1372 return allOf( 1373 new AccessibilityEventFilterUtils.AccessibilityEventTypeMatcher(TYPE_VIEW_CLICKED), 1374 AccessibilityEventFilterUtils.matcherForDisplayId(clickEvent.getDisplayId()), 1375 AccessibilityEventFilterUtils.matcherForClassName(clickEvent.getClassName()), 1376 AccessibilityEventFilterUtils.matcherForFirstText(clickEvent.getText().get(0))) 1377 ::matches; 1378 } 1379 getTestAccessibilityServiceInfoAsList()1380 private List<AccessibilityServiceInfo> getTestAccessibilityServiceInfoAsList() { 1381 final List<AccessibilityServiceInfo> infos = new ArrayList<>(); 1382 final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 1383 info.setAccessibilityTool(true); 1384 info.packageNames = new String[]{PACKAGE_1, PACKAGE_2}; 1385 info.setInteractiveUiTimeoutMillis(INTERACTIVE_UI_TIMEOUT); 1386 info.setNonInteractiveUiTimeoutMillis(NON_INTERACTIVE_UI_TIMEOUT); 1387 info.notificationTimeout = NOTIFICATION_TIMEOUT; 1388 info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED; 1389 info.feedbackType = FEEDBACK_AUDIBLE; 1390 info.flags = FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; 1391 infos.add(info); 1392 return infos; 1393 } 1394 assertTestAccessibilityServiceInfo(List<AccessibilityServiceInfo> infos)1395 private void assertTestAccessibilityServiceInfo(List<AccessibilityServiceInfo> infos) { 1396 assertThat(infos.size()).isEqualTo(1); 1397 final AccessibilityServiceInfo retrievedInfo = infos.get(0); 1398 // Assert individual properties, since info.equals checks the ComponentName, which is 1399 // populated in the info instances belonging to the system process but not in those held by 1400 // the proxy. 1401 assertThat(retrievedInfo.isAccessibilityTool()).isTrue(); 1402 assertThat(retrievedInfo.packageNames).isEqualTo(new String[]{PACKAGE_1, PACKAGE_2}); 1403 assertThat(retrievedInfo.getInteractiveUiTimeoutMillis()).isEqualTo( 1404 INTERACTIVE_UI_TIMEOUT); 1405 assertThat(retrievedInfo.getNonInteractiveUiTimeoutMillis()).isEqualTo( 1406 NON_INTERACTIVE_UI_TIMEOUT); 1407 assertThat(retrievedInfo.notificationTimeout).isEqualTo(NOTIFICATION_TIMEOUT); 1408 assertThat(retrievedInfo.eventTypes).isEqualTo(AccessibilityEvent.TYPE_VIEW_CLICKED); 1409 assertThat(retrievedInfo.feedbackType).isEqualTo(FEEDBACK_AUDIBLE); 1410 assertThat(retrievedInfo.flags).isEqualTo(FLAG_INCLUDE_NOT_IMPORTANT_VIEWS); 1411 } 1412 setAccessibilityFocus(View view)1413 private void setAccessibilityFocus(View view) throws TimeoutException { 1414 if (!view.isAccessibilityFocused()) { 1415 sUiAutomation.executeAndWaitForEvent( 1416 () -> mProxiedVirtualDisplayActivity.runOnUiThread( 1417 () -> view.performAccessibilityAction( 1418 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)), 1419 filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUSED), TIMEOUT_MS); 1420 } 1421 } 1422 setInitialAccessibilityFocusAndWaitForProxyEvents(EditText proxyEditText)1423 private void setInitialAccessibilityFocusAndWaitForProxyEvents(EditText proxyEditText) { 1424 mA11yProxy.setEventFilter(filterWaitForAll( 1425 filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUSED), 1426 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED))); 1427 if (!proxyEditText.isAccessibilityFocused()) { 1428 proxyEditText.performAccessibilityAction( 1429 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); 1430 waitOn(mA11yProxy.mWaitObject, ()-> mA11yProxy.mReceivedEvent.get(), TIMEOUT_MS, 1431 "Expected event was not received within " + TIMEOUT_MS + " ms"); 1432 } 1433 1434 final AccessibilityNodeInfo a11yFocusedNode = mA11yProxy.findFocus( 1435 AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 1436 assertThat(a11yFocusedNode).isEqualTo(proxyEditText.createAccessibilityNodeInfo()); 1437 1438 } 1439 setInputFocusIfNeeded(View view)1440 private void setInputFocusIfNeeded(View view) throws TimeoutException { 1441 if (!view.isFocused()) { 1442 sUiAutomation.executeAndWaitForEvent( 1443 () -> mProxiedVirtualDisplayActivity.runOnUiThread(() -> { 1444 // Ensure state for taking input focus. 1445 view.setVisibility(View.VISIBLE); 1446 view.setFocusable(true); 1447 view.performAccessibilityAction( 1448 AccessibilityNodeInfo.ACTION_FOCUS, null); 1449 }) , 1450 filterForEventType(TYPE_VIEW_FOCUSED), TIMEOUT_MS); 1451 } 1452 } 1453 setTopFocusedDisplayIfNeeded(int displayId, Activity activity, List<AccessibilityWindowInfo> windows)1454 private void setTopFocusedDisplayIfNeeded(int displayId, Activity activity, 1455 List<AccessibilityWindowInfo> windows) throws TimeoutException { 1456 boolean isTopFocusedDisplay = false; 1457 for (AccessibilityWindowInfo window : windows) { 1458 // A focused window of the display means this display is the top-focused display. 1459 if (window.isFocused()) { 1460 isTopFocusedDisplay = true; 1461 break; 1462 } 1463 } 1464 if (!isTopFocusedDisplay) { 1465 final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo(); 1466 info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; 1467 sUiAutomation.setServiceInfo(info); 1468 sUiAutomation.executeAndWaitForEvent( 1469 () -> DisplayUtils.touchDisplay(sUiAutomation, displayId, activity.getTitle()), 1470 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED 1471 | WINDOWS_CHANGE_ACTIVE), TIMEOUT_MS); 1472 } 1473 } 1474 assertVirtualDisplayActivityExistsToProxy()1475 private void assertVirtualDisplayActivityExistsToProxy() { 1476 final List<AccessibilityWindowInfo> proxyWindows = mA11yProxy.getWindows(); 1477 assertThat(findWindowByTitleWithList( 1478 mProxiedVirtualDisplayActivityTitle, proxyWindows)).isNotNull(); 1479 } 1480 launchProxyConcurrentActivityOnDefaultDisplay( InstrumentedAccessibilityService service)1481 private AccessibilityKeyEventTestActivity launchProxyConcurrentActivityOnDefaultDisplay( 1482 InstrumentedAccessibilityService service) 1483 throws Exception { 1484 final AccessibilityKeyEventTestActivity nonProxyActivity = 1485 launchActivityAndWaitForItToBeOnscreen( 1486 sInstrumentation, sUiAutomation, 1487 mNonProxyActivityRule); 1488 final List<AccessibilityWindowInfo> serviceWindows = service.getWindows(); 1489 assertThat(findWindowByTitleWithList(getActivityTitle(sInstrumentation, 1490 nonProxyActivity), serviceWindows)).isNotNull(); 1491 return nonProxyActivity; 1492 } 1493 showTopWindowAndWaitForItToShowUp()1494 private View showTopWindowAndWaitForItToShowUp() throws TimeoutException { 1495 final WindowManager wm = 1496 mProxiedVirtualDisplayActivity.getSystemService(WindowManager.class); 1497 final Rect windowBounds = wm.getCurrentWindowMetrics().getBounds(); 1498 final WindowManager.LayoutParams paramsForTop = 1499 WindowCreationUtils.layoutParamsForWindowOnTop( 1500 sInstrumentation, mProxiedVirtualDisplayActivity, TOP_WINDOW_TITLE, 1501 WindowManager.LayoutParams.MATCH_PARENT, windowBounds.height() / 2); 1502 final Button button = new Button(mProxiedVirtualDisplayActivity); 1503 button.setText(sInstrumentation.getContext().getString(R.string.button1)); 1504 WindowCreationUtils.addWindowAndWaitForEvent(sUiAutomation, sInstrumentation, 1505 mProxiedVirtualDisplayActivity, 1506 button, paramsForTop, (event) -> (event.getEventType() == TYPE_WINDOWS_CHANGED) 1507 && (findWindowByTitleWithList(mProxiedVirtualDisplayActivityTitle, 1508 mA11yProxy.getWindows()) 1509 != null) 1510 && (findWindowByTitleWithList(TOP_WINDOW_TITLE, mA11yProxy.getWindows()) 1511 != null)); 1512 return button; 1513 } 1514 createVirtualDeviceAndLaunchVirtualDisplay()1515 private VirtualDisplay createVirtualDeviceAndLaunchVirtualDisplay() { 1516 sUiAutomation.adoptShellPermissionIdentity(ADD_TRUSTED_DISPLAY, CREATE_VIRTUAL_DEVICE); 1517 VirtualDisplay display; 1518 mVirtualDevice = mVirtualDeviceManager.createVirtualDevice( 1519 mFakeAssociationRule.getAssociationInfo().getId(), 1520 DEFAULT_VIRTUAL_DEVICE_PARAMS); 1521 // Values taken from StreamedAppClipboardTest 1522 mImageReader = ImageReader.newInstance(/* width= */ 100, /* height= */ 100, 1523 PixelFormat.RGBA_8888, /* maxImages= */ 1); 1524 display = mVirtualDevice.createVirtualDisplay( 1525 /* width= */ mImageReader.getWidth(), 1526 /* height= */ mImageReader.getHeight(), 1527 /* densityDpi= */ 240, 1528 mImageReader.getSurface(), 1529 0, 1530 Runnable::run, 1531 new VirtualDisplay.Callback(){}); 1532 return display; 1533 } 1534 launchActivityOnVirtualDisplay(int virtualDisplayId)1535 private AccessibilityKeyEventTestActivity launchActivityOnVirtualDisplay(int virtualDisplayId) 1536 throws Exception { 1537 final AccessibilityKeyEventTestActivity activityOnVirtualDisplay = 1538 launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(sInstrumentation, 1539 sUiAutomation, 1540 ProxyDisplayActivity.class, 1541 virtualDisplayId); 1542 return activityOnVirtualDisplay; 1543 } 1544 startActivityInSeparateProcess()1545 private void startActivityInSeparateProcess() throws TimeoutException { 1546 final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo(); 1547 info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; 1548 sUiAutomation.setServiceInfo(info); 1549 1550 // Specify the default display, else this may get launched on the virtual display. 1551 ActivityOptions options = ActivityOptions.makeBasic(); 1552 options.setLaunchDisplayId(Display.DEFAULT_DISPLAY); 1553 1554 sUiAutomation.executeAndWaitForEvent(() -> 1555 sInstrumentation.getContext().startActivity( 1556 mSeparateProcessActivityIntent, options.toBundle()), 1557 AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle(sUiAutomation, 1558 WINDOWS_CHANGE_ADDED, SEPARATE_PROCESS_ACTIVITY_TITLE), TIMEOUT_MS); 1559 } 1560 stopSeparateProcess()1561 private void stopSeparateProcess() { 1562 // Make sure to kill off the separate process. 1563 final List<String> allPackageNames = new ArrayList<>(); 1564 allPackageNames.add(SEPARATE_PROCESS_PACKAGE_NAME); 1565 final ActivityManager am = 1566 sInstrumentation.getContext().getSystemService(ActivityManager.class); 1567 for (final String pkgName : allPackageNames) { 1568 SystemUtil.runWithShellPermissionIdentity(() -> { 1569 am.forceStopPackage(pkgName); 1570 }); 1571 } 1572 } 1573 displayFocused(AccessibilityEvent event, int i)1574 private boolean displayFocused(AccessibilityEvent event, int i) { 1575 if (event.getEventType() == TYPE_WINDOWS_CHANGED 1576 && (event.getWindowChanges() & WINDOWS_CHANGE_FOCUSED) != 0) { 1577 List<AccessibilityWindowInfo> windows = 1578 sUiAutomation.getWindowsOnAllDisplays().valueAt(i); 1579 for (AccessibilityWindowInfo window : windows) { 1580 if (window.isFocused()) { 1581 return true; 1582 } 1583 } 1584 } 1585 return false; 1586 } 1587 registerProxyAndEnableTouchExploration()1588 private void registerProxyAndEnableTouchExploration() { 1589 registerProxyAndWaitForConnection(); 1590 assertVirtualDisplayActivityExistsToProxy(); 1591 final MyTouchExplorationStateChangeListener listener = 1592 new MyTouchExplorationStateChangeListener(/* initialState */ false); 1593 mProxyActivityA11yManager.addTouchExplorationStateChangeListener(listener); 1594 try { 1595 final List<AccessibilityServiceInfo> serviceInfos = 1596 mA11yProxy.getInstalledAndEnabledServices(); 1597 serviceInfos.get(0).flags |= 1598 AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; 1599 mA11yProxy.setInstalledAndEnabledServices(serviceInfos); 1600 waitOn(listener.mWaitObject, () -> listener.mAtomicBoolean.get(), TIMEOUT_MS, 1601 "Touch exploration state listener called"); 1602 assertThat(mProxyActivityA11yManager.isTouchExplorationEnabled()).isTrue(); 1603 } finally { 1604 mProxyActivityA11yManager.removeTouchExplorationStateChangeListener(listener); 1605 } 1606 } 1607 addAppStreamingRole()1608 private static void addAppStreamingRole() { 1609 runWithShellPermissionIdentity( 1610 () -> { 1611 CallbackFuture future = new CallbackFuture(); 1612 sRoleManager.addRoleHolderAsUser( 1613 DEVICE_PROFILE_APP_STREAMING, 1614 INSTRUMENTED_STREAM_ROLE_PACKAGE_NAME, 0, 1615 android.os.Process.myUserHandle(), 1616 sInstrumentation.getContext().getMainExecutor(), future); 1617 assertThat(future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 1618 }); 1619 } 1620 removeAppStreamingRole()1621 private static void removeAppStreamingRole() { 1622 runWithShellPermissionIdentity( 1623 () -> { 1624 CallbackFuture future = new CallbackFuture(); 1625 runWithShellPermissionIdentity(() -> 1626 sRoleManager.removeRoleHolderAsUser( 1627 DEVICE_PROFILE_APP_STREAMING, 1628 INSTRUMENTED_STREAM_ROLE_PACKAGE_NAME, 0, 1629 android.os.Process.myUserHandle(), 1630 sInstrumentation.getContext().getMainExecutor(), future)); 1631 assertThat(future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 1632 }); 1633 } 1634 1635 private static class CallbackFuture extends CompletableFuture<Boolean> 1636 implements Consumer<Boolean> { 1637 1638 @Override accept(Boolean successful)1639 public void accept(Boolean successful) { 1640 complete(successful); 1641 } 1642 } 1643 registerBroadcastReceiverForAction(String action)1644 private void registerBroadcastReceiverForAction(String action) { 1645 IntentFilter intentFilter = new IntentFilter(); 1646 intentFilter.addAction(action); 1647 sInstrumentation.getContext().registerReceiver(mReceiver, intentFilter, 1648 Context.RECEIVER_EXPORTED); 1649 } 1650 1651 class MyAccessibilityServicesStateChangeListener 1652 implements AccessibilityServicesStateChangeListener { 1653 AtomicBoolean mAtomicBoolean = new AtomicBoolean(false); 1654 Object mWaitObject = new Object(); 1655 int mExpectedInteractiveTimeout; 1656 int mExpectedNonInteractiveTimeout; 1657 MyAccessibilityServicesStateChangeListener(int expectedInteractiveTimeout, int expectedNonInteractiveTimeout)1658 MyAccessibilityServicesStateChangeListener(int expectedInteractiveTimeout, 1659 int expectedNonInteractiveTimeout) { 1660 mExpectedInteractiveTimeout = expectedInteractiveTimeout; 1661 mExpectedNonInteractiveTimeout = expectedNonInteractiveTimeout; 1662 } 1663 1664 @Override onAccessibilityServicesStateChanged(@onNull AccessibilityManager manager)1665 public void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager) { 1666 final int recommendedInteractiveUiTimeout = 1667 mProxyActivityA11yManager.getRecommendedTimeoutMillis( 1668 0, FLAG_CONTENT_CONTROLS); 1669 final int recommendedNonInteractiveUiTimeout = 1670 mProxyActivityA11yManager.getRecommendedTimeoutMillis( 1671 0, FLAG_CONTENT_TEXT); 1672 final boolean updatedTimeouts = 1673 recommendedInteractiveUiTimeout == mExpectedInteractiveTimeout 1674 && recommendedNonInteractiveUiTimeout == mExpectedNonInteractiveTimeout; 1675 synchronized (mWaitObject) { 1676 mAtomicBoolean.set(updatedTimeouts); 1677 mWaitObject.notifyAll(); 1678 } 1679 } 1680 } 1681 1682 1683 class MyTouchExplorationStateChangeListener implements 1684 TouchExplorationStateChangeListener { 1685 AtomicBoolean mAtomicBoolean; 1686 Object mWaitObject = new Object(); 1687 MyTouchExplorationStateChangeListener(boolean initialState)1688 MyTouchExplorationStateChangeListener(boolean initialState) { 1689 mAtomicBoolean = new AtomicBoolean(initialState); 1690 } 1691 @Override onTouchExplorationStateChanged(boolean enabled)1692 public void onTouchExplorationStateChanged(boolean enabled) { 1693 synchronized (mWaitObject) { 1694 mAtomicBoolean.set(enabled); 1695 mWaitObject.notifyAll(); 1696 } 1697 } 1698 } 1699 1700 /** 1701 * Class for testing AccessibilityDisplayProxy. 1702 */ 1703 class MyA11yProxy extends AccessibilityDisplayProxy { 1704 AtomicBoolean mReceivedEvent = new AtomicBoolean(); 1705 AtomicBoolean mConnected = new AtomicBoolean(); 1706 AtomicBoolean mInterrupted = new AtomicBoolean(); 1707 UiAutomation.AccessibilityEventFilter mEventFilter; 1708 final Object mWaitObject = new Object(); MyA11yProxy(int displayId, Executor executor, List<AccessibilityServiceInfo> infos)1709 MyA11yProxy(int displayId, Executor executor, List<AccessibilityServiceInfo> infos) { 1710 super(displayId, executor, infos); 1711 } 1712 1713 @Override onAccessibilityEvent(@onNull AccessibilityEvent event)1714 public void onAccessibilityEvent(@NonNull AccessibilityEvent event) { 1715 // TODO(b/276745079): Collect failing events. 1716 if (mEventFilter != null) { 1717 if (mEventFilter.accept(event)) { 1718 synchronized (mWaitObject) { 1719 mReceivedEvent.set(true); 1720 mWaitObject.notifyAll(); 1721 } 1722 } 1723 } 1724 } 1725 1726 @Override onProxyConnected()1727 public void onProxyConnected() { 1728 synchronized (mWaitObject) { 1729 mConnected.set(true); 1730 mWaitObject.notifyAll(); 1731 } 1732 } 1733 1734 @Override interrupt()1735 public void interrupt() { 1736 synchronized (mWaitObject) { 1737 mInterrupted.set(true); 1738 mWaitObject.notifyAll(); 1739 } 1740 } 1741 setEventFilter(@onNull UiAutomation.AccessibilityEventFilter filter)1742 public void setEventFilter(@NonNull UiAutomation.AccessibilityEventFilter filter) { 1743 mReceivedEvent.set(false); 1744 mEventFilter = filter; 1745 } 1746 } 1747 1748 public class ListenerChangeBroadcastReceiver extends BroadcastReceiver { 1749 private CountDownLatch mLatch; 1750 private String mExpectedAction; 1751 private boolean mExpectedEnabled; 1752 1753 private CharSequence mExpectedService; 1754 setLatchAndExpectedAction(CountDownLatch latch, String expectedAction)1755 public void setLatchAndExpectedAction(CountDownLatch latch, String expectedAction) { 1756 mLatch = latch; 1757 mExpectedAction = expectedAction; 1758 } setLatchAndExpectedEnabledResult(CountDownLatch latch, String expectedAction, boolean expectedEnabled)1759 public void setLatchAndExpectedEnabledResult(CountDownLatch latch, String expectedAction, 1760 boolean expectedEnabled) { 1761 setLatchAndExpectedAction(latch, expectedAction); 1762 mExpectedEnabled = expectedEnabled; 1763 } 1764 setLatchAndExpectedServiceResult(CountDownLatch latch, String action, CharSequence expectedService)1765 public void setLatchAndExpectedServiceResult(CountDownLatch latch, String action, 1766 CharSequence expectedService) { 1767 setLatchAndExpectedAction(latch, action); 1768 mExpectedService = expectedService; 1769 } 1770 1771 @Override onReceive(Context context, Intent intent)1772 public void onReceive(Context context, Intent intent) { 1773 if (intent != null) { 1774 switch (intent.getAction()) { 1775 case ACCESSIBILITY_SERVICE_STATE: 1776 if (mExpectedAction == null || mLatch == null 1777 || !mExpectedAction.equals(ACCESSIBILITY_SERVICE_STATE)) { 1778 return; 1779 } 1780 1781 if (mExpectedService == null) { 1782 mLatch.countDown(); 1783 return; 1784 } 1785 1786 if (intent.getExtras() != null) { 1787 CharSequence[] enabledServices = 1788 intent.getExtras().getCharSequenceArray(EXTRA_ENABLED_SERVICES); 1789 for (CharSequence service : enabledServices) { 1790 if (((String) service).endsWith((String) mExpectedService)) { 1791 mLatch.countDown(); 1792 return; 1793 } 1794 } 1795 } 1796 break; 1797 case TOUCH_EXPLORATION_STATE: 1798 if (mExpectedAction == null || mLatch == null 1799 || !mExpectedAction.equals(TOUCH_EXPLORATION_STATE)) { 1800 return; 1801 } 1802 if (intent.getExtras() != null) { 1803 if (mExpectedEnabled == intent.getExtras().getBoolean(EXTRA_ENABLED)) { 1804 mLatch.countDown(); 1805 } 1806 } 1807 break; 1808 } 1809 } 1810 } 1811 } 1812 } 1813