• 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 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.Mockito.doNothing;
23 import static org.mockito.Mockito.doReturn;
24 import static org.mockito.Mockito.spy;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
27 
28 import android.accessibilityservice.AccessibilityServiceInfo;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.ResolveInfo;
35 import android.content.pm.ServiceInfo;
36 import android.os.Bundle;
37 import android.service.quicksettings.TileService;
38 import android.view.accessibility.AccessibilityManager;
39 
40 import androidx.annotation.NonNull;
41 import androidx.preference.PreferenceManager;
42 import androidx.preference.PreferenceScreen;
43 import androidx.test.core.app.ApplicationProvider;
44 
45 import com.android.internal.accessibility.common.ShortcutConstants;
46 import com.android.settings.SettingsActivity;
47 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
48 import com.android.settings.widget.SettingsMainSwitchPreference;
49 
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.Answers;
54 import org.mockito.ArgumentCaptor;
55 import org.mockito.Mock;
56 import org.mockito.MockitoAnnotations;
57 import org.robolectric.RobolectricTestRunner;
58 import org.robolectric.Shadows;
59 import org.robolectric.shadow.api.Shadow;
60 import org.robolectric.shadows.ShadowAccessibilityManager;
61 import org.robolectric.shadows.ShadowPackageManager;
62 
63 import java.util.List;
64 import java.util.Set;
65 
66 /** Tests for {@link ToggleAccessibilityServicePreferenceFragment} */
67 @RunWith(RobolectricTestRunner.class)
68 public class ToggleAccessibilityServicePreferenceFragmentTest {
69 
70     private static final String PLACEHOLDER_PACKAGE_NAME = "com.placeholder.example";
71     private static final String PLACEHOLDER_PACKAGE_NAME2 = "com.placeholder.example2";
72     private static final String PLACEHOLDER_SERVICE_CLASS_NAME = "a11yservice1";
73     private static final String PLACEHOLDER_SERVICE_CLASS_NAME2 = "a11yservice2";
74     private static final String PLACEHOLDER_TILE_CLASS_NAME =
75             PLACEHOLDER_PACKAGE_NAME + "tile.placeholder";
76     private static final String PLACEHOLDER_TILE_CLASS_NAME2 =
77             PLACEHOLDER_PACKAGE_NAME + "tile.placeholder2";
78     private static final ComponentName PLACEHOLDER_TILE_COMPONENT_NAME = new ComponentName(
79             PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_TILE_CLASS_NAME);
80     private static final String PLACEHOLDER_TILE_NAME =
81             PLACEHOLDER_PACKAGE_NAME + "tile.placeholder";
82     private static final String PLACEHOLDER_TILE_NAME2 =
83             PLACEHOLDER_PACKAGE_NAME + "tile.placeholder2";
84     private static final int NO_DIALOG = -1;
85 
86     private TestToggleAccessibilityServicePreferenceFragment mFragment;
87     private PreferenceScreen mScreen;
88     private Context mContext;
89 
90     private ShadowAccessibilityManager mShadowAccessibilityManager;
91     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
92     private PreferenceManager mPreferenceManager;
93     @Mock
94     private AccessibilityManager mMockAccessibilityManager;
95 
96     @Before
setUpTestFragment()97     public void setUpTestFragment() {
98         MockitoAnnotations.initMocks(this);
99 
100         mContext = spy(ApplicationProvider.getApplicationContext());
101         mFragment = spy(new TestToggleAccessibilityServicePreferenceFragment());
102         mFragment.setArguments(new Bundle());
103         when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
104         when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext);
105         when(mFragment.getContext()).thenReturn(mContext);
106         mScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null));
107         when(mScreen.getPreferenceManager()).thenReturn(mPreferenceManager);
108         doReturn(mScreen).when(mFragment).getPreferenceScreen();
109         mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext));
110     }
111 
112     @Test
getAccessibilityServiceInfo()113     public void getAccessibilityServiceInfo() throws Throwable {
114         final AccessibilityServiceInfo info1 = getFakeAccessibilityServiceInfo(
115                 PLACEHOLDER_PACKAGE_NAME,
116                 PLACEHOLDER_SERVICE_CLASS_NAME);
117         final AccessibilityServiceInfo info2 = getFakeAccessibilityServiceInfo(
118                 PLACEHOLDER_PACKAGE_NAME,
119                 PLACEHOLDER_SERVICE_CLASS_NAME2);
120         final AccessibilityServiceInfo info3 = getFakeAccessibilityServiceInfo(
121                 PLACEHOLDER_PACKAGE_NAME2,
122                 PLACEHOLDER_SERVICE_CLASS_NAME);
123         final AccessibilityServiceInfo info4 = getFakeAccessibilityServiceInfo(
124                 PLACEHOLDER_PACKAGE_NAME2,
125                 PLACEHOLDER_SERVICE_CLASS_NAME2);
126         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
127                 List.of(info1, info2, info3, info4));
128 
129         mFragment.mComponentName = info3.getComponentName();
130 
131         assertThat(mFragment.getAccessibilityServiceInfo()).isEqualTo(info3);
132     }
133 
134     @Test
getAccessibilityServiceInfo_notFound()135     public void getAccessibilityServiceInfo_notFound() throws Throwable {
136         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(List.of());
137 
138         mFragment.mComponentName = getFakeAccessibilityServiceInfo(PLACEHOLDER_PACKAGE_NAME,
139                 PLACEHOLDER_SERVICE_CLASS_NAME).getComponentName();
140 
141         assertThat(mFragment.getAccessibilityServiceInfo()).isNull();
142     }
143 
144     @Test
serviceSupportsAccessibilityButton()145     public void serviceSupportsAccessibilityButton() throws Throwable {
146         final AccessibilityServiceInfo infoWithA11yButton = getFakeAccessibilityServiceInfo(
147                 PLACEHOLDER_PACKAGE_NAME,
148                 PLACEHOLDER_SERVICE_CLASS_NAME);
149         infoWithA11yButton.flags = AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
150         final AccessibilityServiceInfo infoWithoutA11yButton = getFakeAccessibilityServiceInfo(
151                 PLACEHOLDER_PACKAGE_NAME2,
152                 PLACEHOLDER_SERVICE_CLASS_NAME2);
153         infoWithoutA11yButton.flags = 0;
154         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
155                 List.of(infoWithA11yButton, infoWithoutA11yButton));
156 
157         mFragment.mComponentName = infoWithA11yButton.getComponentName();
158         assertThat(mFragment.serviceSupportsAccessibilityButton()).isTrue();
159         mFragment.mComponentName = infoWithoutA11yButton.getComponentName();
160         assertThat(mFragment.serviceSupportsAccessibilityButton()).isFalse();
161     }
162 
163     @Test
enableService_warningRequired_showWarning()164     public void enableService_warningRequired_showWarning() throws Throwable {
165         setupServiceWarningRequired(true);
166         mFragment.mToggleServiceSwitchPreference =
167                 new SettingsMainSwitchPreference(mContext, /* attrs= */null);
168 
169         mFragment.onCheckedChanged(null, true);
170 
171         assertThat(mFragment.mLastShownDialogId).isEqualTo(
172                 AccessibilityDialogUtils.DialogEnums.ENABLE_WARNING_FROM_TOGGLE);
173     }
174 
175     @Test
enableService_warningNotRequired_dontShowWarning()176     public void enableService_warningNotRequired_dontShowWarning() throws Throwable {
177         final AccessibilityServiceInfo info = setupServiceWarningRequired(false);
178         mFragment.mToggleServiceSwitchPreference =
179                 new SettingsMainSwitchPreference(mContext, /* attrs= */null);
180         mFragment.mPreferenceKey = info.getComponentName().flattenToString();
181 
182         mFragment.onCheckedChanged(null, true);
183 
184         assertThat(mFragment.mLastShownDialogId).isEqualTo(
185                 AccessibilityDialogUtils.DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
186     }
187 
188     @Test
toggleShortcutPreference_warningRequired_showWarning()189     public void toggleShortcutPreference_warningRequired_showWarning() throws Throwable {
190         setupServiceWarningRequired(true);
191         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */null);
192 
193         mFragment.mShortcutPreference.setChecked(true);
194         mFragment.onToggleClicked(mFragment.mShortcutPreference);
195 
196         assertThat(mFragment.mLastShownDialogId).isEqualTo(
197                 AccessibilityDialogUtils.DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE);
198         assertThat(mFragment.mShortcutPreference.isChecked()).isFalse();
199     }
200 
201     @Test
toggleShortcutPreference_warningNotRequired_dontShowWarning()202     public void toggleShortcutPreference_warningNotRequired_dontShowWarning() throws Throwable {
203         setupServiceWarningRequired(false);
204         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */null);
205 
206         mFragment.mShortcutPreference.setChecked(true);
207         mFragment.onToggleClicked(mFragment.mShortcutPreference);
208 
209         assertThat(mFragment.mLastShownDialogId).isEqualTo(
210                 AccessibilityDialogUtils.DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
211         assertThat(mFragment.mShortcutPreference.isChecked()).isTrue();
212     }
213 
214     @Test
clickShortcutSettingsPreference_warningRequired_showWarning()215     public void clickShortcutSettingsPreference_warningRequired_showWarning() throws Throwable {
216         setupServiceWarningRequired(true);
217         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */null);
218 
219         mFragment.onSettingsClicked(mFragment.mShortcutPreference);
220 
221         assertThat(mFragment.mLastShownDialogId).isEqualTo(
222                 AccessibilityDialogUtils.DialogEnums.ENABLE_WARNING_FROM_SHORTCUT);
223     }
224 
225     @Test
clickShortcutSettingsPreference_warningNotRequired_dontShowWarning_launchActivity()226     public void clickShortcutSettingsPreference_warningNotRequired_dontShowWarning_launchActivity()
227             throws Throwable {
228         setupServiceWarningRequired(false);
229         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */null);
230         doNothing().when(mContext).startActivity(any());
231 
232         mFragment.onSettingsClicked(mFragment.mShortcutPreference);
233 
234         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
235         verify(mContext).startActivity(captor.capture());
236         assertThat(captor.getValue().getExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
237                 .isEqualTo(EditShortcutsPreferenceFragment.class.getName());
238     }
239 
240     @Test
getDefaultShortcutTypes_isAccessibilityTool_hasAssociatedTile_qsTypeIsDefault()241     public void getDefaultShortcutTypes_isAccessibilityTool_hasAssociatedTile_qsTypeIsDefault()
242             throws Throwable {
243         PreferredShortcuts.clearPreferredShortcuts(mContext);
244         setupAccessibilityServiceInfoForFragment(
245                 /* isAccessibilityTool= */ true,
246                 /* tileService= */ PLACEHOLDER_TILE_COMPONENT_NAME
247                 /* warningRequired= */);
248 
249         assertThat(mFragment.getDefaultShortcutTypes())
250                 .isEqualTo(ShortcutConstants.UserShortcutType.QUICK_SETTINGS);
251     }
252 
253     @Test
getDefaultShortcutTypes_isNotAccessibilityTool_hasAssociatedTile_softwareTypeIsDefault()254     public void getDefaultShortcutTypes_isNotAccessibilityTool_hasAssociatedTile_softwareTypeIsDefault()
255             throws Throwable {
256         PreferredShortcuts.clearPreferredShortcuts(mContext);
257         setupAccessibilityServiceInfoForFragment(
258                 /* isAccessibilityTool= */ false,
259                 /* tileService= */ PLACEHOLDER_TILE_COMPONENT_NAME
260                 /* warningRequired= */);
261 
262         assertThat(mFragment.getDefaultShortcutTypes())
263                 .isEqualTo(ShortcutConstants.UserShortcutType.SOFTWARE);
264     }
265 
266     @Test
getDefaultShortcutTypes_isAccessibilityTool_noAssociatedTile_softwareTypeIsDefault()267     public void getDefaultShortcutTypes_isAccessibilityTool_noAssociatedTile_softwareTypeIsDefault()
268             throws Throwable {
269         PreferredShortcuts.clearPreferredShortcuts(mContext);
270         setupAccessibilityServiceInfoForFragment(
271                 /* isAccessibilityTool= */ true,
272                 /* tileService= */ null
273                 /* warningRequired= */);
274 
275         assertThat(mFragment.getDefaultShortcutTypes())
276                 .isEqualTo(ShortcutConstants.UserShortcutType.SOFTWARE);
277     }
278 
279     @Test
getDefaultShortcutTypes_isNotAccessibilityTool_noAssociatedTile_softwareTypeIsDefault()280     public void getDefaultShortcutTypes_isNotAccessibilityTool_noAssociatedTile_softwareTypeIsDefault()
281             throws Throwable {
282         PreferredShortcuts.clearPreferredShortcuts(mContext);
283         setupAccessibilityServiceInfoForFragment(
284                 /* isAccessibilityTool= */ false,
285                 /* tileService= */ null
286                 /* warningRequired= */);
287 
288         assertThat(mFragment.getDefaultShortcutTypes())
289                 .isEqualTo(ShortcutConstants.UserShortcutType.SOFTWARE);
290     }
291 
292     @Test
toggleShortcutPreference_noUserPreferredShortcut_hasQsTile_enableQsShortcut()293     public void toggleShortcutPreference_noUserPreferredShortcut_hasQsTile_enableQsShortcut()
294             throws Throwable {
295         PreferredShortcuts.clearPreferredShortcuts(mContext);
296         setupAccessibilityServiceInfoForFragment(
297                 /* isAccessibilityTool= */ true,
298                 /* tileService= */ PLACEHOLDER_TILE_COMPONENT_NAME
299                 /* warningRequired= */);
300         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */ null);
301 
302         mFragment.mShortcutPreference.setChecked(true);
303         mFragment.onToggleClicked(mFragment.mShortcutPreference);
304 
305         verify(mMockAccessibilityManager)
306                 .enableShortcutsForTargets(true,
307                         ShortcutConstants.UserShortcutType.QUICK_SETTINGS,
308                         Set.of(mFragment.mComponentName.flattenToString()), mContext.getUserId());
309     }
310 
311     @Test
toggleShortcutPreference_noUserPreferredShortcut_noQsTile_enableSoftwareShortcut()312     public void toggleShortcutPreference_noUserPreferredShortcut_noQsTile_enableSoftwareShortcut()
313             throws Throwable {
314         PreferredShortcuts.clearPreferredShortcuts(mContext);
315         setupAccessibilityServiceInfoForFragment(
316                 /* isAccessibilityTool= */ true,
317                 /* tileService= */ null
318                 /* warningRequired= */);
319         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */ null);
320 
321         mFragment.mShortcutPreference.setChecked(true);
322         mFragment.onToggleClicked(mFragment.mShortcutPreference);
323 
324         verify(mMockAccessibilityManager)
325                 .enableShortcutsForTargets(true,
326                         ShortcutConstants.UserShortcutType.SOFTWARE,
327                         Set.of(mFragment.mComponentName.flattenToString()), mContext.getUserId());
328     }
329 
330     @Test
toggleShortcutPreference_userPreferVolumeKeysShortcut_noQsTile_enableVolumeKeysShortcut()331     public void toggleShortcutPreference_userPreferVolumeKeysShortcut_noQsTile_enableVolumeKeysShortcut()
332             throws Throwable {
333         setupAccessibilityServiceInfoForFragment(
334                 /* isAccessibilityTool= */ true,
335                 /* tileService= */ null
336                 /* warningRequired= */);
337         String componentName = mFragment.mComponentName.flattenToString();
338         PreferredShortcuts.saveUserShortcutType(
339                 mContext,
340                 new PreferredShortcut(componentName, ShortcutConstants.UserShortcutType.HARDWARE));
341         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */ null);
342 
343         mFragment.mShortcutPreference.setChecked(true);
344         mFragment.onToggleClicked(mFragment.mShortcutPreference);
345 
346         verify(mMockAccessibilityManager)
347                 .enableShortcutsForTargets(
348                         true,
349                         ShortcutConstants.UserShortcutType.HARDWARE,
350                         Set.of(componentName),
351                         mContext.getUserId());
352     }
353 
354     @Test
toggleShortcutPreference_userPreferVolumeKeysShortcut_hasQsTile_enableVolumeKeysShortcut()355     public void toggleShortcutPreference_userPreferVolumeKeysShortcut_hasQsTile_enableVolumeKeysShortcut()
356             throws Throwable {
357         setupAccessibilityServiceInfoForFragment(
358                 /* isAccessibilityTool= */ true,
359                 /* tileService= */ PLACEHOLDER_TILE_COMPONENT_NAME
360                 /* warningRequired= */);
361         String componentName = mFragment.mComponentName.flattenToString();
362         PreferredShortcuts.saveUserShortcutType(
363                 mContext,
364                 new PreferredShortcut(componentName, ShortcutConstants.UserShortcutType.HARDWARE));
365         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */ null);
366 
367         mFragment.mShortcutPreference.setChecked(true);
368         mFragment.onToggleClicked(mFragment.mShortcutPreference);
369 
370         verify(mMockAccessibilityManager)
371                 .enableShortcutsForTargets(
372                         true,
373                         ShortcutConstants.UserShortcutType.HARDWARE,
374                         Set.of(componentName),
375                         mContext.getUserId());
376     }
377 
setupTileService(String packageName, String name, String tileName)378     private void setupTileService(String packageName, String name, String tileName) {
379         final Intent tileProbe = new Intent(TileService.ACTION_QS_TILE);
380         final ResolveInfo info = new ResolveInfo();
381         info.serviceInfo = new FakeServiceInfo(packageName, name, tileName);
382         final ShadowPackageManager shadowPackageManager =
383                 Shadows.shadowOf(mContext.getPackageManager());
384         shadowPackageManager.addResolveInfoForIntent(tileProbe, info);
385     }
386 
setupServiceWarningRequired(boolean required)387     private AccessibilityServiceInfo setupServiceWarningRequired(boolean required)
388             throws Throwable {
389         final AccessibilityServiceInfo info = getFakeAccessibilityServiceInfo(
390                 PLACEHOLDER_PACKAGE_NAME,
391                 PLACEHOLDER_SERVICE_CLASS_NAME);
392         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
393         mFragment.mComponentName = info.getComponentName();
394         when(mContext.getSystemService(AccessibilityManager.class))
395                 .thenReturn(mMockAccessibilityManager);
396         when(mMockAccessibilityManager.isAccessibilityServiceWarningRequired(any()))
397                 .thenReturn(required);
398         when(mFragment.getAccessibilityServiceInfo()).thenReturn(info);
399         return info;
400     }
401 
setupAccessibilityServiceInfoForFragment( boolean isAccessibilityTool, ComponentName tileService)402     private void setupAccessibilityServiceInfoForFragment(
403             boolean isAccessibilityTool, ComponentName tileService) throws Throwable {
404         AccessibilityServiceInfo info = setupServiceWarningRequired(false);
405         info.setAccessibilityTool(isAccessibilityTool);
406         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(List.of(info));
407         mFragment.mComponentName = info.getComponentName();
408         when(mFragment.getTileComponentName()).thenReturn(tileService);
409         when(mFragment.getAccessibilityServiceInfo()).thenReturn(info);
410     }
411 
412     private static class FakeServiceInfo extends ServiceInfo {
413         private String mTileName;
414 
FakeServiceInfo(String packageName, String name, String tileName)415         FakeServiceInfo(String packageName, String name, String tileName) {
416             this.packageName = packageName;
417             this.name = name;
418             mTileName = tileName;
419         }
420 
loadLabel(PackageManager mgr)421         public String loadLabel(PackageManager mgr) {
422             return mTileName;
423         }
424     }
425 
426     @NonNull
getFakeAccessibilityServiceInfo(String packageName, String className)427     private AccessibilityServiceInfo getFakeAccessibilityServiceInfo(String packageName,
428             String className) throws Throwable {
429         final ApplicationInfo applicationInfo = new ApplicationInfo();
430         final ServiceInfo serviceInfo = new ServiceInfo();
431         applicationInfo.packageName = packageName;
432         serviceInfo.packageName = packageName;
433         serviceInfo.name = className;
434         serviceInfo.applicationInfo = applicationInfo;
435         final ResolveInfo resolveInfo = new ResolveInfo();
436         resolveInfo.serviceInfo = serviceInfo;
437         final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, mContext);
438         ComponentName componentName = ComponentName.createRelative(packageName, className);
439         info.setComponentName(componentName);
440         return info;
441     }
442 
443     private static class TestToggleAccessibilityServicePreferenceFragment
444             extends ToggleAccessibilityServicePreferenceFragment {
445         int mLastShownDialogId = NO_DIALOG;
446 
447         @Override
getTileComponentName()448         protected ComponentName getTileComponentName() {
449             return PLACEHOLDER_TILE_COMPONENT_NAME;
450         }
451 
452         @Override
showDialog(int dialogId)453         protected void showDialog(int dialogId) {
454             mLastShownDialogId = dialogId;
455         }
456     }
457 }
458