1 package org.robolectric.shadows;
2 
3 import static android.content.Context.ACCESSIBILITY_SERVICE;
4 import static android.os.Build.VERSION_CODES.O;
5 import static android.os.Build.VERSION_CODES.O_MR1;
6 import static android.os.Build.VERSION_CODES.Q;
7 import static com.google.common.truth.Truth.assertThat;
8 import static org.robolectric.Shadows.shadowOf;
9 
10 import android.accessibilityservice.AccessibilityServiceInfo;
11 import android.app.Activity;
12 import android.content.Context;
13 import android.content.pm.ServiceInfo;
14 import android.view.accessibility.AccessibilityEvent;
15 import android.view.accessibility.AccessibilityManager;
16 import androidx.test.core.app.ApplicationProvider;
17 import androidx.test.ext.junit.runners.AndroidJUnit4;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.concurrent.atomic.AtomicBoolean;
22 import org.junit.Before;
23 import org.junit.Test;
24 import org.junit.runner.RunWith;
25 import org.robolectric.Robolectric;
26 import org.robolectric.annotation.Config;
27 import org.robolectric.util.ReflectionHelpers;
28 
29 @RunWith(AndroidJUnit4.class)
30 public class ShadowAccessibilityManagerTest {
31 
32   private AccessibilityManager accessibilityManager;
33 
34   @Before
setUp()35   public void setUp() throws Exception {
36     accessibilityManager =
37         (AccessibilityManager)
38             ApplicationProvider.getApplicationContext().getSystemService(ACCESSIBILITY_SERVICE);
39   }
40 
41   @Test
shouldReturnTrueWhenEnabled()42   public void shouldReturnTrueWhenEnabled() {
43     shadowOf(accessibilityManager).setEnabled(true);
44     assertThat(accessibilityManager.isEnabled()).isTrue();
45     assertThat(getAccessibilityManagerInstance().isEnabled()).isTrue();
46   }
47 
48   // Emulates Android framework behavior, e.g.,
49   // AccessibilityManager.getInstance(context).isEnabled().
getAccessibilityManagerInstance()50   private static AccessibilityManager getAccessibilityManagerInstance() {
51     return ReflectionHelpers.callStaticMethod(
52         AccessibilityManager.class,
53         "getInstance",
54         ReflectionHelpers.ClassParameter.from(
55             Context.class, ApplicationProvider.getApplicationContext()));
56   }
57 
58   @Test
shouldReturnTrueForTouchExplorationWhenEnabled()59   public void shouldReturnTrueForTouchExplorationWhenEnabled() {
60     shadowOf(accessibilityManager).setTouchExplorationEnabled(true);
61     assertThat(accessibilityManager.isTouchExplorationEnabled()).isTrue();
62   }
63 
64   @Test
shouldReturnExpectedEnabledServiceList()65   public void shouldReturnExpectedEnabledServiceList() {
66     List<AccessibilityServiceInfo> expected =
67         new ArrayList<>(Collections.singletonList(new AccessibilityServiceInfo()));
68     shadowOf(accessibilityManager).setEnabledAccessibilityServiceList(expected);
69     assertThat(accessibilityManager.getEnabledAccessibilityServiceList(0)).isEqualTo(expected);
70   }
71 
72   @Test
shouldReturnExpectedInstalledServiceList()73   public void shouldReturnExpectedInstalledServiceList() {
74     List<AccessibilityServiceInfo> expected =
75         new ArrayList<>(Collections.singletonList(new AccessibilityServiceInfo()));
76     shadowOf(accessibilityManager).setInstalledAccessibilityServiceList(expected);
77     assertThat(accessibilityManager.getInstalledAccessibilityServiceList()).isEqualTo(expected);
78   }
79 
80   @Test
shouldReturnExpectedAccessibilityServiceList()81   public void shouldReturnExpectedAccessibilityServiceList() {
82     List<ServiceInfo> expected = new ArrayList<>(Collections.singletonList(new ServiceInfo()));
83     shadowOf(accessibilityManager).setAccessibilityServiceList(expected);
84     assertThat(accessibilityManager.getAccessibilityServiceList()).isEqualTo(expected);
85   }
86 
87   @Test
88   @Config(minSdk = O_MR1)
isAccessibilityButtonSupported()89   public void isAccessibilityButtonSupported() {
90     assertThat(AccessibilityManager.isAccessibilityButtonSupported()).isTrue();
91 
92     ShadowAccessibilityManager.setAccessibilityButtonSupported(false);
93     assertThat(AccessibilityManager.isAccessibilityButtonSupported()).isFalse();
94 
95     ShadowAccessibilityManager.setAccessibilityButtonSupported(true);
96     assertThat(AccessibilityManager.isAccessibilityButtonSupported()).isTrue();
97   }
98 
99   @Test
100   @Config(minSdk = O)
performAccessibilityShortcut_shouldEnableAccessibilityAndTouchExploration()101   public void performAccessibilityShortcut_shouldEnableAccessibilityAndTouchExploration() {
102     accessibilityManager.performAccessibilityShortcut();
103 
104     assertThat(accessibilityManager.isEnabled()).isTrue();
105     assertThat(accessibilityManager.isTouchExplorationEnabled()).isTrue();
106   }
107 
108   @Test
getSentAccessibilityEvents_returnsEmptyInitially()109   public void getSentAccessibilityEvents_returnsEmptyInitially() {
110     assertThat(shadowOf(accessibilityManager).getSentAccessibilityEvents()).isEmpty();
111   }
112 
113   @Test
getSentAccessibilityEvents_returnsAllSentAccessibilityEventsInOrder()114   public void getSentAccessibilityEvents_returnsAllSentAccessibilityEventsInOrder() {
115     AccessibilityEvent event1 = AccessibilityEvent.obtain();
116     event1.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
117 
118     AccessibilityEvent event2 = AccessibilityEvent.obtain();
119     event2.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
120 
121     AccessibilityEvent event3 = AccessibilityEvent.obtain();
122     event3.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
123 
124     shadowOf(accessibilityManager).setEnabled(true);
125     accessibilityManager.sendAccessibilityEvent(event1);
126     accessibilityManager.sendAccessibilityEvent(event2);
127     accessibilityManager.sendAccessibilityEvent(event3);
128 
129     assertThat(shadowOf(accessibilityManager).getSentAccessibilityEvents())
130         .containsExactly(event1, event2, event3)
131         .inOrder();
132   }
133 
134   @Test
addAccessibilityStateChangeListener_shouldAddListener()135   public void addAccessibilityStateChangeListener_shouldAddListener() {
136     TestAccessibilityStateChangeListener listener1 = new TestAccessibilityStateChangeListener();
137     TestAccessibilityStateChangeListener listener2 = new TestAccessibilityStateChangeListener();
138 
139     accessibilityManager.addAccessibilityStateChangeListener(listener1);
140     accessibilityManager.addAccessibilityStateChangeListener(listener2);
141 
142     shadowOf(accessibilityManager).setEnabled(true);
143 
144     assertThat(listener1.isEnabled()).isTrue();
145     assertThat(listener2.isEnabled()).isTrue();
146   }
147 
148   @Test
removeAccessibilityStateChangeListener_shouldRemoveListeners()149   public void removeAccessibilityStateChangeListener_shouldRemoveListeners() {
150     // Add two different callbacks.
151     TestAccessibilityStateChangeListener listener1 = new TestAccessibilityStateChangeListener();
152     TestAccessibilityStateChangeListener listener2 = new TestAccessibilityStateChangeListener();
153     accessibilityManager.addAccessibilityStateChangeListener(listener1);
154     accessibilityManager.addAccessibilityStateChangeListener(listener2);
155 
156     shadowOf(accessibilityManager).setEnabled(true);
157 
158     assertThat(listener1.isEnabled()).isTrue();
159     assertThat(listener2.isEnabled()).isTrue();
160     // Remove one at the time.
161     accessibilityManager.removeAccessibilityStateChangeListener(listener2);
162 
163     shadowOf(accessibilityManager).setEnabled(false);
164 
165     assertThat(listener1.isEnabled()).isFalse();
166     assertThat(listener2.isEnabled()).isTrue();
167 
168     accessibilityManager.removeAccessibilityStateChangeListener(listener1);
169 
170     shadowOf(accessibilityManager).setEnabled(true);
171 
172     assertThat(listener1.isEnabled()).isFalse();
173     assertThat(listener2.isEnabled()).isTrue();
174   }
175 
176   @Test
removeAccessibilityStateChangeListener_returnsTrueIfRemoved()177   public void removeAccessibilityStateChangeListener_returnsTrueIfRemoved() {
178     TestAccessibilityStateChangeListener listener = new TestAccessibilityStateChangeListener();
179     accessibilityManager.addAccessibilityStateChangeListener(listener);
180 
181     assertThat(accessibilityManager.removeAccessibilityStateChangeListener(listener)).isTrue();
182   }
183 
184   @Test
removeAccessibilityStateChangeListener_returnsFalseIfNotRegistered()185   public void removeAccessibilityStateChangeListener_returnsFalseIfNotRegistered() {
186     assertThat(
187             accessibilityManager.removeAccessibilityStateChangeListener(
188                 new TestAccessibilityStateChangeListener()))
189         .isFalse();
190     assertThat(accessibilityManager.removeAccessibilityStateChangeListener(null)).isFalse();
191   }
192 
193   @Test
setTouchExplorationEnabled_invokesCallbacks()194   public void setTouchExplorationEnabled_invokesCallbacks() {
195     AtomicBoolean enabled = new AtomicBoolean(false);
196     accessibilityManager.addTouchExplorationStateChangeListener(val -> enabled.set(val));
197     shadowOf(accessibilityManager).setTouchExplorationEnabled(true);
198     assertThat(enabled.get()).isEqualTo(true);
199     shadowOf(accessibilityManager).setTouchExplorationEnabled(false);
200     assertThat(enabled.get()).isEqualTo(false);
201   }
202 
203   @Test
204   @Config(minSdk = Q)
getRecommendedTimeoutMillis_default()205   public void getRecommendedTimeoutMillis_default() {
206     int flags =
207         AccessibilityManager.FLAG_CONTENT_ICONS
208             | AccessibilityManager.FLAG_CONTENT_TEXT
209             | AccessibilityManager.FLAG_CONTENT_CONTROLS;
210 
211     assertThat(accessibilityManager.getRecommendedTimeoutMillis(1, flags)).isEqualTo(1);
212   }
213 
214   @Test
215   @Config(minSdk = Q)
getRecommendedTimeoutMillis_interactive()216   public void getRecommendedTimeoutMillis_interactive() {
217     int flags =
218         AccessibilityManager.FLAG_CONTENT_ICONS
219             | AccessibilityManager.FLAG_CONTENT_TEXT
220             | AccessibilityManager.FLAG_CONTENT_CONTROLS;
221     shadowOf(accessibilityManager).setNonInteractiveUiTimeout(2);
222     shadowOf(accessibilityManager).setInteractiveUiTimeout(3);
223 
224     assertThat(accessibilityManager.getRecommendedTimeoutMillis(1, flags)).isEqualTo(3);
225   }
226 
227   @Test
228   @Config(minSdk = Q)
getRecommendedTimeoutMillis_nonInteractive()229   public void getRecommendedTimeoutMillis_nonInteractive() {
230     int flags = AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_TEXT;
231     shadowOf(accessibilityManager).setNonInteractiveUiTimeout(2);
232     shadowOf(accessibilityManager).setInteractiveUiTimeout(3);
233 
234     assertThat(accessibilityManager.getRecommendedTimeoutMillis(1, flags)).isEqualTo(2);
235   }
236 
237   @Test
238   @Config(minSdk = Q)
getRecommendedTimeoutMillis_empty()239   public void getRecommendedTimeoutMillis_empty() {
240     int flags = 0;
241     shadowOf(accessibilityManager).setNonInteractiveUiTimeout(2);
242     shadowOf(accessibilityManager).setInteractiveUiTimeout(3);
243 
244     assertThat(accessibilityManager.getRecommendedTimeoutMillis(1, flags)).isEqualTo(1);
245   }
246 
247   private static class TestAccessibilityStateChangeListener
248       implements AccessibilityManager.AccessibilityStateChangeListener {
249 
250     private boolean enabled = false;
251 
252     @Override
onAccessibilityStateChanged(boolean enabled)253     public void onAccessibilityStateChanged(boolean enabled) {
254       this.enabled = enabled;
255     }
256 
isEnabled()257     public boolean isEnabled() {
258       return enabled;
259     }
260   }
261 
262   @Test
getAccessibilityServiceList_doesNotNPE()263   public void getAccessibilityServiceList_doesNotNPE() {
264     assertThat(accessibilityManager.getAccessibilityServiceList()).isEmpty();
265     assertThat(accessibilityManager.getInstalledAccessibilityServiceList()).isEmpty();
266     assertThat(
267             accessibilityManager.getEnabledAccessibilityServiceList(
268                 AccessibilityServiceInfo.FEEDBACK_SPOKEN))
269         .isEmpty();
270   }
271 
272   @Test
273   @Config(minSdk = O)
accessibilityManager_activityContextEnabled_differentInstancesHaveSameServices()274   public void accessibilityManager_activityContextEnabled_differentInstancesHaveSameServices() {
275     String originalProperty = System.getProperty("robolectric.createActivityContexts", "");
276     System.setProperty("robolectric.createActivityContexts", "true");
277     Activity activity = null;
278     try {
279       AccessibilityManager applicationAccessibilityManager =
280           (AccessibilityManager)
281               ApplicationProvider.getApplicationContext()
282                   .getSystemService(Context.ACCESSIBILITY_SERVICE);
283       activity = Robolectric.setupActivity(Activity.class);
284       AccessibilityManager activityAccessibilityManager =
285           (AccessibilityManager) activity.getSystemService(Context.ACCESSIBILITY_SERVICE);
286 
287       assertThat(applicationAccessibilityManager).isSameInstanceAs(activityAccessibilityManager);
288 
289       List<AccessibilityServiceInfo> applicationServices =
290           applicationAccessibilityManager.getInstalledAccessibilityServiceList();
291       List<AccessibilityServiceInfo> activityServices =
292           activityAccessibilityManager.getInstalledAccessibilityServiceList();
293 
294       assertThat(activityServices).isEqualTo(applicationServices);
295     } finally {
296       if (activity != null) {
297         activity.finish();
298       }
299       System.setProperty("robolectric.createActivityContexts", originalProperty);
300     }
301   }
302 }
303