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