1 /*
2  * Copyright (C) 2017 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 com.android.settings.accessibility;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.ArgumentMatchers.eq;
24 import static org.mockito.ArgumentMatchers.isNull;
25 import static org.mockito.Mockito.atLeast;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import static java.util.Collections.singletonList;
31 
32 import android.accessibilityservice.AccessibilityServiceInfo;
33 import android.accessibilityservice.AccessibilityShortcutInfo;
34 import android.content.ComponentName;
35 import android.content.ContentResolver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.pm.ActivityInfo;
40 import android.content.pm.ApplicationInfo;
41 import android.content.pm.ResolveInfo;
42 import android.content.pm.ServiceInfo;
43 import android.os.Build;
44 import android.os.Bundle;
45 import android.provider.Settings;
46 import android.view.accessibility.AccessibilityManager;
47 
48 import androidx.fragment.app.FragmentActivity;
49 import androidx.preference.PreferenceManager;
50 import androidx.test.core.app.ApplicationProvider;
51 
52 import com.android.internal.content.PackageMonitor;
53 import com.android.settings.R;
54 import com.android.settings.testutils.XmlTestUtils;
55 import com.android.settings.testutils.shadow.ShadowDeviceConfig;
56 import com.android.settings.testutils.shadow.ShadowFragment;
57 import com.android.settings.testutils.shadow.ShadowUserManager;
58 import com.android.settingslib.RestrictedPreference;
59 import com.android.settingslib.search.SearchIndexableRaw;
60 
61 import org.junit.Before;
62 import org.junit.Rule;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.mockito.ArgumentCaptor;
66 import org.mockito.Mock;
67 import org.mockito.Mockito;
68 import org.mockito.Spy;
69 import org.mockito.junit.MockitoJUnit;
70 import org.mockito.junit.MockitoRule;
71 import org.robolectric.RobolectricTestRunner;
72 import org.robolectric.annotation.Config;
73 import org.robolectric.shadow.api.Shadow;
74 import org.robolectric.shadows.ShadowAccessibilityManager;
75 import org.xmlpull.v1.XmlPullParserException;
76 
77 import java.io.IOException;
78 import java.util.ArrayList;
79 import java.util.List;
80 
81 /** Test for {@link AccessibilitySettings}. */
82 @RunWith(RobolectricTestRunner.class)
83 public class AccessibilitySettingsTest {
84     private static final String PACKAGE_NAME = "com.android.test";
85     private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service";
86     private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME, CLASS_NAME);
87     private static final int ON = 1;
88     private static final int OFF = 0;
89     private static final String EMPTY_STRING = "";
90     private static final String DEFAULT_SUMMARY = "default summary";
91     private static final String DEFAULT_DESCRIPTION = "default description";
92     private static final String DEFAULT_LABEL = "default label";
93     private static final Boolean SERVICE_ENABLED = true;
94     private static final Boolean SERVICE_DISABLED = false;
95     @Rule
96     public final MockitoRule mocks = MockitoJUnit.rule();
97     @Spy
98     private final Context mContext = ApplicationProvider.getApplicationContext();
99     @Spy
100     private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo(
101             PACKAGE_NAME, CLASS_NAME);
102     @Spy
103     private final AccessibilitySettings mFragment = new AccessibilitySettings();
104     @Mock
105     private AccessibilityShortcutInfo mShortcutInfo;
106     @Mock
107     private FragmentActivity mActivity;
108     @Mock
109     private ContentResolver mContentResolver;
110     @Mock
111     private PreferenceManager mPreferenceManager;
112     private ShadowAccessibilityManager mShadowAccessibilityManager;
113 
114     @Before
setup()115     public void setup() {
116         mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext));
117         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>());
118         when(mFragment.getContext()).thenReturn(mContext);
119         when(mFragment.getActivity()).thenReturn(mActivity);
120         when(mActivity.getContentResolver()).thenReturn(mContentResolver);
121         when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
122         when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext);
123         mContext.setTheme(R.style.Theme_AppCompat);
124     }
125 
126     @Test
getNonIndexableKeys_existInXmlLayout()127     public void getNonIndexableKeys_existInXmlLayout() {
128         final List<String> niks = AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER
129                 .getNonIndexableKeys(mContext);
130         final List<String> keys =
131                 XmlTestUtils.getKeysFromPreferenceXml(mContext, R.xml.accessibility_settings);
132 
133         assertThat(keys).containsAtLeastElementsIn(niks);
134     }
135 
136     @Test
getRawDataToIndex_isNull()137     public void getRawDataToIndex_isNull() {
138         final List<SearchIndexableRaw> indexableRawList =
139                 AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER.getRawDataToIndex(mContext, true);
140 
141         assertThat(indexableRawList).isNull();
142     }
143 
144     @Test
145     @Config(shadows = {ShadowDeviceConfig.class})
isRampingRingerEnabled_settingsFlagOn_Enabled()146     public void isRampingRingerEnabled_settingsFlagOn_Enabled() {
147         Settings.Global.putInt(
148                 mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, ON);
149         assertThat(AccessibilitySettings.isRampingRingerEnabled(mContext)).isTrue();
150     }
151 
152     @Test
153     @Config(shadows = {ShadowDeviceConfig.class})
isRampingRingerEnabled_settingsFlagOff_Disabled()154     public void isRampingRingerEnabled_settingsFlagOff_Disabled() {
155         Settings.Global.putInt(
156                 mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, OFF);
157         assertThat(AccessibilitySettings.isRampingRingerEnabled(mContext)).isFalse();
158     }
159 
160     @Test
getServiceSummary_serviceCrash_showsStopped()161     public void getServiceSummary_serviceCrash_showsStopped() {
162         mServiceInfo.crashed = true;
163 
164         final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext,
165                 mServiceInfo, SERVICE_ENABLED);
166 
167         assertThat(summary).isEqualTo(
168                 mContext.getString(R.string.accessibility_summary_state_stopped));
169     }
170 
171     @Test
getServiceSummary_invisibleToggle_shortcutDisabled_showsOffSummary()172     public void getServiceSummary_invisibleToggle_shortcutDisabled_showsOffSummary() {
173         setInvisibleToggleFragmentType(mServiceInfo);
174         doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any());
175 
176         final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext,
177                 mServiceInfo, SERVICE_ENABLED);
178 
179         assertThat(summary).isEqualTo(
180                 mContext.getString(R.string.preference_summary_default_combination,
181                         mContext.getString(R.string.accessibility_summary_shortcut_disabled),
182                         DEFAULT_SUMMARY));
183     }
184 
185     @Test
getServiceSummary_enableService_showsEnabled()186     public void getServiceSummary_enableService_showsEnabled() {
187         doReturn(EMPTY_STRING).when(mServiceInfo).loadSummary(any());
188 
189         final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext,
190                 mServiceInfo, SERVICE_ENABLED);
191 
192         assertThat(summary).isEqualTo(
193                 mContext.getString(R.string.accessibility_summary_state_enabled));
194     }
195 
196     @Test
getServiceSummary_disableService_showsDisabled()197     public void getServiceSummary_disableService_showsDisabled() {
198         doReturn(EMPTY_STRING).when(mServiceInfo).loadSummary(any());
199 
200         final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext,
201                 mServiceInfo, SERVICE_DISABLED);
202 
203         assertThat(summary).isEqualTo(
204                 mContext.getString(R.string.accessibility_summary_state_disabled));
205     }
206 
207     @Test
getServiceSummary_enableServiceAndHasSummary_showsEnabledSummary()208     public void getServiceSummary_enableServiceAndHasSummary_showsEnabledSummary() {
209         final String service_enabled = mContext.getString(
210                 R.string.accessibility_summary_state_enabled);
211         doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any());
212 
213         final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext,
214                 mServiceInfo, SERVICE_ENABLED);
215 
216         assertThat(summary).isEqualTo(
217                 mContext.getString(R.string.preference_summary_default_combination, service_enabled,
218                         DEFAULT_SUMMARY));
219     }
220 
221     @Test
getServiceSummary_disableServiceAndHasSummary_showsCombineDisabledSummary()222     public void getServiceSummary_disableServiceAndHasSummary_showsCombineDisabledSummary() {
223         final String service_disabled = mContext.getString(
224                 R.string.accessibility_summary_state_disabled);
225         doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any());
226 
227         final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext,
228                 mServiceInfo, SERVICE_DISABLED);
229 
230         assertThat(summary).isEqualTo(
231                 mContext.getString(R.string.preference_summary_default_combination,
232                         service_disabled, DEFAULT_SUMMARY));
233     }
234 
235     @Test
getServiceDescription_serviceCrash_showsStopped()236     public void getServiceDescription_serviceCrash_showsStopped() {
237         mServiceInfo.crashed = true;
238 
239         final CharSequence description = AccessibilitySettings.getServiceDescription(mContext,
240                 mServiceInfo, SERVICE_ENABLED);
241 
242         assertThat(description).isEqualTo(
243                 mContext.getString(R.string.accessibility_description_state_stopped));
244     }
245 
246     @Test
getServiceDescription_haveDescription_showsDescription()247     public void getServiceDescription_haveDescription_showsDescription() {
248         doReturn(DEFAULT_DESCRIPTION).when(mServiceInfo).loadDescription(any());
249 
250         final CharSequence description = AccessibilitySettings.getServiceDescription(mContext,
251                 mServiceInfo, SERVICE_ENABLED);
252 
253         assertThat(description).isEqualTo(DEFAULT_DESCRIPTION);
254     }
255 
256     @Test
createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey()257     public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() {
258         final String key = COMPONENT_NAME.flattenToString();
259         final AccessibilitySettings.RestrictedPreferenceHelper helper =
260                 new AccessibilitySettings.RestrictedPreferenceHelper(mContext);
261         final List<AccessibilityServiceInfo> infoList = new ArrayList<>(
262                 singletonList(mServiceInfo));
263 
264         final List<RestrictedPreference> preferenceList =
265                 helper.createAccessibilityServicePreferenceList(infoList);
266         RestrictedPreference preference = preferenceList.get(0);
267 
268         assertThat(preference.getKey()).isEqualTo(key);
269     }
270 
271     @Test
createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey()272     public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() {
273         final String key = COMPONENT_NAME.flattenToString();
274         final AccessibilitySettings.RestrictedPreferenceHelper helper =
275                 new AccessibilitySettings.RestrictedPreferenceHelper(mContext);
276         setMockAccessibilityShortcutInfo(mShortcutInfo);
277         final List<AccessibilityShortcutInfo> infoList = new ArrayList<>(
278                 singletonList(mShortcutInfo));
279 
280         final List<RestrictedPreference> preferenceList =
281                 helper.createAccessibilityActivityPreferenceList(infoList);
282         RestrictedPreference preference = preferenceList.get(0);
283 
284         assertThat(preference.getKey()).isEqualTo(key);
285     }
286 
287     @Test
288     @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
onCreate_haveRegisterToSpecificUrisAndActions()289     public void onCreate_haveRegisterToSpecificUrisAndActions() {
290         final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
291         final IntentFilter intentFilter;
292         mFragment.onAttach(mContext);
293 
294         mFragment.onCreate(Bundle.EMPTY);
295 
296         verify(mContentResolver).registerContentObserver(
297                 eq(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS)),
298                 anyBoolean(),
299                 any(SettingsContentObserver.class));
300         verify(mContentResolver).registerContentObserver(eq(Settings.Secure.getUriFor(
301                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)), anyBoolean(),
302                 any(SettingsContentObserver.class));
303         verify(mActivity, atLeast(1)).registerReceiver(any(PackageMonitor.class), captor.capture(),
304                 isNull(), any());
305         intentFilter = captor.getAllValues().get(/* first time */ 0);
306         assertThat(intentFilter.hasAction(Intent.ACTION_PACKAGE_ADDED)).isTrue();
307         assertThat(intentFilter.hasAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue();
308     }
309 
310     @Test
311     @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
onDestroy_unregisterObserverAndReceiver()312     public void onDestroy_unregisterObserverAndReceiver() {
313         setupFragment();
314         mFragment.onPause();
315         mFragment.onStop();
316 
317         mFragment.onDestroy();
318 
319         verify(mContentResolver).unregisterContentObserver(any(SettingsContentObserver.class));
320         verify(mActivity).unregisterReceiver(any(PackageMonitor.class));
321 
322     }
323 
324     @Test
325     @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
onContentChanged_updatePreferenceInForeground_preferenceUpdated()326     public void onContentChanged_updatePreferenceInForeground_preferenceUpdated() {
327         setupFragment();
328         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
329                 singletonList(mServiceInfo));
330 
331         mFragment.onContentChanged();
332 
333         RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference(
334                 COMPONENT_NAME.flattenToString());
335 
336         assertThat(preference).isNotNull();
337 
338     }
339 
340     @Test
341     @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
onContentChanged_updatePreferenceInBackground_preferenceUpdated()342     public void onContentChanged_updatePreferenceInBackground_preferenceUpdated() {
343         setupFragment();
344         mFragment.onPause();
345         mFragment.onStop();
346 
347         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
348                 singletonList(mServiceInfo));
349 
350         mFragment.onContentChanged();
351         mFragment.onStart();
352 
353         RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference(
354                 COMPONENT_NAME.flattenToString());
355 
356         assertThat(preference).isNotNull();
357 
358     }
359 
getMockAccessibilityServiceInfo(String packageName, String className)360     private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName,
361             String className) {
362         final ApplicationInfo applicationInfo = new ApplicationInfo();
363         final ServiceInfo serviceInfo = new ServiceInfo();
364         applicationInfo.packageName = packageName;
365         serviceInfo.packageName = packageName;
366         serviceInfo.name = className;
367         serviceInfo.applicationInfo = applicationInfo;
368 
369         final ResolveInfo resolveInfo = new ResolveInfo();
370         resolveInfo.serviceInfo = serviceInfo;
371         try {
372             final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo,
373                     mContext);
374             info.setComponentName(new ComponentName(packageName, className));
375             return info;
376         } catch (XmlPullParserException | IOException e) {
377             // Do nothing
378         }
379 
380         return null;
381     }
382 
setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo)383     private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) {
384         final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class);
385         when(mockInfo.getActivityInfo()).thenReturn(activityInfo);
386         when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL);
387         when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY);
388         when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION);
389         when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME);
390     }
391 
setInvisibleToggleFragmentType(AccessibilityServiceInfo info)392     private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) {
393         info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
394         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
395     }
396 
setupFragment()397     private void setupFragment() {
398         mFragment.onAttach(mContext);
399         mFragment.onCreate(Bundle.EMPTY);
400         mFragment.onStart();
401         mFragment.onResume();
402     }
403 }
404