1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.accessibilityservice.cts; 18 19 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen; 20 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import static org.junit.Assert.assertThrows; 25 import static org.junit.Assume.assumeTrue; 26 27 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule; 28 import android.accessibility.cts.common.InstrumentedAccessibilityService; 29 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule; 30 import android.accessibilityservice.AccessibilityService; 31 import android.accessibilityservice.AccessibilityServiceInfo; 32 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity; 33 import android.accessibilityservice.cts.utils.ActivityLaunchUtils; 34 import android.accessibilityservice.cts.utils.AsyncUtils; 35 import android.accessibilityservice.cts.utils.DisplayUtils; 36 import android.app.Activity; 37 import android.app.Instrumentation; 38 import android.app.UiAutomation; 39 import android.content.Context; 40 import android.content.pm.PackageManager; 41 import android.graphics.PixelFormat; 42 import android.graphics.Rect; 43 import android.hardware.display.DisplayManager; 44 import android.os.Binder; 45 import android.platform.test.annotations.Presubmit; 46 import android.platform.test.annotations.RequiresFlagsEnabled; 47 import android.platform.test.flag.junit.CheckFlagsRule; 48 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 49 import android.server.wm.CtsWindowInfoUtils; 50 import android.util.SparseArray; 51 import android.view.Display; 52 import android.view.SurfaceControl; 53 import android.view.SurfaceControlViewHost; 54 import android.view.WindowManager; 55 import android.view.accessibility.AccessibilityNodeInfo; 56 import android.view.accessibility.AccessibilityWindowInfo; 57 import android.widget.Button; 58 import android.widget.FrameLayout; 59 import android.window.WindowInfosListenerForTest; 60 61 import androidx.test.filters.FlakyTest; 62 import androidx.test.platform.app.InstrumentationRegistry; 63 import androidx.test.runner.AndroidJUnit4; 64 65 import com.android.compatibility.common.util.CddTest; 66 67 import org.junit.AfterClass; 68 import org.junit.Before; 69 import org.junit.BeforeClass; 70 import org.junit.Rule; 71 import org.junit.Test; 72 import org.junit.rules.RuleChain; 73 import org.junit.runner.RunWith; 74 75 import java.util.List; 76 import java.util.concurrent.BlockingQueue; 77 import java.util.concurrent.Executor; 78 import java.util.concurrent.Executors; 79 import java.util.concurrent.LinkedBlockingQueue; 80 import java.util.concurrent.TimeUnit; 81 import java.util.concurrent.TimeoutException; 82 import java.util.function.Function; 83 import java.util.function.IntConsumer; 84 import java.util.function.Predicate; 85 86 // Test that an AccessibilityService can display an accessibility overlay 87 @RunWith(AndroidJUnit4.class) 88 @CddTest(requirements = {"3.10/C-1-1,C-1-2"}) 89 @Presubmit 90 public class AccessibilityOverlayTest { 91 92 private static Instrumentation sInstrumentation; 93 private static UiAutomation sUiAutomation; 94 InstrumentedAccessibilityService mService; 95 96 private InstrumentedAccessibilityServiceTestRule<StubAccessibilityButtonService> mServiceRule = 97 new InstrumentedAccessibilityServiceTestRule<>(StubAccessibilityButtonService.class); 98 99 private AccessibilityDumpOnFailureRule mDumpOnFailureRule = 100 new AccessibilityDumpOnFailureRule(); 101 102 private CheckFlagsRule mCheckFlagsRule = 103 DeviceFlagsValueProvider.createCheckFlagsRule(sUiAutomation); 104 105 @Rule 106 public final RuleChain mRuleChain = 107 RuleChain.outerRule(mServiceRule).around(mDumpOnFailureRule).around(mCheckFlagsRule); 108 109 private Executor mExecutor = Executors.newSingleThreadExecutor(); 110 private ResultCapturingCallback mCallback = new ResultCapturingCallback(); 111 112 @BeforeClass oneTimeSetUp()113 public static void oneTimeSetUp() { 114 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 115 sUiAutomation = 116 sInstrumentation.getUiAutomation( 117 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 118 AccessibilityServiceInfo info = sUiAutomation.getServiceInfo(); 119 info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; 120 sUiAutomation.setServiceInfo(info); 121 } 122 123 @AfterClass postTestTearDown()124 public static void postTestTearDown() { 125 sUiAutomation.destroy(); 126 } 127 128 @Before setUp()129 public void setUp() { 130 mService = mServiceRule.getService(); 131 } 132 133 @Test testA11yServiceShowsOverlay_shouldAppear()134 public void testA11yServiceShowsOverlay_shouldAppear() throws Exception { 135 final String overlayTitle = "Overlay title"; 136 sUiAutomation.executeAndWaitForEvent( 137 () -> 138 mService.runOnServiceSync( 139 () -> { 140 addOverlayWindow(mService, overlayTitle); 141 }), 142 (event) -> findOverlayWindow(Display.DEFAULT_DISPLAY) != null, 143 AsyncUtils.DEFAULT_TIMEOUT_MS); 144 145 assertThat(findOverlayWindow(Display.DEFAULT_DISPLAY).getTitle().toString()) 146 .isEqualTo(overlayTitle); 147 } 148 149 @Test testA11yServiceShowsOverlayOnVirtualDisplay_shouldAppear()150 public void testA11yServiceShowsOverlayOnVirtualDisplay_shouldAppear() throws Exception { 151 addOverlayToVirtualDisplayAndCheck( 152 display -> mService.createDisplayContext(display), /* expectException= */ false); 153 } 154 155 @Test testA11yServiceShowOverlay_withDerivedWindowContext_shouldAppear()156 public void testA11yServiceShowOverlay_withDerivedWindowContext_shouldAppear() 157 throws Exception { 158 addOverlayToVirtualDisplayAndCheck( 159 display -> 160 mService.createDisplayContext(display) 161 .createWindowContext( 162 WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, 163 /* options= */ null), 164 /* expectException= */ false); 165 } 166 167 @Test testA11yServiceShowOverlay_withDerivedWindowContextWithDisplay_shouldAppear()168 public void testA11yServiceShowOverlay_withDerivedWindowContextWithDisplay_shouldAppear() 169 throws Exception { 170 addOverlayToVirtualDisplayAndCheck( 171 display -> 172 mService.createWindowContext( 173 display, 174 WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, 175 /* options= */ null), 176 /* expectException= */ false); 177 } 178 179 @Test testA11yServiceShowOverlay_withDerivedWindowContextWithTypeMismatch_throwException()180 public void testA11yServiceShowOverlay_withDerivedWindowContextWithTypeMismatch_throwException() 181 throws Exception { 182 addOverlayToVirtualDisplayAndCheck( 183 display -> 184 mService.createWindowContext( 185 display, 186 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, 187 /* options= */ null), 188 /* expectException= */ true); 189 } 190 191 @Test testA11yServiceShowsDisplayEmbeddedOverlayWithoutCallback_shouldAppearAndDisappear()192 public void testA11yServiceShowsDisplayEmbeddedOverlayWithoutCallback_shouldAppearAndDisappear() 193 throws Exception { 194 // Set up a view that will become our accessibility overlay. 195 final String overlayTitle = "Overlay title"; 196 final SurfaceControl sc = createDisplayOverlay(overlayTitle); 197 attachOverlayToDisplayAndCheck(sc, overlayTitle, null, null); 198 removeOverlayAndCheck(sc, overlayTitle); 199 } 200 201 @Test 202 @RequiresFlagsEnabled(com.android.server.accessibility.Flags.FLAG_CLEANUP_A11Y_OVERLAYS) testEmbeddedDisplayOverlayWithServiceExit_shouldAppearAndDisappear()203 public void testEmbeddedDisplayOverlayWithServiceExit_shouldAppearAndDisappear() 204 throws Exception { 205 // Set up a view that will become our accessibility overlay. 206 final String overlayTitle = "Overlay title"; 207 final SurfaceControl sc = createDisplayOverlay(overlayTitle); 208 attachOverlayToDisplayAndCheck(sc, overlayTitle, null, null); 209 disableServiceAndCheckForOverlay(overlayTitle); 210 } 211 212 @Test 213 @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS) testA11yServiceShowsDisplayEmbeddedOverlayWithCallback_shouldAppearAndDisappear()214 public void testA11yServiceShowsDisplayEmbeddedOverlayWithCallback_shouldAppearAndDisappear() 215 throws Exception { 216 // Set up a view that will become our accessibility overlay. 217 final String overlayTitle = "Overlay title"; 218 final SurfaceControl sc = createDisplayOverlay(overlayTitle); 219 attachOverlayToDisplayAndCheck(sc, overlayTitle, mExecutor, mCallback); 220 mCallback.assertCallbackReceived(AccessibilityService.OVERLAY_RESULT_SUCCESS); 221 removeOverlayAndCheck(sc, overlayTitle); 222 } 223 224 @Test 225 @FlakyTest testA11yServiceShowsWindowEmbeddedOverlayWithoutCallback_shouldAppearAndDisappear()226 public void testA11yServiceShowsWindowEmbeddedOverlayWithoutCallback_shouldAppearAndDisappear() 227 throws Exception { 228 final String overlayTitle = "App Overlay title"; 229 Activity activity = showActivity(); 230 try { 231 SurfaceControl sc = doOverlayWindowTest(overlayTitle, null, null); 232 removeOverlayAndCheck(sc, overlayTitle); 233 } finally { 234 if (activity != null) { 235 activity.finish(); 236 } 237 } 238 } 239 240 @Test 241 @FlakyTest 242 @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS) testA11yServiceShowsWindowEmbeddedOverlayWithCallback_shouldAppearAndDisappear()243 public void testA11yServiceShowsWindowEmbeddedOverlayWithCallback_shouldAppearAndDisappear() 244 throws Exception { 245 final String overlayTitle = "App Overlay title"; 246 Activity activity = showActivity(); 247 try { 248 SurfaceControl sc = doOverlayWindowTest(overlayTitle, mExecutor, mCallback); 249 mCallback.assertCallbackReceived(AccessibilityService.OVERLAY_RESULT_SUCCESS); 250 removeOverlayAndCheck(sc, overlayTitle); 251 } finally { 252 if (activity != null) { 253 activity.finish(); 254 } 255 } 256 } 257 258 @Test 259 @FlakyTest 260 @RequiresFlagsEnabled(com.android.server.accessibility.Flags.FLAG_CLEANUP_A11Y_OVERLAYS) testEmbeddedWindowOverlayWithServiceExit_shouldAppearAndDisappear()261 public void testEmbeddedWindowOverlayWithServiceExit_shouldAppearAndDisappear() 262 throws Exception { 263 final String overlayTitle = "App Overlay title"; 264 Activity activity = showActivity(); 265 try { 266 doOverlayWindowTest(overlayTitle, null, null); 267 disableServiceAndCheckForOverlay(overlayTitle); 268 } finally { 269 if (activity != null) { 270 activity.finish(); 271 } 272 } 273 } 274 doOverlayWindowTest( String overlayTitle, Executor executor, ResultCapturingCallback callback)275 private SurfaceControl doOverlayWindowTest( 276 String overlayTitle, Executor executor, ResultCapturingCallback callback) 277 throws Exception { 278 final StringBuilder timeoutExceptionRecords = new StringBuilder(); 279 try { 280 final Display display = 281 mService.getSystemService(DisplayManager.class) 282 .getDisplay(Display.DEFAULT_DISPLAY); 283 final Context context = mService.createDisplayContext(display); 284 final SurfaceControlViewHost viewHost = 285 mService.getOnService( 286 () -> new SurfaceControlViewHost(context, display, new Binder())); 287 final SurfaceControl sc = viewHost.getSurfacePackage().getSurfaceControl(); 288 final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); 289 transaction.setVisibility(sc, true).apply(); 290 transaction.close(); 291 292 // Create an accessibility overlay hosting a FrameLayout with the same size 293 // as the activity's root node bounds. 294 final AccessibilityNodeInfo activityRootNode = sUiAutomation.getRootInActiveWindow(); 295 final Rect activityRootNodeBounds = new Rect(); 296 activityRootNode.getBoundsInWindow(activityRootNodeBounds); 297 final WindowManager.LayoutParams overlayParams = new WindowManager.LayoutParams(); 298 299 overlayParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; 300 overlayParams.format = PixelFormat.TRANSLUCENT; 301 overlayParams.setTitle(overlayTitle); 302 overlayParams.accessibilityTitle = overlayTitle; 303 overlayParams.height = activityRootNodeBounds.height(); 304 overlayParams.width = activityRootNodeBounds.width(); 305 final FrameLayout overlayLayout = new FrameLayout(context); 306 mService.runOnServiceSync(() -> viewHost.setView(overlayLayout, overlayParams)); 307 308 // Add a new Button view inside the overlay's FrameLayout, directly on top of 309 // the window-space bounds of a node within the activity. 310 final AccessibilityNodeInfo activityNodeToDrawOver = 311 activityRootNode 312 .findAccessibilityNodeInfosByViewId( 313 "android.accessibilityservice.cts:id/button1") 314 .get(0); 315 final Rect activityNodeToDrawOverBounds = new Rect(); 316 activityNodeToDrawOver.getBoundsInWindow(activityNodeToDrawOverBounds); 317 Button overlayButton = new Button(context); 318 final String buttonText = "overlay button"; 319 overlayButton.setText(buttonText); 320 overlayButton.setX(activityNodeToDrawOverBounds.left); 321 overlayButton.setY(activityNodeToDrawOverBounds.top); 322 mService.runOnServiceSync( 323 () -> 324 overlayLayout.addView( 325 overlayButton, 326 new FrameLayout.LayoutParams( 327 activityNodeToDrawOverBounds.width(), 328 activityNodeToDrawOverBounds.height()))); 329 330 // Attach the SurfaceControlViewHost as an accessibility overlay to the activity window. 331 sUiAutomation.executeAndWaitForEvent( 332 () -> { 333 if (callback != null && executor != null) { 334 mService.attachAccessibilityOverlayToWindow( 335 activityRootNode.getWindowId(), sc, executor, callback); 336 } else { 337 mService.attachAccessibilityOverlayToWindow( 338 activityRootNode.getWindowId(), sc); 339 } 340 }, 341 (event) -> { 342 // Wait until the overlay window is added 343 final AccessibilityWindowInfo overlayWindow = 344 ActivityLaunchUtils.findWindowByTitle(sUiAutomation, overlayTitle); 345 if (overlayWindow == null 346 || overlayWindow.getType() 347 != AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) { 348 return false; 349 } 350 // Refresh the activity's button node to ensure the AccessibilityNodeInfo 351 // is the latest 352 activityNodeToDrawOver.refresh(); 353 354 // Wait until overlay is drawn on correct location 355 final AccessibilityNodeInfo overlayButtonNode = 356 overlayWindow 357 .getRoot() 358 .findAccessibilityNodeInfosByText(buttonText) 359 .get(0); 360 final Rect expectedBoundsInWindow = new Rect(); 361 final Rect actualBoundsInWindow = new Rect(); 362 activityNodeToDrawOver.getBoundsInWindow(expectedBoundsInWindow); 363 overlayButtonNode.getBoundsInWindow(actualBoundsInWindow); 364 365 final Rect expectedBoundsInScreen = new Rect(); 366 final Rect actualBoundsInScreen = new Rect(); 367 activityNodeToDrawOver.getBoundsInScreen(expectedBoundsInScreen); 368 overlayButtonNode.getBoundsInScreen(actualBoundsInScreen); 369 370 // Stores the related information including event, bounds 371 // as a timeout exception record. 372 timeoutExceptionRecords.append( 373 String.format( 374 """ 375 { Received event: %s } 376 Expected bounds in window: %s, actual bounds in window: %s 377 Expected bounds in screen: %s, actual bounds in screen: %s 378 """, 379 event, 380 expectedBoundsInWindow, 381 actualBoundsInWindow, 382 expectedBoundsInScreen, 383 actualBoundsInScreen)); 384 385 // The overlay button should have the same window-space and screen-space 386 // bounds as the view in the activity, as configured above. 387 return actualBoundsInWindow.equals(expectedBoundsInWindow) 388 && actualBoundsInScreen.equals(expectedBoundsInScreen); 389 }, 390 AsyncUtils.DEFAULT_TIMEOUT_MS); 391 392 checkTrustedOverlayExists(overlayTitle); 393 return sc; 394 } catch (TimeoutException timeout) { 395 throw new TimeoutException( 396 timeout.getMessage() 397 + "\n\nTimeout exception records : \n" 398 + timeoutExceptionRecords); 399 } 400 } 401 checkTrustedOverlayExists(String overlayTitle)402 private void checkTrustedOverlayExists(String overlayTitle) throws Exception { 403 try { 404 Predicate<List<WindowInfosListenerForTest.WindowInfo>> windowPredicate = 405 windows -> 406 windows.stream() 407 .anyMatch( 408 window -> 409 window.name.contains(overlayTitle) 410 && window.isTrustedOverlay); 411 assertWithMessage("Expected to find trusted overlay window") 412 .that( 413 CtsWindowInfoUtils.waitForWindowInfos( 414 windowPredicate, 415 AsyncUtils.DEFAULT_TIMEOUT_MS, 416 TimeUnit.MILLISECONDS, 417 sUiAutomation)) 418 .isTrue(); 419 } finally { 420 sUiAutomation.dropShellPermissionIdentity(); 421 } 422 } 423 addOverlayWindow(Context context, String overlayTitle)424 private void addOverlayWindow(Context context, String overlayTitle) { 425 final Button button = new Button(context); 426 button.setText("Button"); 427 final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); 428 params.width = WindowManager.LayoutParams.MATCH_PARENT; 429 params.height = WindowManager.LayoutParams.MATCH_PARENT; 430 params.flags = 431 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 432 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 433 params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; 434 params.setTitle(overlayTitle); 435 context.getSystemService(WindowManager.class).addView(button, params); 436 } 437 findOverlayWindow(int displayId)438 private AccessibilityWindowInfo findOverlayWindow(int displayId) { 439 final SparseArray<List<AccessibilityWindowInfo>> allWindows = 440 sUiAutomation.getWindowsOnAllDisplays(); 441 final List<AccessibilityWindowInfo> windows = allWindows.get(displayId); 442 443 if (windows != null) { 444 for (AccessibilityWindowInfo window : windows) { 445 if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) { 446 return window; 447 } 448 } 449 } 450 return null; 451 } 452 addOverlayToVirtualDisplayAndCheck( Function<Display, Context> createContext, boolean expectException)453 private void addOverlayToVirtualDisplayAndCheck( 454 Function<Display, Context> createContext, boolean expectException) throws Exception { 455 assumeTrue( 456 "Device does not support activities on secondary displays", 457 sInstrumentation 458 .getContext() 459 .getPackageManager() 460 .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); 461 462 try (DisplayUtils.VirtualDisplaySession displaySession = 463 new DisplayUtils.VirtualDisplaySession()) { 464 final Display newDisplay = 465 displaySession.createDisplayWithDefaultDisplayMetricsAndWait( 466 mService, /* isPrivate= */ false); 467 final int displayId = newDisplay.getDisplayId(); 468 final String overlayTitle = "Overlay title on virtualDisplay"; 469 470 // Create an initial activity window on the virtual display to ensure that 471 // AccessibilityWindowManager is tracking windows for the display. 472 launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen( 473 sInstrumentation, 474 sUiAutomation, 475 AccessibilityWindowQueryActivity.class, 476 displayId); 477 478 if (expectException) { 479 assertThrows( 480 IllegalArgumentException.class, 481 () -> addOverlayWindow(createContext.apply(newDisplay), overlayTitle)); 482 assertThat(findOverlayWindow(displayId)).isNull(); 483 } else { 484 sUiAutomation.executeAndWaitForEvent( 485 () -> 486 mService.runOnServiceSync( 487 () -> 488 addOverlayWindow( 489 createContext.apply(newDisplay), 490 overlayTitle)), 491 (event) -> findOverlayWindow(displayId) != null, 492 AsyncUtils.DEFAULT_TIMEOUT_MS); 493 494 assertThat(findOverlayWindow(displayId).getTitle()).isEqualTo(overlayTitle); 495 } 496 } 497 } 498 499 class ResultCapturingCallback implements IntConsumer { 500 501 private BlockingQueue<Integer> mResults = new LinkedBlockingQueue<>(); 502 503 @Override accept(int result)504 public void accept(int result) { 505 mResults.offer(result); 506 } 507 assertCallbackReceived(int expected)508 public void assertCallbackReceived(int expected) { 509 Integer received = null; 510 try { 511 received = mResults.poll(AsyncUtils.DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 512 } catch (InterruptedException e) { 513 throw new RuntimeException(e); 514 } 515 assertThat(expected).isEqualTo(received); 516 } 517 } 518 createDisplayOverlay(String overlayTitle)519 private SurfaceControl createDisplayOverlay(String overlayTitle) { 520 final Button button = new Button(mService); 521 button.setText("Button"); 522 final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); 523 params.width = 10; 524 params.height = 10; 525 params.flags = 526 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 527 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 528 params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; 529 params.setTitle(overlayTitle); 530 531 // Create a SurfaceControlViewHost to host the overlay. 532 Display display = 533 mService.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY); 534 Context context = mService.createDisplayContext(display); 535 final SurfaceControl sc = 536 mService.getOnService( 537 () -> { 538 SurfaceControlViewHost scvh = 539 new SurfaceControlViewHost(context, display, new Binder()); 540 scvh.setView(button, params); 541 return scvh.getSurfacePackage().getSurfaceControl(); 542 }); 543 544 // Make sure the overlay is visible. 545 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 546 t.setVisibility(sc, true).setLayer(sc, 1).apply(); 547 return sc; 548 } 549 attachOverlayToDisplayAndCheck( SurfaceControl sc, String overlayTitle, Executor executor, IntConsumer callback)550 private void attachOverlayToDisplayAndCheck( 551 SurfaceControl sc, String overlayTitle, Executor executor, IntConsumer callback) 552 throws Exception { 553 sUiAutomation.executeAndWaitForEvent( 554 () -> { 555 if (executor != null && callback != null) { 556 mService.attachAccessibilityOverlayToDisplay( 557 Display.DEFAULT_DISPLAY, sc, executor, callback); 558 } else { 559 mService.attachAccessibilityOverlayToDisplay(Display.DEFAULT_DISPLAY, sc); 560 } 561 }, 562 (event) -> 563 ActivityLaunchUtils.findWindowByTitle(sUiAutomation, overlayTitle) != null, 564 AsyncUtils.DEFAULT_TIMEOUT_MS); 565 checkTrustedOverlayExists(overlayTitle); 566 } 567 removeOverlayAndCheck(SurfaceControl sc, String overlayTitle)568 private void removeOverlayAndCheck(SurfaceControl sc, String overlayTitle) throws Exception { 569 // Remove the overlay. 570 sUiAutomation.executeAndWaitForEvent( 571 () -> { 572 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 573 t.reparent(sc, null).apply(); 574 t.close(); 575 sc.release(); 576 }, 577 (event) -> 578 ActivityLaunchUtils.findWindowByTitle(sUiAutomation, overlayTitle) == null, 579 AsyncUtils.DEFAULT_TIMEOUT_MS); 580 } 581 582 /** Disables the service and makes sure the overlay is not on the screen. */ disableServiceAndCheckForOverlay(String overlayTitle)583 private void disableServiceAndCheckForOverlay(String overlayTitle) throws Exception { 584 sUiAutomation.executeAndWaitForEvent( 585 () -> mService.disableSelfAndRemove(), 586 (event) -> 587 ActivityLaunchUtils.findWindowByTitle(sUiAutomation, overlayTitle) == null, 588 AsyncUtils.DEFAULT_TIMEOUT_MS); 589 } 590 showActivity()591 private Activity showActivity() throws Exception { 592 final Activity activity = 593 launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen( 594 sInstrumentation, 595 sUiAutomation, 596 AccessibilityWindowQueryActivity.class, 597 Display.DEFAULT_DISPLAY); 598 return activity; 599 } 600 } 601