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