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