• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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