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