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