1 /*
2  * Copyright (C) 2020 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.android.settings.accessibility.AccessibilityUtil.State.OFF;
20 import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
21 import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
22 import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_SAVED_USER_SHORTCUT_TYPE;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
29 import static org.mockito.Mockito.doReturn;
30 import static org.mockito.Mockito.mock;
31 import static org.mockito.Mockito.spy;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.when;
34 
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.DialogInterface;
38 import android.content.res.Resources;
39 import android.os.Bundle;
40 import android.provider.Settings;
41 import android.text.TextUtils;
42 import android.view.LayoutInflater;
43 import android.view.View;
44 import android.view.ViewGroup;
45 
46 import androidx.annotation.XmlRes;
47 import androidx.appcompat.app.AlertDialog;
48 import androidx.fragment.app.FragmentActivity;
49 import androidx.preference.Preference;
50 import androidx.preference.PreferenceManager;
51 import androidx.preference.PreferenceScreen;
52 import androidx.test.core.app.ApplicationProvider;
53 
54 import com.android.settings.DialogCreatable;
55 import com.android.settings.R;
56 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
57 import com.android.settings.testutils.shadow.ShadowFragment;
58 import com.android.settings.testutils.shadow.ShadowSettingsPreferenceFragment;
59 import com.android.settingslib.core.lifecycle.Lifecycle;
60 
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.mockito.Mockito;
65 import org.mockito.MockitoAnnotations;
66 import org.robolectric.Robolectric;
67 import org.robolectric.RobolectricTestRunner;
68 import org.robolectric.annotation.Config;
69 
70 @RunWith(RobolectricTestRunner.class)
71 @Config(shadows = {ShadowSettingsPreferenceFragment.class})
72 public class ToggleScreenMagnificationPreferenceFragmentTest {
73 
74     private static final String PLACEHOLDER_PACKAGE_NAME = "com.mock.example";
75     private static final String PLACEHOLDER_CLASS_NAME =
76             PLACEHOLDER_PACKAGE_NAME + ".mock_a11y_service";
77     private static final ComponentName PLACEHOLDER_COMPONENT_NAME = new ComponentName(
78             PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CLASS_NAME);
79     private static final String PLACEHOLDER_DIALOG_TITLE = "title";
80 
81     private static final String SOFTWARE_SHORTCUT_KEY =
82             Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
83     private static final String HARDWARE_SHORTCUT_KEY =
84             Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
85     private static final String TRIPLETAP_SHORTCUT_KEY =
86             Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
87 
88     private static final String MAGNIFICATION_CONTROLLER_NAME =
89             "com.android.server.accessibility.MagnificationController";
90 
91     private TestToggleScreenMagnificationPreferenceFragment mFragment;
92     private Context mContext;
93     private Resources mResources;
94 
95     private FragmentActivity mActivity;
96 
97     @Before
setUpTestFragment()98     public void setUpTestFragment() {
99         MockitoAnnotations.initMocks(this);
100 
101         mContext = spy(ApplicationProvider.getApplicationContext());
102         mActivity = Robolectric.setupActivity(FragmentActivity.class);
103         mFragment = spy(new TestToggleScreenMagnificationPreferenceFragment(mContext));
104         mResources = spy(mContext.getResources());
105         when(mContext.getResources()).thenReturn(mResources);
106         when(mFragment.getContext().getResources()).thenReturn(mResources);
107         doReturn(mActivity).when(mFragment).getActivity();
108     }
109 
110     @Test
hasValueInSettings_putValue_hasValue()111     public void hasValueInSettings_putValue_hasValue() {
112         setMagnificationTripleTapEnabled(/* enabled= */ true);
113 
114         assertThat(ToggleScreenMagnificationPreferenceFragment.hasMagnificationValuesInSettings(
115                 mContext, UserShortcutType.TRIPLETAP)).isTrue();
116     }
117 
118     @Test
optInAllValuesToSettings_optInValue_haveMatchString()119     public void optInAllValuesToSettings_optInValue_haveMatchString() {
120         int shortcutTypes = UserShortcutType.SOFTWARE | UserShortcutType.TRIPLETAP;
121 
122         ToggleScreenMagnificationPreferenceFragment.optInAllMagnificationValuesToSettings(mContext,
123                 shortcutTypes);
124 
125         assertThat(getStringFromSettings(SOFTWARE_SHORTCUT_KEY)).isEqualTo(
126                 MAGNIFICATION_CONTROLLER_NAME);
127         assertThat(getMagnificationTripleTapStatus()).isTrue();
128 
129     }
130 
131     @Test
optInAllValuesToSettings_existOtherValue_optInValue_haveMatchString()132     public void optInAllValuesToSettings_existOtherValue_optInValue_haveMatchString() {
133         putStringIntoSettings(SOFTWARE_SHORTCUT_KEY, PLACEHOLDER_COMPONENT_NAME.flattenToString());
134 
135         ToggleScreenMagnificationPreferenceFragment.optInAllMagnificationValuesToSettings(mContext,
136                 UserShortcutType.SOFTWARE);
137 
138         assertThat(getStringFromSettings(SOFTWARE_SHORTCUT_KEY)).isEqualTo(
139                 PLACEHOLDER_COMPONENT_NAME.flattenToString() + ":" + MAGNIFICATION_CONTROLLER_NAME);
140     }
141 
142     @Test
optOutAllValuesToSettings_optOutValue_emptyString()143     public void optOutAllValuesToSettings_optOutValue_emptyString() {
144         putStringIntoSettings(SOFTWARE_SHORTCUT_KEY, MAGNIFICATION_CONTROLLER_NAME);
145         putStringIntoSettings(HARDWARE_SHORTCUT_KEY, MAGNIFICATION_CONTROLLER_NAME);
146         setMagnificationTripleTapEnabled(/* enabled= */ true);
147         int shortcutTypes =
148                 UserShortcutType.SOFTWARE | UserShortcutType.HARDWARE | UserShortcutType.TRIPLETAP;
149 
150         ToggleScreenMagnificationPreferenceFragment.optOutAllMagnificationValuesFromSettings(
151                 mContext, shortcutTypes);
152 
153         assertThat(getStringFromSettings(SOFTWARE_SHORTCUT_KEY)).isEmpty();
154         assertThat(getStringFromSettings(HARDWARE_SHORTCUT_KEY)).isEmpty();
155         assertThat(getMagnificationTripleTapStatus()).isFalse();
156     }
157 
158     @Test
optOutValueFromSettings_existOtherValue_optOutValue_haveMatchString()159     public void optOutValueFromSettings_existOtherValue_optOutValue_haveMatchString() {
160         putStringIntoSettings(SOFTWARE_SHORTCUT_KEY,
161                 PLACEHOLDER_COMPONENT_NAME.flattenToString() + ":" + MAGNIFICATION_CONTROLLER_NAME);
162         putStringIntoSettings(HARDWARE_SHORTCUT_KEY,
163                 PLACEHOLDER_COMPONENT_NAME.flattenToString() + ":" + MAGNIFICATION_CONTROLLER_NAME);
164         int shortcutTypes = UserShortcutType.SOFTWARE | UserShortcutType.HARDWARE;
165 
166         ToggleScreenMagnificationPreferenceFragment.optOutAllMagnificationValuesFromSettings(
167                 mContext, shortcutTypes);
168 
169         assertThat(getStringFromSettings(SOFTWARE_SHORTCUT_KEY)).isEqualTo(
170                 PLACEHOLDER_COMPONENT_NAME.flattenToString());
171         assertThat(getStringFromSettings(HARDWARE_SHORTCUT_KEY)).isEqualTo(
172                 PLACEHOLDER_COMPONENT_NAME.flattenToString());
173     }
174 
175     @Test
updateShortcutPreferenceData_assignDefaultValueToVariable()176     public void updateShortcutPreferenceData_assignDefaultValueToVariable() {
177         mFragment.updateShortcutPreferenceData();
178 
179         final int expectedType = PreferredShortcuts.retrieveUserShortcutType(mContext,
180                 MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
181         // Compare to default UserShortcutType
182         assertThat(expectedType).isEqualTo(UserShortcutType.SOFTWARE);
183     }
184 
185     @Test
updateShortcutPreferenceData_hasValueInSettings_assignToVariable()186     public void updateShortcutPreferenceData_hasValueInSettings_assignToVariable() {
187         putStringIntoSettings(SOFTWARE_SHORTCUT_KEY, MAGNIFICATION_CONTROLLER_NAME);
188         setMagnificationTripleTapEnabled(/* enabled= */ true);
189 
190         mFragment.updateShortcutPreferenceData();
191 
192         final int expectedType = PreferredShortcuts.retrieveUserShortcutType(mContext,
193                 MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
194         assertThat(expectedType).isEqualTo(UserShortcutType.SOFTWARE | UserShortcutType.TRIPLETAP);
195     }
196 
197     @Test
updateShortcutPreferenceData_hasValueInSharedPreference_assignToVariable()198     public void updateShortcutPreferenceData_hasValueInSharedPreference_assignToVariable() {
199         final PreferredShortcut tripleTapShortcut = new PreferredShortcut(
200                 MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.TRIPLETAP);
201 
202         putUserShortcutTypeIntoSharedPreference(mContext, tripleTapShortcut);
203         mFragment.updateShortcutPreferenceData();
204 
205         final int expectedType = PreferredShortcuts.retrieveUserShortcutType(mContext,
206                 MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
207         assertThat(expectedType).isEqualTo(UserShortcutType.TRIPLETAP);
208     }
209 
210     @Test
setupMagnificationEditShortcutDialog_shortcutPreferenceOff_checkboxIsEmptyValue()211     public void setupMagnificationEditShortcutDialog_shortcutPreferenceOff_checkboxIsEmptyValue() {
212         mContext.setTheme(R.style.Theme_AppCompat);
213         final AlertDialog dialog = AccessibilityDialogUtils.showEditShortcutDialog(
214                 mContext, DialogType.EDIT_SHORTCUT_MAGNIFICATION, PLACEHOLDER_DIALOG_TITLE,
215                 this::callEmptyOnClicked);
216         final ShortcutPreference shortcutPreference = new ShortcutPreference(mContext, /* attrs= */
217                 null);
218         mFragment.mShortcutPreference = shortcutPreference;
219 
220         mFragment.mShortcutPreference.setChecked(false);
221         mFragment.setupMagnificationEditShortcutDialog(dialog);
222 
223         final int checkboxValue = mFragment.getShortcutTypeCheckBoxValue();
224         assertThat(checkboxValue).isEqualTo(UserShortcutType.EMPTY);
225     }
226 
227     @Test
setupMagnificationEditShortcutDialog_shortcutPreferenceOn_checkboxIsSavedValue()228     public void setupMagnificationEditShortcutDialog_shortcutPreferenceOn_checkboxIsSavedValue() {
229         mContext.setTheme(R.style.Theme_AppCompat);
230         final AlertDialog dialog = AccessibilityDialogUtils.showEditShortcutDialog(
231                 mContext, DialogType.EDIT_SHORTCUT_MAGNIFICATION, PLACEHOLDER_DIALOG_TITLE,
232                 this::callEmptyOnClicked);
233         final ShortcutPreference shortcutPreference = new ShortcutPreference(mContext, /* attrs= */
234                 null);
235         final PreferredShortcut tripletapShortcut = new PreferredShortcut(
236                 MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.TRIPLETAP);
237         mFragment.mShortcutPreference = shortcutPreference;
238 
239         PreferredShortcuts.saveUserShortcutType(mContext, tripletapShortcut);
240         mFragment.mShortcutPreference.setChecked(true);
241         mFragment.setupMagnificationEditShortcutDialog(dialog);
242 
243         final int checkboxValue = mFragment.getShortcutTypeCheckBoxValue();
244         assertThat(checkboxValue).isEqualTo(UserShortcutType.TRIPLETAP);
245     }
246 
247     @Test
248     @Config(shadows = ShadowFragment.class)
restoreValueFromSavedInstanceState_assignToVariable()249     public void restoreValueFromSavedInstanceState_assignToVariable() {
250         mContext.setTheme(R.style.Theme_AppCompat);
251         final AlertDialog dialog = AccessibilityDialogUtils.showEditShortcutDialog(
252                 mContext, DialogType.EDIT_SHORTCUT_MAGNIFICATION, PLACEHOLDER_DIALOG_TITLE,
253                 this::callEmptyOnClicked);
254         final Bundle savedInstanceState = new Bundle();
255         mFragment.mShortcutPreference = new ShortcutPreference(mContext, /* attrs= */ null);
256 
257         savedInstanceState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE,
258                 UserShortcutType.HARDWARE | UserShortcutType.TRIPLETAP);
259         mFragment.onCreate(savedInstanceState);
260         mFragment.setupMagnificationEditShortcutDialog(dialog);
261         final int value = mFragment.getShortcutTypeCheckBoxValue();
262         mFragment.saveNonEmptyUserShortcutType(value);
263 
264         final int expectedType = PreferredShortcuts.retrieveUserShortcutType(mContext,
265                 MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
266         assertThat(value).isEqualTo(6);
267         assertThat(expectedType).isEqualTo(UserShortcutType.HARDWARE | UserShortcutType.TRIPLETAP);
268     }
269 
270     @Test
onCreateView_notSupportsMagnificationArea_settingsPreferenceIsNull()271     public void onCreateView_notSupportsMagnificationArea_settingsPreferenceIsNull() {
272         when(mResources.getBoolean(
273                 com.android.internal.R.bool.config_magnification_area))
274                 .thenReturn(false);
275 
276         mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
277 
278         assertThat(mFragment.mSettingsPreference).isNull();
279     }
280 
281     @Test
onCreateView_setDialogDelegateAndAddTheControllerToLifeCycleObserver()282     public void onCreateView_setDialogDelegateAndAddTheControllerToLifeCycleObserver() {
283         mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
284 
285         verify(mFragment).setDialogDelegate(any(MagnificationModePreferenceController.class));
286         verify(mFragment.mSpyLifeyCycle).addObserver(
287                 any(MagnificationModePreferenceController.class));
288     }
289 
290     @Test
onCreateDialog_setDialogDelegate_invokeDialogDelegate()291     public void onCreateDialog_setDialogDelegate_invokeDialogDelegate() {
292         final DialogCreatable dialogDelegate = mock(DialogCreatable.class, RETURNS_DEEP_STUBS);
293         when(dialogDelegate.getDialogMetricsCategory(anyInt())).thenReturn(1);
294         mFragment.setDialogDelegate(dialogDelegate);
295 
296         mFragment.onCreateDialog(1);
297         mFragment.getDialogMetricsCategory(1);
298 
299         verify(dialogDelegate).onCreateDialog(1);
300         verify(dialogDelegate).getDialogMetricsCategory(1);
301     }
302 
putStringIntoSettings(String key, String componentName)303     private void putStringIntoSettings(String key, String componentName) {
304         Settings.Secure.putString(mContext.getContentResolver(), key, componentName);
305     }
306 
putUserShortcutTypeIntoSharedPreference(Context context, PreferredShortcut shortcut)307     private void putUserShortcutTypeIntoSharedPreference(Context context,
308             PreferredShortcut shortcut) {
309         PreferredShortcuts.saveUserShortcutType(context, shortcut);
310     }
311 
setMagnificationTripleTapEnabled(boolean enabled)312     private void setMagnificationTripleTapEnabled(boolean enabled) {
313         Settings.Secure.putInt(mContext.getContentResolver(), TRIPLETAP_SHORTCUT_KEY,
314                 enabled ? ON : OFF);
315     }
316 
getStringFromSettings(String key)317     private String getStringFromSettings(String key) {
318         return Settings.Secure.getString(mContext.getContentResolver(), key);
319     }
320 
getMagnificationTripleTapStatus()321     private boolean getMagnificationTripleTapStatus() {
322         return Settings.Secure.getInt(mContext.getContentResolver(), TRIPLETAP_SHORTCUT_KEY, OFF)
323                 == ON;
324     }
325 
callEmptyOnClicked(DialogInterface dialog, int which)326     private void callEmptyOnClicked(DialogInterface dialog, int which) {}
327 
328     /**
329      * a test fragment that initializes PreferenceScreen for testing.
330      */
331     static class TestToggleScreenMagnificationPreferenceFragment
332             extends ToggleScreenMagnificationPreferenceFragment {
333 
334         private final Lifecycle mSpyLifeyCycle = Mockito.mock(Lifecycle.class);
335 
336         private final Context mContext;
337         private final PreferenceManager mPreferenceManager;
338 
TestToggleScreenMagnificationPreferenceFragment(Context context)339         TestToggleScreenMagnificationPreferenceFragment(Context context) {
340             super();
341             mContext = context;
342             mPreferenceManager = new PreferenceManager(context);
343             mPreferenceManager.setPreferences(mPreferenceManager.createPreferenceScreen(context));
344             setArguments(new Bundle());
345         }
346 
347         @Override
onPreferenceToggled(String preferenceKey, boolean enabled)348         protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
349         }
350 
351         @Override
getMetricsCategory()352         public int getMetricsCategory() {
353             return 0;
354         }
355 
356         @Override
getUserShortcutTypes()357         int getUserShortcutTypes() {
358             return 0;
359         }
360 
361         @Override
getPreferenceScreenResId()362         public int getPreferenceScreenResId() {
363             return R.xml.placeholder_prefs;
364         }
365 
366         @Override
getPreferenceScreen()367         public PreferenceScreen getPreferenceScreen() {
368             return mPreferenceManager.getPreferenceScreen();
369         }
370 
371         @Override
findPreference(CharSequence key)372         public <T extends Preference> T findPreference(CharSequence key) {
373             if (TextUtils.isEmpty(key)) {
374                 return null;
375             }
376             return getPreferenceScreen().findPreference(key);
377         }
378 
379         @Override
getPreferenceManager()380         public PreferenceManager getPreferenceManager() {
381             return mPreferenceManager;
382         }
383 
384         @Override
onViewCreated(View view, Bundle savedInstanceState)385         public void onViewCreated(View view, Bundle savedInstanceState) {
386             // do nothing
387         }
388 
389         @Override
onDestroyView()390         public void onDestroyView() {
391             // do nothing
392         }
393 
394         @Override
addPreferencesFromResource(@mlRes int preferencesResId)395         public void addPreferencesFromResource(@XmlRes int preferencesResId) {
396             // do nothing
397         }
398 
399         @Override
updateShortcutPreference()400         protected void updateShortcutPreference() {
401             // UI related function, do nothing in tests
402         }
403 
404         @Override
getSettingsLifecycle()405         public Lifecycle getSettingsLifecycle() {
406             return mSpyLifeyCycle;
407         }
408 
409         @Override
getContext()410         public Context getContext() {
411             return mContext;
412         }
413     }
414 }
415