1 /*
2  * Copyright (C) 2012 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.view.accessibility.cts;
18 
19 import static android.accessibility.cts.common.InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertSame;
26 import static org.junit.Assert.assertThrows;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 
30 
31 import android.Manifest;
32 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
33 import android.accessibility.cts.common.InstrumentedAccessibilityService;
34 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
35 import android.accessibilityservice.AccessibilityServiceInfo;
36 import android.app.Instrumentation;
37 import android.app.Service;
38 import android.app.UiAutomation;
39 import android.content.Context;
40 import android.content.pm.PackageManager;
41 import android.content.pm.ServiceInfo;
42 import android.os.Handler;
43 import android.platform.test.annotations.AsbSecurityTest;
44 import android.platform.test.annotations.RequiresFlagsEnabled;
45 import android.platform.test.flag.junit.CheckFlagsRule;
46 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
47 import android.view.InputEvent;
48 import android.view.MotionEvent;
49 import android.view.accessibility.AccessibilityEvent;
50 import android.view.accessibility.AccessibilityManager;
51 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
52 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
53 import android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener;
54 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
55 import android.view.accessibility.Flags;
56 
57 import androidx.test.InstrumentationRegistry;
58 import androidx.test.runner.AndroidJUnit4;
59 
60 import com.android.compatibility.common.util.PollingCheck;
61 import com.android.compatibility.common.util.SettingsStateChangerRule;
62 import com.android.compatibility.common.util.SystemUtil;
63 import com.android.compatibility.common.util.TestUtils;
64 import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
65 
66 import org.junit.Before;
67 import org.junit.Rule;
68 import org.junit.Test;
69 import org.junit.rules.RuleChain;
70 import org.junit.runner.RunWith;
71 
72 import java.io.IOException;
73 import java.lang.reflect.Field;
74 import java.lang.reflect.InvocationTargetException;
75 import java.lang.reflect.Method;
76 import java.util.List;
77 import java.util.concurrent.atomic.AtomicBoolean;
78 
79 /**
80  * Class for testing {@link AccessibilityManager}.
81  */
82 @RunWith(AndroidJUnit4.class)
83 public class AccessibilityManagerTest extends StsExtraBusinessLogicTestCase {
84 
85     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
86             new AccessibilityDumpOnFailureRule();
87 
88     private InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
89             mSpeakingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
90                     SpeakingAccessibilityService.class, false);
91 
92     private InstrumentedAccessibilityServiceTestRule<VibratingAccessibilityService>
93             mVibratingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
94                     VibratingAccessibilityService.class, false);
95 
96     private InstrumentedAccessibilityServiceTestRule<SpeakingAndVibratingAccessibilityService>
97             mSpeakingAndVibratingAccessibilityServiceRule =
98             new InstrumentedAccessibilityServiceTestRule<>(
99                     SpeakingAndVibratingAccessibilityService.class, false);
100 
101     private InstrumentedAccessibilityServiceTestRule<NoFeedbackAccessibilityService>
102             mNoFeedbackAccessibilityServiceRule =
103             new InstrumentedAccessibilityServiceTestRule<>(
104                     NoFeedbackAccessibilityService.class, false);
105 
106     private static final Instrumentation sInstrumentation =
107             InstrumentationRegistry.getInstrumentation();
108 
109     private static final String SPEAKING_ACCESSIBLITY_SERVICE_NAME =
110             "android.view.accessibility.cts.SpeakingAccessibilityService";
111 
112     private static final String VIBRATING_ACCESSIBLITY_SERVICE_NAME =
113             "android.view.accessibility.cts.VibratingAccessibilityService";
114 
115     private static final String MULTIPLE_FEEDBACK_TYPES_ACCESSIBILITY_SERVICE_NAME =
116             "android.view.accessibility.cts.SpeakingAndVibratingAccessibilityService";
117 
118     private static final String NO_FEEDBACK_ACCESSIBILITY_SERVICE_NAME =
119             "android.view.accessibility.cts.NoFeedbackAccessibilityService";
120 
121     public static final String ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS =
122             "accessibility_non_interactive_ui_timeout_ms";
123 
124     public static final String ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS =
125             "accessibility_interactive_ui_timeout_ms";
126     private static final String ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT =
127             "enabled_accessibility_audio_description_by_default";
128 
129     private final SettingsStateChangerRule mAudioDescriptionSetterRule =
130             new SettingsStateChangerRule(
131                     sInstrumentation.getContext(),
132                     ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT,
133                     "0");
134 
135     @Rule
136     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
137 
138     @Rule
139     public final RuleChain mRuleChain = RuleChain
140             // SettingsStateChangerRule will suppress accessibility services, so it should be
141             // executed before enabling a11y services and after disabling a11y services.
142             .outerRule(mAudioDescriptionSetterRule)
143             .around(mNoFeedbackAccessibilityServiceRule)
144             .around(mSpeakingAndVibratingAccessibilityServiceRule)
145             .around(mVibratingAccessibilityServiceRule)
146             .around(mSpeakingAccessibilityServiceRule)
147             // Inner rule capture failure and dump data before finishing activity and a11y service
148             .around(mDumpOnFailureRule);
149 
150     private AccessibilityManager mAccessibilityManager;
151 
152     private Context mTargetContext;
153 
154     private Handler mHandler;
155 
156     @Before
setUp()157     public void setUp() throws Exception {
158         mAccessibilityManager = (AccessibilityManager)
159                 sInstrumentation.getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
160         mTargetContext = sInstrumentation.getTargetContext();
161         mHandler = new Handler(mTargetContext.getMainLooper());
162         // In case the test runner started a UiAutomation, destroy it to start with a clean slate.
163         sInstrumentation.getUiAutomation().destroy();
164         InstrumentedAccessibilityService.disableAllServices();
165     }
166 
167     @Test
testAddAndRemoveAccessibilityStateChangeListener()168     public void testAddAndRemoveAccessibilityStateChangeListener() throws Exception {
169         AccessibilityStateChangeListener listener = (state) -> {
170                 /* do nothing */
171         };
172         assertTrue(mAccessibilityManager.addAccessibilityStateChangeListener(listener));
173         assertTrue(mAccessibilityManager.removeAccessibilityStateChangeListener(listener));
174         assertFalse(mAccessibilityManager.removeAccessibilityStateChangeListener(listener));
175     }
176 
177     @Test
testAddAndRemoveTouchExplorationStateChangeListener()178     public void testAddAndRemoveTouchExplorationStateChangeListener() throws Exception {
179         TouchExplorationStateChangeListener listener = (boolean enabled) -> {
180             // Do nothing.
181         };
182         assertTrue(mAccessibilityManager.addTouchExplorationStateChangeListener(listener));
183         assertTrue(mAccessibilityManager.removeTouchExplorationStateChangeListener(listener));
184         assertFalse(mAccessibilityManager.removeTouchExplorationStateChangeListener(listener));
185     }
186 
187     @Test
testAddAndRemoveAudioDescriptionRequestedChangeListener()188     public void testAddAndRemoveAudioDescriptionRequestedChangeListener() throws Exception {
189         AudioDescriptionRequestedChangeListener listener = (boolean enabled) -> {
190             // Do nothing.
191         };
192         mAccessibilityManager.addAudioDescriptionRequestedChangeListener(
193                 mTargetContext.getMainExecutor(), listener);
194         assertTrue(
195                 mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(listener));
196         assertFalse(
197                 mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(listener));
198     }
199 
200     @Test
testIsTouchExplorationEnabled()201     public void testIsTouchExplorationEnabled() throws Exception {
202         mSpeakingAccessibilityServiceRule.enableService();
203         mVibratingAccessibilityServiceRule.enableService();
204         new PollingCheck() {
205             @Override
206             protected boolean check() {
207                 return mAccessibilityManager.isTouchExplorationEnabled();
208             }
209         }.run();
210     }
211 
212     @Test
testRemoveAccessibilityServicesStateChangeListener()213     public void testRemoveAccessibilityServicesStateChangeListener() throws Exception {
214         AccessibilityServicesStateChangeListener listener = (state) -> {
215             /* do nothing */
216         };
217         mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener);
218 
219         assertTrue(mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener));
220         assertFalse(mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener));
221     }
222 
223     @AsbSecurityTest(cveBugId = {309426390})
224     @Test
testInjectInputEventToInputFilter_throwsWithoutInjectEventsPermission()225     public void testInjectInputEventToInputFilter_throwsWithoutInjectEventsPermission()
226             throws Exception {
227         // Ensure the test itself doesn't have INJECT_EVENTS permission before
228         // calling the method that requires it and expecting failure.
229         assertThat(sInstrumentation.getContext().checkSelfPermission(
230                 Manifest.permission.INJECT_EVENTS)).isEqualTo(PackageManager.PERMISSION_DENIED);
231 
232         // Use reflection to directly invoke IAccessibilityManager#injectInputEventToInputFilter.
233         final AccessibilityManager accessibilityManager = (AccessibilityManager)
234                 sInstrumentation.getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
235         final Field serviceField = AccessibilityManager.class.getDeclaredField("mService");
236         serviceField.setAccessible(true);
237         final Method injectInputEventToInputFilter =
238                 Class.forName("android.view.accessibility.IAccessibilityManager")
239                         .getDeclaredMethod("injectInputEventToInputFilter", InputEvent.class);
240 
241         final InvocationTargetException exception = assertThrows(InvocationTargetException.class,
242                 () -> injectInputEventToInputFilter.invoke(
243                         serviceField.get(accessibilityManager),
244                         MotionEvent.obtain(0, 0, 0, 0, 0, 0)));
245         assertThat(exception).hasCauseThat().isInstanceOf(SecurityException.class);
246         assertThat(exception).hasCauseThat().hasMessageThat().contains("INJECT_EVENTS");
247     }
248 
249     @Test
testGetInstalledAccessibilityServicesList()250     public void testGetInstalledAccessibilityServicesList() throws Exception {
251         List<AccessibilityServiceInfo> installedServices =
252                 mAccessibilityManager.getInstalledAccessibilityServiceList();
253         assertFalse("There must be at least one installed service.", installedServices.isEmpty());
254         boolean speakingServiceInstalled = false;
255         boolean vibratingServiceInstalled = false;
256         final int serviceCount = installedServices.size();
257         for (int i = 0; i < serviceCount; i++) {
258             AccessibilityServiceInfo installedService = installedServices.get(i);
259             ServiceInfo serviceInfo = installedService.getResolveInfo().serviceInfo;
260             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
261                     && SPEAKING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
262                 speakingServiceInstalled = true;
263             }
264             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
265                     && VIBRATING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
266                 vibratingServiceInstalled = true;
267             }
268         }
269         assertTrue("The speaking service should be installed.", speakingServiceInstalled);
270         assertTrue("The vibrating service should be installed.", vibratingServiceInstalled);
271     }
272 
273     @Test
testGetEnabledAccessibilityServiceList()274     public void testGetEnabledAccessibilityServiceList() throws Exception {
275         mSpeakingAccessibilityServiceRule.enableService();
276         mVibratingAccessibilityServiceRule.enableService();
277         List<AccessibilityServiceInfo> enabledServices =
278                 mAccessibilityManager.getEnabledAccessibilityServiceList(
279                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
280         boolean speakingServiceEnabled = false;
281         boolean vibratingServiceEnabled = false;
282         final int serviceCount = enabledServices.size();
283         for (int i = 0; i < serviceCount; i++) {
284             AccessibilityServiceInfo enabledService = enabledServices.get(i);
285             ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
286             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
287                     && SPEAKING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
288                 speakingServiceEnabled = true;
289             }
290             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
291                     && VIBRATING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
292                 vibratingServiceEnabled = true;
293             }
294         }
295         assertTrue("The speaking service should be enabled.", speakingServiceEnabled);
296         assertTrue("The vibrating service should be enabled.", vibratingServiceEnabled);
297     }
298 
299     @AsbSecurityTest(cveBugId = {243849844})
300     @Test
testGetEnabledAccessibilityServiceList_NoFeedback()301     public void testGetEnabledAccessibilityServiceList_NoFeedback() {
302         mNoFeedbackAccessibilityServiceRule.enableService();
303         List<AccessibilityServiceInfo> enabledServices =
304                 mAccessibilityManager.getEnabledAccessibilityServiceList(
305                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
306         boolean noFeedbackServiceEnabled = false;
307         final int serviceCount = enabledServices.size();
308         for (int i = 0; i < serviceCount; i++) {
309             AccessibilityServiceInfo enabledService = enabledServices.get(i);
310             ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
311             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
312                     && NO_FEEDBACK_ACCESSIBILITY_SERVICE_NAME.equals(serviceInfo.name)) {
313                 noFeedbackServiceEnabled = true;
314             }
315         }
316         assertTrue("The no-feedback service should be enabled.", noFeedbackServiceEnabled);
317     }
318 
319     @Test
testGetEnabledAccessibilityServiceListForType()320     public void testGetEnabledAccessibilityServiceListForType() throws Exception {
321         mSpeakingAccessibilityServiceRule.enableService();
322         mVibratingAccessibilityServiceRule.enableService();
323         List<AccessibilityServiceInfo> enabledServices =
324                 mAccessibilityManager.getEnabledAccessibilityServiceList(
325                         AccessibilityServiceInfo.FEEDBACK_SPOKEN);
326         assertSame("There should be only one enabled speaking service.", 1, enabledServices.size());
327         final int serviceCount = enabledServices.size();
328         for (int i = 0; i < serviceCount; i++) {
329             AccessibilityServiceInfo enabledService = enabledServices.get(i);
330             ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
331             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
332                     && SPEAKING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
333                 return;
334             }
335         }
336         fail("The speaking service is not enabled.");
337     }
338 
339     @Test
testGetEnabledAccessibilityServiceListForTypes()340     public void testGetEnabledAccessibilityServiceListForTypes() throws Exception {
341         mSpeakingAccessibilityServiceRule.enableService();
342         mVibratingAccessibilityServiceRule.enableService();
343         // For this test, also enable a service with multiple feedback types
344         mSpeakingAndVibratingAccessibilityServiceRule.enableService();
345 
346         List<AccessibilityServiceInfo> enabledServices =
347                 mAccessibilityManager.getEnabledAccessibilityServiceList(
348                         AccessibilityServiceInfo.FEEDBACK_SPOKEN
349                                 | AccessibilityServiceInfo.FEEDBACK_HAPTIC);
350         assertSame("There should be 3 enabled accessibility services.", 3, enabledServices.size());
351         boolean speakingServiceEnabled = false;
352         boolean vibratingServiceEnabled = false;
353         boolean multipleFeedbackTypesServiceEnabled = false;
354         final int serviceCount = enabledServices.size();
355         for (int i = 0; i < serviceCount; i++) {
356             AccessibilityServiceInfo enabledService = enabledServices.get(i);
357             ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
358             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
359                     && SPEAKING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
360                 speakingServiceEnabled = true;
361             }
362             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
363                     && VIBRATING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
364                 vibratingServiceEnabled = true;
365             }
366             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
367                     && MULTIPLE_FEEDBACK_TYPES_ACCESSIBILITY_SERVICE_NAME.equals(
368                     serviceInfo.name)) {
369                 multipleFeedbackTypesServiceEnabled = true;
370             }
371         }
372         assertTrue("The speaking service should be enabled.", speakingServiceEnabled);
373         assertTrue("The vibrating service should be enabled.", vibratingServiceEnabled);
374         assertTrue("The multiple feedback types service should be enabled.",
375                 multipleFeedbackTypesServiceEnabled);
376     }
377 
378     @SuppressWarnings("deprecation")
379     @Test
testGetAccessibilityServiceList()380     public void testGetAccessibilityServiceList() throws Exception {
381         List<ServiceInfo> services = mAccessibilityManager.getAccessibilityServiceList();
382         boolean speakingServiceInstalled = false;
383         boolean vibratingServiceInstalled = false;
384         final int serviceCount = services.size();
385         for (int i = 0; i < serviceCount; i++) {
386             ServiceInfo serviceInfo = services.get(i);
387             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
388                     && SPEAKING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
389                 speakingServiceInstalled = true;
390             }
391             if (mTargetContext.getPackageName().equals(serviceInfo.packageName)
392                     && VIBRATING_ACCESSIBLITY_SERVICE_NAME.equals(serviceInfo.name)) {
393                 vibratingServiceInstalled = true;
394             }
395         }
396         assertTrue("The speaking service should be installed.", speakingServiceInstalled);
397         assertTrue("The vibrating service should be installed.", vibratingServiceInstalled);
398     }
399 
400     @Test
testInterrupt()401     public void testInterrupt() throws Exception {
402         // The APIs are heavily tested in the android.accessibilityservice package.
403         // This just makes sure the call does not throw an exception.
404         mSpeakingAccessibilityServiceRule.enableService();
405         mVibratingAccessibilityServiceRule.enableService();
406         waitForAccessibilityEnabled();
407         mAccessibilityManager.interrupt();
408     }
409 
410     @Test
testSendAccessibilityEvent()411     public void testSendAccessibilityEvent() throws Exception {
412         // The APIs are heavily tested in the android.accessibilityservice package.
413         // This just makes sure the call does not throw an exception.
414         mSpeakingAccessibilityServiceRule.enableService();
415         mVibratingAccessibilityServiceRule.enableService();
416         waitForAccessibilityEnabled();
417         mAccessibilityManager.sendAccessibilityEvent(AccessibilityEvent.obtain(
418                 AccessibilityEvent.TYPE_VIEW_CLICKED));
419     }
420 
421     @Test
testTouchExplorationListenerNoHandler()422     public void testTouchExplorationListenerNoHandler() throws Exception {
423         final Object waitObject = new Object();
424         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
425 
426         TouchExplorationStateChangeListener listener = (boolean b) -> {
427             synchronized (waitObject) {
428                 atomicBoolean.set(b);
429                 waitObject.notifyAll();
430             }
431         };
432         mAccessibilityManager.addTouchExplorationStateChangeListener(listener);
433         mSpeakingAccessibilityServiceRule.enableService();
434         mVibratingAccessibilityServiceRule.enableService();
435         waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
436                 "Touch exploration state listener called when services enabled");
437         assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
438                 mAccessibilityManager.isTouchExplorationEnabled());
439         InstrumentedAccessibilityService.disableAllServices();
440         waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
441                 "Touch exploration state listener called when services disabled");
442         assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
443                 mAccessibilityManager.isTouchExplorationEnabled());
444         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
445     }
446 
447     @Test
testTouchExplorationListenerWithHandler()448     public void testTouchExplorationListenerWithHandler() throws Exception {
449         final Object waitObject = new Object();
450         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
451 
452         TouchExplorationStateChangeListener listener = (boolean b) -> {
453             synchronized (waitObject) {
454                 atomicBoolean.set(b);
455                 waitObject.notifyAll();
456             }
457         };
458         mAccessibilityManager.addTouchExplorationStateChangeListener(listener, mHandler);
459         mSpeakingAccessibilityServiceRule.enableService();
460         mVibratingAccessibilityServiceRule.enableService();
461         waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
462                 "Touch exploration state listener called when services enabled");
463         assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
464                 mAccessibilityManager.isTouchExplorationEnabled());
465         InstrumentedAccessibilityService.disableAllServices();
466         waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
467                 "Touch exploration state listener called when services disabled");
468         assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
469                 mAccessibilityManager.isTouchExplorationEnabled());
470         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
471     }
472 
473     @Test
testAccessibilityServicesStateListenerNoExecutor()474     public void testAccessibilityServicesStateListenerNoExecutor() {
475         final Object waitObject = new Object();
476         final AtomicBoolean serviceEnabled = new AtomicBoolean(false);
477         final UiAutomation automan = sInstrumentation.getUiAutomation(
478                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
479         final AccessibilityServicesStateChangeListener listener = (AccessibilityManager manager) ->
480                 checkServiceEnabled(waitObject, manager, serviceEnabled,
481                         VibratingAccessibilityService.class.getSimpleName());
482         try {
483             mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener);
484 
485             mVibratingAccessibilityServiceRule.enableService();
486 
487             waitForAtomicBooleanBecomes(serviceEnabled, true, waitObject,
488                     "Accessibility services state listener called when service is enabled");
489         } finally {
490             automan.destroy();
491         }
492     }
493 
494     @Test
testAccessibilityServicesStateListenerWithExecutor()495     public void testAccessibilityServicesStateListenerWithExecutor() {
496         final Object waitObject = new Object();
497         final AtomicBoolean serviceEnabled = new AtomicBoolean(false);
498         final UiAutomation automan = sInstrumentation.getUiAutomation(
499                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
500         final AccessibilityServicesStateChangeListener listener = (AccessibilityManager manager) ->
501                 checkServiceEnabled(waitObject, manager, serviceEnabled,
502                         VibratingAccessibilityService.class.getSimpleName());
503         try {
504             mAccessibilityManager.addAccessibilityServicesStateChangeListener(
505                     mTargetContext.getMainExecutor(), listener);
506 
507             mVibratingAccessibilityServiceRule.enableService();
508 
509             waitForAtomicBooleanBecomes(serviceEnabled, true, waitObject,
510                     "Accessibility services state listener called when service is enabled");
511         } finally {
512             automan.destroy();
513         }
514     }
515 
516 
517     @Test
testAccessibilityStateListenerNoHandler()518     public void testAccessibilityStateListenerNoHandler() throws Exception {
519         final Object waitObject = new Object();
520         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
521 
522         AccessibilityStateChangeListener listener = (boolean b) -> {
523             synchronized (waitObject) {
524                 atomicBoolean.set(b);
525                 waitObject.notifyAll();
526             }
527         };
528         mAccessibilityManager.addAccessibilityStateChangeListener(listener);
529         mSpeakingAndVibratingAccessibilityServiceRule.enableService();
530         waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
531                 "Accessibility state listener called when services enabled");
532         assertTrue("Listener told that accessibility is enabled, but manager says disabled",
533                 mAccessibilityManager.isEnabled());
534         InstrumentedAccessibilityService.disableAllServices();
535         waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
536                 "Accessibility state listener called when services disabled");
537         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
538                 mAccessibilityManager.isEnabled());
539         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
540     }
541 
542     @Test
testAudioDescriptionRequestedChangeListenerWithExecutor()543     public void testAudioDescriptionRequestedChangeListenerWithExecutor() {
544         final UiAutomation automan = sInstrumentation.getUiAutomation(
545                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
546         final Object waitObject = new Object();
547         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
548 
549         AudioDescriptionRequestedChangeListener listener = (boolean b) -> {
550             synchronized (waitObject) {
551                 atomicBoolean.set(b);
552                 waitObject.notifyAll();
553             }
554         };
555 
556         try {
557             mAccessibilityManager.addAudioDescriptionRequestedChangeListener(
558                     mTargetContext.getMainExecutor(), listener);
559             putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "1");
560             waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
561                     "Audio description state listener called when services enabled");
562             assertTrue("Listener told that audio description by default is request.",
563                     mAccessibilityManager.isAudioDescriptionRequested());
564 
565             putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "0");
566             waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
567                     "Audio description state listener called when services disabled");
568             assertFalse("Listener told that audio description by default is not request.",
569                     mAccessibilityManager.isAudioDescriptionRequested());
570             assertTrue(
571                     mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(
572                             listener));
573         } finally {
574             automan.destroy();
575         }
576     }
577 
578     @Test
testIsAudioDescriptionEnabled()579     public void testIsAudioDescriptionEnabled() throws Exception {
580         final UiAutomation automan = sInstrumentation.getUiAutomation(
581                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
582 
583         try {
584             putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "1");
585             PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
586                 @Override
587                 public boolean canProceed() {
588                     return mAccessibilityManager.isAudioDescriptionRequested();
589                 }
590             });
591             assertTrue(mAccessibilityManager.isAudioDescriptionRequested());
592 
593             putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "0");
594             PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
595                 @Override
596                 public boolean canProceed() {
597                     return !mAccessibilityManager.isAudioDescriptionRequested();
598                 }
599             });
600             assertFalse(mAccessibilityManager.isAudioDescriptionRequested());
601         } finally {
602             automan.destroy();
603         }
604     }
605 
606     @Test
testAccessibilityStateListenerWithHandler()607     public void testAccessibilityStateListenerWithHandler() throws Exception {
608         final Object waitObject = new Object();
609         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
610 
611         AccessibilityStateChangeListener listener = (boolean b) -> {
612             synchronized (waitObject) {
613                 atomicBoolean.set(b);
614                 waitObject.notifyAll();
615             }
616         };
617         mAccessibilityManager.addAccessibilityStateChangeListener(listener, mHandler);
618         mSpeakingAndVibratingAccessibilityServiceRule.enableService();
619         waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
620                 "Accessibility state listener called when services enabled");
621         assertTrue("Listener told that accessibility is enabled, but manager says disabled",
622                 mAccessibilityManager.isEnabled());
623         InstrumentedAccessibilityService.disableAllServices();
624         waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
625                 "Accessibility state listener called when services disabled");
626         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
627                 mAccessibilityManager.isEnabled());
628         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
629     }
630 
631     @Test
testGetRecommendedTimeoutMillis()632     public void testGetRecommendedTimeoutMillis() throws Exception {
633         mSpeakingAccessibilityServiceRule.enableService();
634         mVibratingAccessibilityServiceRule.enableService();
635         waitForAccessibilityEnabled();
636         UiAutomation automan = sInstrumentation.getUiAutomation(
637                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
638         try {
639             // SpeakingA11yService interactive/nonInteractive timeout is 6000/1000
640             // vibratingA11yService interactive/nonInteractive timeout is 5000/2000
641             turnOffRecommendedUiTimoutSettings(automan);
642             PollingCheck.waitFor(() -> sameRecommendedTimeout(6000, 2000));
643             turnOnRecommendedUiTimoutSettings(automan, 7000, 0);
644             PollingCheck.waitFor(() -> sameRecommendedTimeout(7000, 2000));
645             turnOnRecommendedUiTimoutSettings(automan, 0, 4000);
646             PollingCheck.waitFor(() -> sameRecommendedTimeout(6000, 4000));
647             turnOnRecommendedUiTimoutSettings(automan, 9000, 8000);
648             PollingCheck.waitFor(() -> sameRecommendedTimeout(9000, 8000));
649             turnOffRecommendedUiTimoutSettings(automan);
650             PollingCheck.waitFor(() -> sameRecommendedTimeout(6000, 2000));
651             assertEquals("Should return original timeout", 3000,
652                     mAccessibilityManager.getRecommendedTimeoutMillis(3000,
653                             AccessibilityManager.FLAG_CONTENT_ICONS));
654             assertEquals("Should return original timeout", 7000,
655                     mAccessibilityManager.getRecommendedTimeoutMillis(7000,
656                             AccessibilityManager.FLAG_CONTENT_CONTROLS));
657         } finally {
658             automan.destroy();
659         }
660     }
661 
662     @Test
663     @RequiresFlagsEnabled(Flags.FLAG_FLASH_NOTIFICATION_SYSTEM_API)
testStartAndStopFlashNotificationSequence()664     public void testStartAndStopFlashNotificationSequence() throws Exception {
665         assertTrue("Start flash notification sequence failed.",
666                 mAccessibilityManager.startFlashNotificationSequence(mTargetContext, 1));
667         assertTrue("Stop flash notification sequence failed.",
668                 mAccessibilityManager.stopFlashNotificationSequence(mTargetContext));
669     }
670 
checkServiceEnabled(Object waitObject, AccessibilityManager manager, AtomicBoolean serviceEnabled, String serviceName)671     private void checkServiceEnabled(Object waitObject, AccessibilityManager manager,
672             AtomicBoolean serviceEnabled, String serviceName) {
673         synchronized (waitObject) {
674             List<AccessibilityServiceInfo> infos = manager.getEnabledAccessibilityServiceList(
675                     AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
676             for (AccessibilityServiceInfo info : infos) {
677                 final String serviceId = info.getId();
678                 if (serviceId.endsWith(serviceName)) {
679                     serviceEnabled.set(true);
680                     waitObject.notifyAll();
681                 }
682             }
683         }
684     }
685 
waitForAtomicBooleanBecomes(AtomicBoolean atomicBoolean, boolean expectedValue, Object waitObject, String condition)686     private void waitForAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
687             boolean expectedValue, Object waitObject, String condition) {
688         long timeoutTime = TIMEOUT_SERVICE_ENABLE;
689         TestUtils.waitOn(waitObject, () -> atomicBoolean.get() == expectedValue, timeoutTime,
690                 condition);
691     }
692 
waitForAccessibilityEnabled()693     private void waitForAccessibilityEnabled() throws InterruptedException {
694         final Object waitObject = new Object();
695 
696         AccessibilityStateChangeListener listener = (boolean b) -> {
697             synchronized (waitObject) {
698                 waitObject.notifyAll();
699             }
700         };
701         mAccessibilityManager.addAccessibilityStateChangeListener(listener);
702         long timeoutTime =
703                 System.currentTimeMillis() + TIMEOUT_SERVICE_ENABLE;
704         synchronized (waitObject) {
705             while (!mAccessibilityManager.isEnabled()
706                     && (System.currentTimeMillis() < timeoutTime)) {
707                 waitObject.wait(timeoutTime - System.currentTimeMillis());
708             }
709         }
710         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
711         assertTrue("Timed out enabling accessibility", mAccessibilityManager.isEnabled());
712     }
713 
turnOffRecommendedUiTimoutSettings(UiAutomation automan)714     private void turnOffRecommendedUiTimoutSettings(UiAutomation automan) {
715         putSecureSetting(automan, ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, null);
716         putSecureSetting(automan, ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, null);
717     }
718 
turnOnRecommendedUiTimoutSettings(UiAutomation automan, int interactiveUiTimeout, int nonInteractiveUiTimeout)719     private void turnOnRecommendedUiTimoutSettings(UiAutomation automan,
720             int interactiveUiTimeout, int nonInteractiveUiTimeout) {
721         putSecureSetting(automan, ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS,
722                 Integer.toString(interactiveUiTimeout));
723         putSecureSetting(automan, ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
724                 Integer.toString(nonInteractiveUiTimeout));
725     }
726 
sameRecommendedTimeout(int interactiveUiTimeout, int nonInteractiveUiTimeout)727     private boolean sameRecommendedTimeout(int interactiveUiTimeout,
728             int nonInteractiveUiTimeout) {
729         final int currentInteractiveUiTimeout = mAccessibilityManager
730                 .getRecommendedTimeoutMillis(0, AccessibilityManager.FLAG_CONTENT_CONTROLS);
731         final int currentNonInteractiveUiTimeout = mAccessibilityManager
732                 .getRecommendedTimeoutMillis(0, AccessibilityManager.FLAG_CONTENT_ICONS);
733         return (currentInteractiveUiTimeout == interactiveUiTimeout
734                 && currentNonInteractiveUiTimeout == nonInteractiveUiTimeout);
735     }
736 
putSecureSetting(UiAutomation automan, String name, String value)737     private void putSecureSetting(UiAutomation automan, String name, String value) {
738         final StringBuilder cmd = new StringBuilder("settings put secure ")
739                 .append(name).append(" ")
740                 .append(value);
741         try {
742             SystemUtil.runShellCommand(automan, cmd.toString());
743         } catch (IOException e) {
744             fail("Fail to run shell command");
745         }
746     }
747 }
748