1 /* 2 * Copyright (C) 2013 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.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; 20 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; 21 import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; 22 import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; 23 import static com.android.settings.accessibility.AccessibilityUtil.State.ON; 24 25 import android.app.Dialog; 26 import android.app.settings.SettingsEnums; 27 import android.content.ComponentName; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.DialogInterface; 31 import android.content.pm.PackageManager; 32 import android.icu.text.CaseMap; 33 import android.icu.text.MessageFormat; 34 import android.net.Uri; 35 import android.os.Bundle; 36 import android.os.UserHandle; 37 import android.provider.DeviceConfig; 38 import android.provider.Settings; 39 import android.text.TextUtils; 40 import android.view.LayoutInflater; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.view.accessibility.AccessibilityManager; 44 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; 45 import android.widget.CheckBox; 46 47 import androidx.annotation.Nullable; 48 import androidx.preference.Preference; 49 import androidx.preference.PreferenceCategory; 50 import androidx.preference.SwitchPreferenceCompat; 51 import androidx.preference.TwoStatePreference; 52 53 import com.android.internal.annotations.VisibleForTesting; 54 import com.android.server.accessibility.Flags; 55 import com.android.settings.DialogCreatable; 56 import com.android.settings.R; 57 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType; 58 import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; 59 import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType; 60 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment; 61 import com.android.settings.utils.LocaleUtils; 62 import com.android.settingslib.core.AbstractPreferenceController; 63 import com.android.settingslib.widget.IllustrationPreference; 64 65 import com.google.android.setupcompat.util.WizardManagerHelper; 66 67 import java.util.ArrayList; 68 import java.util.List; 69 import java.util.Locale; 70 import java.util.Set; 71 import java.util.StringJoiner; 72 73 /** 74 * Fragment that shows the actual UI for providing basic magnification accessibility service setup 75 * and does not have toggle bar to turn on service to use. 76 */ 77 public class ToggleScreenMagnificationPreferenceFragment extends 78 ToggleFeaturePreferenceFragment implements 79 MagnificationModePreferenceController.DialogHelper { 80 81 private static final String TAG = "ToggleScreenMagnificationPreferenceFragment"; 82 private static final char COMPONENT_NAME_SEPARATOR = ':'; 83 private static final TextUtils.SimpleStringSplitter sStringColonSplitter = 84 new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); 85 86 // TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util. 87 private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; 88 private CheckBox mSoftwareTypeCheckBox; 89 private CheckBox mHardwareTypeCheckBox; 90 private CheckBox mTripleTapTypeCheckBox; 91 @Nullable private CheckBox mTwoFingerTripleTapTypeCheckBox; 92 private DialogCreatable mDialogDelegate; 93 94 private boolean mInSetupWizard; 95 96 @Override onCreate(Bundle savedInstanceState)97 public void onCreate(Bundle savedInstanceState) { 98 super.onCreate(savedInstanceState); 99 getActivity().setTitle(R.string.accessibility_screen_magnification_title); 100 mInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 101 } 102 103 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)104 public View onCreateView(LayoutInflater inflater, ViewGroup container, 105 Bundle savedInstanceState) { 106 mPackageName = getString(R.string.accessibility_screen_magnification_title); 107 mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) 108 .authority(getPrefContext().getPackageName()) 109 .appendPath(String.valueOf(R.raw.a11y_magnification_banner)) 110 .build(); 111 mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { 112 removeDialog(DialogEnums.EDIT_SHORTCUT); 113 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 114 }; 115 116 final View view = super.onCreateView(inflater, container, savedInstanceState); 117 updateFooterPreference(); 118 return view; 119 } 120 updateFooterPreference()121 private void updateFooterPreference() { 122 final String title = getPrefContext().getString( 123 R.string.accessibility_screen_magnification_about_title); 124 final String learnMoreText = getPrefContext().getString( 125 R.string.accessibility_screen_magnification_footer_learn_more_content_description); 126 mFooterPreferenceController.setIntroductionTitle(title); 127 mFooterPreferenceController.setupHelpLink(getHelpResource(), learnMoreText); 128 mFooterPreferenceController.displayPreference(getPreferenceScreen()); 129 } 130 131 @Override onResume()132 public void onResume() { 133 super.onResume(); 134 final IllustrationPreference illustrationPreference = 135 getPreferenceScreen().findPreference(KEY_ANIMATED_IMAGE); 136 if (illustrationPreference != null) { 137 illustrationPreference.applyDynamicColor(); 138 } 139 140 final AccessibilityManager am = getPrefContext().getSystemService( 141 AccessibilityManager.class); 142 am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 143 } 144 145 @Override getPreferenceScreenResId()146 protected int getPreferenceScreenResId() { 147 // TODO(b/171272809): Add back when controllers move to static type 148 return 0; 149 } 150 151 @Override getLogTag()152 protected String getLogTag() { 153 return TAG; 154 } 155 156 @Override onPause()157 public void onPause() { 158 final AccessibilityManager am = getPrefContext().getSystemService( 159 AccessibilityManager.class); 160 am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 161 162 super.onPause(); 163 } 164 165 @Override onCreateDialog(int dialogId)166 public Dialog onCreateDialog(int dialogId) { 167 if (mDialogDelegate != null) { 168 mDialog = mDialogDelegate.onCreateDialog(dialogId); 169 if (mDialog != null) { 170 return mDialog; 171 } 172 } 173 switch (dialogId) { 174 case DialogEnums.GESTURE_NAVIGATION_TUTORIAL: 175 return AccessibilityShortcutsTutorial 176 .showAccessibilityGestureTutorialDialog(getPrefContext()); 177 case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT: 178 final CharSequence dialogTitle = getShortcutTitle(); 179 final int dialogType = mInSetupWizard 180 ? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW 181 : DialogType.EDIT_SHORTCUT_MAGNIFICATION; 182 mDialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(), 183 dialogType, dialogTitle, this::callOnAlertDialogCheckboxClicked); 184 setupMagnificationEditShortcutDialog(mDialog); 185 return mDialog; 186 default: 187 return super.onCreateDialog(dialogId); 188 } 189 } 190 191 @Override initSettingsPreference()192 protected void initSettingsPreference() { 193 // If the device doesn't support window magnification feature, it should hide the 194 // settings preference. 195 final boolean supportWindowMagnification = 196 getContext().getResources().getBoolean( 197 com.android.internal.R.bool.config_magnification_area) 198 && getContext().getPackageManager().hasSystemFeature( 199 PackageManager.FEATURE_WINDOW_MAGNIFICATION); 200 if (!supportWindowMagnification) { 201 return; 202 } 203 mSettingsPreference = new Preference(getPrefContext()); 204 mSettingsPreference.setTitle(R.string.accessibility_magnification_mode_title); 205 mSettingsPreference.setKey(MagnificationModePreferenceController.PREF_KEY); 206 mSettingsPreference.setPersistent(false); 207 208 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 209 generalCategory.addPreference(mSettingsPreference); 210 211 final MagnificationModePreferenceController magnificationModePreferenceController = 212 new MagnificationModePreferenceController(getContext(), 213 MagnificationModePreferenceController.PREF_KEY); 214 magnificationModePreferenceController.setDialogHelper(this); 215 getSettingsLifecycle().addObserver(magnificationModePreferenceController); 216 magnificationModePreferenceController.displayPreference(getPreferenceScreen()); 217 addPreferenceController(magnificationModePreferenceController); 218 219 addFollowTypingSetting(generalCategory); 220 addOneFingerPanningSetting(generalCategory); 221 addAlwaysOnSetting(generalCategory); 222 addJoystickSetting(generalCategory); 223 } 224 225 @Override onProcessArguments(Bundle arguments)226 protected void onProcessArguments(Bundle arguments) { 227 Context context = getContext(); 228 229 if (!arguments.containsKey(AccessibilitySettings.EXTRA_PREFERENCE_KEY)) { 230 arguments.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, 231 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); 232 } 233 234 if (!arguments.containsKey(AccessibilitySettings.EXTRA_INTRO)) { 235 arguments.putCharSequence(AccessibilitySettings.EXTRA_INTRO, 236 context.getString(R.string.accessibility_screen_magnification_intro_text)); 237 } 238 239 if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) { 240 String summary = MessageFormat.format( 241 context.getString(R.string.accessibility_screen_magnification_summary), 242 new Object[]{1, 2, 3, 4, 5}); 243 arguments.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary); 244 } 245 246 super.onProcessArguments(arguments); 247 } 248 addFollowTypingSetting(PreferenceCategory generalCategory)249 private void addFollowTypingSetting(PreferenceCategory generalCategory) { 250 var followingTypingSwitchPreference = new SwitchPreferenceCompat(getPrefContext()); 251 followingTypingSwitchPreference.setTitle( 252 R.string.accessibility_screen_magnification_follow_typing_title); 253 followingTypingSwitchPreference.setSummary( 254 R.string.accessibility_screen_magnification_follow_typing_summary); 255 followingTypingSwitchPreference.setKey( 256 MagnificationFollowTypingPreferenceController.PREF_KEY); 257 generalCategory.addPreference(followingTypingSwitchPreference); 258 259 var followTypingPreferenceController = new MagnificationFollowTypingPreferenceController( 260 getContext(), MagnificationFollowTypingPreferenceController.PREF_KEY); 261 followTypingPreferenceController.setInSetupWizard(mInSetupWizard); 262 followTypingPreferenceController.displayPreference(getPreferenceScreen()); 263 addPreferenceController(followTypingPreferenceController); 264 } 265 isAlwaysOnSettingEnabled()266 private boolean isAlwaysOnSettingEnabled() { 267 final boolean defaultValue = getContext().getResources().getBoolean( 268 com.android.internal.R.bool.config_magnification_always_on_enabled); 269 270 return DeviceConfig.getBoolean( 271 DeviceConfig.NAMESPACE_WINDOW_MANAGER, 272 "AlwaysOnMagnifier__enable_always_on_magnifier", 273 defaultValue 274 ); 275 } 276 addAlwaysOnSetting(PreferenceCategory generalCategory)277 private void addAlwaysOnSetting(PreferenceCategory generalCategory) { 278 if (!isAlwaysOnSettingEnabled()) { 279 return; 280 } 281 282 var alwaysOnPreference = new SwitchPreferenceCompat(getPrefContext()); 283 alwaysOnPreference.setTitle( 284 R.string.accessibility_screen_magnification_always_on_title); 285 alwaysOnPreference.setSummary( 286 R.string.accessibility_screen_magnification_always_on_summary); 287 alwaysOnPreference.setKey( 288 MagnificationAlwaysOnPreferenceController.PREF_KEY); 289 generalCategory.addPreference(alwaysOnPreference); 290 291 var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController( 292 getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY); 293 alwaysOnPreferenceController.setInSetupWizard(mInSetupWizard); 294 getSettingsLifecycle().addObserver(alwaysOnPreferenceController); 295 alwaysOnPreferenceController.displayPreference(getPreferenceScreen()); 296 addPreferenceController(alwaysOnPreferenceController); 297 } 298 addOneFingerPanningSetting(PreferenceCategory generalCategory)299 private void addOneFingerPanningSetting(PreferenceCategory generalCategory) { 300 if (!Flags.enableMagnificationOneFingerPanningGesture()) { 301 return; 302 } 303 304 var oneFingerPanningPreference = new SwitchPreferenceCompat(getPrefContext()); 305 oneFingerPanningPreference.setTitle( 306 R.string.accessibility_magnification_one_finger_panning_title); 307 oneFingerPanningPreference.setKey( 308 MagnificationOneFingerPanningPreferenceController.PREF_KEY); 309 generalCategory.addPreference(oneFingerPanningPreference); 310 311 var oneFingerPanningPreferenceController = 312 new MagnificationOneFingerPanningPreferenceController(getContext()); 313 oneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard); 314 getSettingsLifecycle().addObserver(oneFingerPanningPreferenceController); 315 oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen()); 316 addPreferenceController(oneFingerPanningPreferenceController); 317 } 318 addJoystickSetting(PreferenceCategory generalCategory)319 private void addJoystickSetting(PreferenceCategory generalCategory) { 320 if (!DeviceConfig.getBoolean( 321 DeviceConfig.NAMESPACE_WINDOW_MANAGER, 322 "MagnificationJoystick__enable_magnification_joystick", 323 false 324 )) { 325 return; 326 } 327 328 TwoStatePreference joystickPreference = new SwitchPreferenceCompat(getPrefContext()); 329 joystickPreference.setTitle( 330 R.string.accessibility_screen_magnification_joystick_title); 331 joystickPreference.setSummary( 332 R.string.accessibility_screen_magnification_joystick_summary); 333 joystickPreference.setKey( 334 MagnificationJoystickPreferenceController.PREF_KEY); 335 generalCategory.addPreference(joystickPreference); 336 337 MagnificationJoystickPreferenceController joystickPreferenceController = 338 new MagnificationJoystickPreferenceController( 339 getContext(), 340 MagnificationJoystickPreferenceController.PREF_KEY 341 ); 342 joystickPreferenceController.setInSetupWizard(mInSetupWizard); 343 joystickPreferenceController.displayPreference(getPreferenceScreen()); 344 addPreferenceController(joystickPreferenceController); 345 } 346 347 @Override showDialog(int dialogId)348 public void showDialog(int dialogId) { 349 super.showDialog(dialogId); 350 } 351 352 @Override setDialogDelegate(DialogCreatable delegate)353 public void setDialogDelegate(DialogCreatable delegate) { 354 mDialogDelegate = delegate; 355 } 356 357 @Override getShortcutTypeCheckBoxValue()358 protected int getShortcutTypeCheckBoxValue() { 359 if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) { 360 return NOT_SET; 361 } 362 363 int value = UserShortcutType.EMPTY; 364 if (mSoftwareTypeCheckBox.isChecked()) { 365 value |= UserShortcutType.SOFTWARE; 366 } 367 if (mHardwareTypeCheckBox.isChecked()) { 368 value |= UserShortcutType.HARDWARE; 369 } 370 if (mTripleTapTypeCheckBox.isChecked()) { 371 value |= UserShortcutType.TRIPLETAP; 372 } 373 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 374 if (mTwoFingerTripleTapTypeCheckBox.isChecked()) { 375 value |= UserShortcutType.TWOFINGER_DOUBLETAP; 376 } 377 } 378 return value; 379 } 380 381 @VisibleForTesting setupMagnificationEditShortcutDialog(Dialog dialog)382 void setupMagnificationEditShortcutDialog(Dialog dialog) { 383 final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut); 384 mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox); 385 setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox); 386 387 final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut); 388 mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox); 389 setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox); 390 391 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 392 final View dialogTwoFignerTripleTapView = 393 dialog.findViewById(R.id.two_finger_triple_tap_shortcut); 394 mTwoFingerTripleTapTypeCheckBox = dialogTwoFignerTripleTapView.findViewById( 395 R.id.checkbox); 396 setDialogTextAreaClickListener( 397 dialogTwoFignerTripleTapView, mTwoFingerTripleTapTypeCheckBox); 398 } 399 400 final View dialogTripleTapView = dialog.findViewById(R.id.triple_tap_shortcut); 401 mTripleTapTypeCheckBox = dialogTripleTapView.findViewById(R.id.checkbox); 402 setDialogTextAreaClickListener(dialogTripleTapView, mTripleTapTypeCheckBox); 403 404 final View advancedView = dialog.findViewById(R.id.advanced_shortcut); 405 if (mTripleTapTypeCheckBox.isChecked()) { 406 advancedView.setVisibility(View.GONE); 407 dialogTripleTapView.setVisibility(View.VISIBLE); 408 } 409 410 updateMagnificationEditShortcutDialogCheckBox(); 411 } 412 setDialogTextAreaClickListener(View dialogView, CheckBox checkBox)413 private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) { 414 final View dialogTextArea = dialogView.findViewById(R.id.container); 415 dialogTextArea.setOnClickListener(v -> checkBox.toggle()); 416 } 417 updateMagnificationEditShortcutDialogCheckBox()418 private void updateMagnificationEditShortcutDialogCheckBox() { 419 // If it is during onConfigChanged process then restore the value, or get the saved value 420 // when shortcutPreference is checked. 421 int value = restoreOnConfigChangedValue(); 422 if (value == NOT_SET) { 423 final int lastNonEmptyUserShortcutType = getUserPreferredShortcutTypes(); 424 value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType 425 : UserShortcutType.EMPTY; 426 } 427 428 mSoftwareTypeCheckBox.setChecked( 429 hasShortcutType(value, UserShortcutType.SOFTWARE)); 430 mHardwareTypeCheckBox.setChecked( 431 hasShortcutType(value, UserShortcutType.HARDWARE)); 432 mTripleTapTypeCheckBox.setChecked( 433 hasShortcutType(value, UserShortcutType.TRIPLETAP)); 434 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 435 mTwoFingerTripleTapTypeCheckBox.setChecked( 436 hasShortcutType(value, UserShortcutType.TWOFINGER_DOUBLETAP)); 437 } 438 } 439 restoreOnConfigChangedValue()440 private int restoreOnConfigChangedValue() { 441 final int savedValue = mSavedCheckBoxValue; 442 mSavedCheckBoxValue = NOT_SET; 443 return savedValue; 444 } 445 hasShortcutType(int value, @UserShortcutType int type)446 private boolean hasShortcutType(int value, @UserShortcutType int type) { 447 return (value & type) == type; 448 } 449 getSoftwareShortcutTypeSummary(Context context)450 private static CharSequence getSoftwareShortcutTypeSummary(Context context) { 451 int resId; 452 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 453 resId = R.string.accessibility_shortcut_edit_summary_software; 454 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 455 resId = R.string.accessibility_shortcut_edit_summary_software_gesture; 456 } else { 457 resId = R.string.accessibility_shortcut_edit_summary_software; 458 } 459 return context.getText(resId); 460 } 461 462 @Override registerKeysToObserverCallback( AccessibilitySettingsContentObserver contentObserver)463 protected void registerKeysToObserverCallback( 464 AccessibilitySettingsContentObserver contentObserver) { 465 super.registerKeysToObserverCallback(contentObserver); 466 467 var keysToObserve = List.of( 468 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, 469 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, 470 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED 471 ); 472 contentObserver.registerKeysToObserverCallback(keysToObserve, 473 key -> updatePreferencesState()); 474 } 475 updatePreferencesState()476 private void updatePreferencesState() { 477 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 478 getPreferenceControllers().forEach(controllers::addAll); 479 controllers.forEach(controller -> controller.updateState( 480 findPreference(controller.getPreferenceKey()))); 481 } 482 483 @Override getShortcutFeatureSettingsKeys()484 protected List<String> getShortcutFeatureSettingsKeys() { 485 final List<String> shortcutKeys = super.getShortcutFeatureSettingsKeys(); 486 shortcutKeys.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); 487 return shortcutKeys; 488 } 489 490 @Override getShortcutTypeSummary(Context context)491 protected CharSequence getShortcutTypeSummary(Context context) { 492 if (!mShortcutPreference.isChecked()) { 493 return context.getText(R.string.switch_off_text); 494 } 495 496 final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context, 497 MAGNIFICATION_CONTROLLER_NAME); 498 499 // LINT.IfChange(shortcut_type_ui_order) 500 final List<CharSequence> list = new ArrayList<>(); 501 if (android.view.accessibility.Flags.a11yQsShortcut()) { 502 if (hasShortcutType(shortcutTypes, UserShortcutType.QUICK_SETTINGS)) { 503 final CharSequence qsTitle = context.getText( 504 R.string.accessibility_feature_shortcut_setting_summary_quick_settings); 505 list.add(qsTitle); 506 } 507 } 508 if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) { 509 list.add(getSoftwareShortcutTypeSummary(context)); 510 } 511 if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) { 512 final CharSequence hardwareTitle = context.getText( 513 R.string.accessibility_shortcut_hardware_keyword); 514 list.add(hardwareTitle); 515 } 516 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 517 if (hasShortcutType(shortcutTypes, UserShortcutType.TWOFINGER_DOUBLETAP)) { 518 final CharSequence twoFingerDoubleTapTitle = context.getString( 519 R.string.accessibility_shortcut_two_finger_double_tap_keyword, 2); 520 list.add(twoFingerDoubleTapTitle); 521 } 522 } 523 if (hasShortcutType(shortcutTypes, UserShortcutType.TRIPLETAP)) { 524 final CharSequence tripleTapTitle = context.getText( 525 R.string.accessibility_shortcut_triple_tap_keyword); 526 list.add(tripleTapTitle); 527 } 528 // LINT.ThenChange(/res/xml/accessibility_edit_shortcuts.xml:shortcut_type_ui_order) 529 530 // Show software shortcut if first time to use. 531 if (list.isEmpty()) { 532 list.add(getSoftwareShortcutTypeSummary(context)); 533 } 534 535 return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */ 536 null, LocaleUtils.getConcatenatedString(list)); 537 } 538 539 @Override callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which)540 protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) { 541 final int value = getShortcutTypeCheckBoxValue(); 542 543 saveNonEmptyUserShortcutType(value); 544 optInAllMagnificationValuesToSettings(getPrefContext(), value); 545 optOutAllMagnificationValuesFromSettings(getPrefContext(), ~value); 546 mShortcutPreference.setChecked(value != UserShortcutType.EMPTY); 547 mShortcutPreference.setSummary( 548 getShortcutTypeSummary(getPrefContext())); 549 550 if (mHardwareTypeCheckBox.isChecked()) { 551 AccessibilityUtil.skipVolumeShortcutDialogTimeoutRestriction(getPrefContext()); 552 } 553 } 554 555 @Override getHelpResource()556 public int getHelpResource() { 557 return R.string.help_url_magnification; 558 } 559 560 @Override getMetricsCategory()561 public int getMetricsCategory() { 562 // TODO: Distinguish between magnification modes 563 return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION; 564 } 565 566 @Override getDialogMetricsCategory(int dialogId)567 public int getDialogMetricsCategory(int dialogId) { 568 if (mDialogDelegate != null) { 569 final int category = mDialogDelegate.getDialogMetricsCategory(dialogId); 570 if (category != 0) { 571 return category; 572 } 573 } 574 575 switch (dialogId) { 576 case DialogEnums.GESTURE_NAVIGATION_TUTORIAL: 577 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION; 578 case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL: 579 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON; 580 case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT: 581 return SettingsEnums.DIALOG_MAGNIFICATION_EDIT_SHORTCUT; 582 default: 583 return super.getDialogMetricsCategory(dialogId); 584 } 585 } 586 587 @Override getUserShortcutTypes()588 int getUserShortcutTypes() { 589 return getUserShortcutTypeFromSettings(getPrefContext()); 590 } 591 592 @Override getTileComponentName()593 ComponentName getTileComponentName() { 594 return null; 595 } 596 597 @Override getTileTooltipContent(@uickSettingsTooltipType int type)598 CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) { 599 return null; 600 } 601 602 @Override onPreferenceToggled(String preferenceKey, boolean enabled)603 protected void onPreferenceToggled(String preferenceKey, boolean enabled) { 604 if (enabled && TextUtils.equals( 605 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 606 preferenceKey)) { 607 showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); 608 } 609 Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? ON : OFF); 610 } 611 612 @Override onInstallSwitchPreferenceToggleSwitch()613 protected void onInstallSwitchPreferenceToggleSwitch() { 614 mToggleServiceSwitchPreference.setVisible(false); 615 } 616 617 @Override onToggleClicked(ShortcutPreference preference)618 public void onToggleClicked(ShortcutPreference preference) { 619 final int shortcutTypes = getUserPreferredShortcutTypes(); 620 if (preference.isChecked()) { 621 optInAllMagnificationValuesToSettings(getPrefContext(), shortcutTypes); 622 showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); 623 } else { 624 optOutAllMagnificationValuesFromSettings(getPrefContext(), shortcutTypes); 625 } 626 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 627 } 628 629 @Override onSettingsClicked(ShortcutPreference preference)630 public void onSettingsClicked(ShortcutPreference preference) { 631 if (com.android.settings.accessibility.Flags.editShortcutsInFullScreen()) { 632 EditShortcutsPreferenceFragment.showEditShortcutScreen( 633 requireContext(), 634 getMetricsCategory(), 635 getShortcutTitle(), 636 MAGNIFICATION_COMPONENT_NAME, 637 getIntent()); 638 } else { 639 showDialog(DialogEnums.MAGNIFICATION_EDIT_SHORTCUT); 640 } 641 } 642 643 @Override updateShortcutPreferenceData()644 protected void updateShortcutPreferenceData() { 645 final int shortcutTypes = getUserShortcutTypeFromSettings(getPrefContext()); 646 if (shortcutTypes != UserShortcutType.EMPTY) { 647 final PreferredShortcut shortcut = new PreferredShortcut( 648 MAGNIFICATION_CONTROLLER_NAME, shortcutTypes); 649 PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); 650 } 651 } 652 653 @Override initShortcutPreference()654 protected void initShortcutPreference() { 655 mShortcutPreference = new ShortcutPreference(getPrefContext(), null); 656 mShortcutPreference.setPersistent(false); 657 mShortcutPreference.setKey(getShortcutPreferenceKey()); 658 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 659 mShortcutPreference.setOnClickCallback(this); 660 mShortcutPreference.setTitle(getShortcutTitle()); 661 662 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 663 generalCategory.addPreference(mShortcutPreference); 664 } 665 666 @Override getShortcutTitle()667 protected CharSequence getShortcutTitle() { 668 return getText(R.string.accessibility_screen_magnification_shortcut_title); 669 } 670 671 @Override updateShortcutPreference()672 protected void updateShortcutPreference() { 673 final int shortcutTypes = getUserPreferredShortcutTypes(); 674 mShortcutPreference.setChecked( 675 hasMagnificationValuesInSettings(getPrefContext(), shortcutTypes)); 676 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 677 } 678 679 @VisibleForTesting saveNonEmptyUserShortcutType(int type)680 void saveNonEmptyUserShortcutType(int type) { 681 if (type == UserShortcutType.EMPTY) { 682 return; 683 } 684 685 final PreferredShortcut shortcut = new PreferredShortcut( 686 MAGNIFICATION_CONTROLLER_NAME, type); 687 PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); 688 } 689 690 @VisibleForTesting optInAllMagnificationValuesToSettings(Context context, int shortcutTypes)691 static void optInAllMagnificationValuesToSettings(Context context, int shortcutTypes) { 692 if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) { 693 optInMagnificationValueToSettings(context, UserShortcutType.SOFTWARE); 694 } 695 if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) { 696 optInMagnificationValueToSettings(context, UserShortcutType.HARDWARE); 697 } 698 if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) { 699 optInMagnificationValueToSettings(context, UserShortcutType.TRIPLETAP); 700 } 701 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 702 if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP) 703 == UserShortcutType.TWOFINGER_DOUBLETAP)) { 704 optInMagnificationValueToSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP); 705 } 706 } 707 if (android.view.accessibility.Flags.a11yQsShortcut()) { 708 if (((shortcutTypes & UserShortcutType.QUICK_SETTINGS) 709 == UserShortcutType.QUICK_SETTINGS)) { 710 optInMagnificationValueToSettings(context, UserShortcutType.QUICK_SETTINGS); 711 } 712 } 713 } 714 optInMagnificationValueToSettings( Context context, @UserShortcutType int shortcutType)715 private static void optInMagnificationValueToSettings( 716 Context context, @UserShortcutType int shortcutType) { 717 if (android.view.accessibility.Flags.a11yQsShortcut()) { 718 AccessibilityManager a11yManager = context.getSystemService(AccessibilityManager.class); 719 if (a11yManager != null) { 720 a11yManager.enableShortcutsForTargets( 721 /* enable= */ true, 722 shortcutType, 723 Set.of(MAGNIFICATION_CONTROLLER_NAME), 724 UserHandle.myUserId() 725 ); 726 } 727 return; 728 } 729 730 if (shortcutType == UserShortcutType.TRIPLETAP) { 731 Settings.Secure.putInt(context.getContentResolver(), 732 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, ON); 733 return; 734 } 735 736 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 737 if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) { 738 Settings.Secure.putInt( 739 context.getContentResolver(), 740 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, 741 ON); 742 return; 743 } 744 } 745 746 if (hasMagnificationValueInSettings(context, shortcutType)) { 747 return; 748 } 749 750 final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType); 751 final String targetString = Settings.Secure.getString(context.getContentResolver(), 752 targetKey); 753 final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR)); 754 755 if (!TextUtils.isEmpty(targetString)) { 756 joiner.add(targetString); 757 } 758 joiner.add(MAGNIFICATION_CONTROLLER_NAME); 759 760 Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString()); 761 // The size setting defaults to unknown. If the user has ever manually changed the size 762 // before, we do not automatically change it. 763 if (shortcutType == UserShortcutType.SOFTWARE 764 && Settings.Secure.getInt(context.getContentResolver(), 765 Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, 766 FloatingMenuSizePreferenceController.Size.UNKNOWN) 767 == FloatingMenuSizePreferenceController.Size.UNKNOWN) { 768 Settings.Secure.putInt(context.getContentResolver(), 769 Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, 770 FloatingMenuSizePreferenceController.Size.LARGE); 771 } 772 } 773 774 @VisibleForTesting optOutAllMagnificationValuesFromSettings(Context context, int shortcutTypes)775 static void optOutAllMagnificationValuesFromSettings(Context context, 776 int shortcutTypes) { 777 if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) { 778 optOutMagnificationValueFromSettings(context, UserShortcutType.SOFTWARE); 779 } 780 if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) { 781 optOutMagnificationValueFromSettings(context, UserShortcutType.HARDWARE); 782 } 783 if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) { 784 optOutMagnificationValueFromSettings(context, UserShortcutType.TRIPLETAP); 785 } 786 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 787 if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP) 788 == UserShortcutType.TWOFINGER_DOUBLETAP)) { 789 optOutMagnificationValueFromSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP); 790 } 791 } 792 if (android.view.accessibility.Flags.a11yQsShortcut()) { 793 if (((shortcutTypes & UserShortcutType.QUICK_SETTINGS) 794 == UserShortcutType.QUICK_SETTINGS)) { 795 optOutMagnificationValueFromSettings(context, UserShortcutType.QUICK_SETTINGS); 796 } 797 } 798 } 799 optOutMagnificationValueFromSettings(Context context, @UserShortcutType int shortcutType)800 private static void optOutMagnificationValueFromSettings(Context context, 801 @UserShortcutType int shortcutType) { 802 if (android.view.accessibility.Flags.a11yQsShortcut()) { 803 AccessibilityManager a11yManager = context.getSystemService(AccessibilityManager.class); 804 if (a11yManager != null) { 805 a11yManager.enableShortcutsForTargets( 806 /* enable= */ false, 807 shortcutType, 808 Set.of(MAGNIFICATION_CONTROLLER_NAME), 809 UserHandle.myUserId() 810 ); 811 } 812 return; 813 } 814 815 if (shortcutType == UserShortcutType.TRIPLETAP) { 816 Settings.Secure.putInt(context.getContentResolver(), 817 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF); 818 return; 819 } 820 821 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 822 if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) { 823 Settings.Secure.putInt( 824 context.getContentResolver(), 825 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, 826 OFF); 827 return; 828 } 829 } 830 831 final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType); 832 final String targetString = Settings.Secure.getString(context.getContentResolver(), 833 targetKey); 834 835 if (TextUtils.isEmpty(targetString)) { 836 return; 837 } 838 839 final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR)); 840 841 sStringColonSplitter.setString(targetString); 842 while (sStringColonSplitter.hasNext()) { 843 final String name = sStringColonSplitter.next(); 844 if (TextUtils.isEmpty(name) || MAGNIFICATION_CONTROLLER_NAME.equals(name)) { 845 continue; 846 } 847 joiner.add(name); 848 } 849 850 Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString()); 851 } 852 853 @VisibleForTesting hasMagnificationValuesInSettings(Context context, int shortcutTypes)854 static boolean hasMagnificationValuesInSettings(Context context, int shortcutTypes) { 855 boolean exist = false; 856 857 if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) { 858 exist = hasMagnificationValueInSettings(context, UserShortcutType.SOFTWARE); 859 } 860 if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) { 861 exist |= hasMagnificationValueInSettings(context, UserShortcutType.HARDWARE); 862 } 863 if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) { 864 exist |= hasMagnificationValueInSettings(context, UserShortcutType.TRIPLETAP); 865 } 866 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 867 if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP) 868 == UserShortcutType.TWOFINGER_DOUBLETAP)) { 869 exist |= hasMagnificationValueInSettings(context, 870 UserShortcutType.TWOFINGER_DOUBLETAP); 871 } 872 } 873 return exist; 874 } 875 hasMagnificationValueInSettings(Context context, @UserShortcutType int shortcutType)876 private static boolean hasMagnificationValueInSettings(Context context, 877 @UserShortcutType int shortcutType) { 878 if (shortcutType == UserShortcutType.TRIPLETAP) { 879 return Settings.Secure.getInt(context.getContentResolver(), 880 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON; 881 } 882 883 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 884 if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) { 885 return Settings.Secure.getInt(context.getContentResolver(), 886 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, 887 OFF) == ON; 888 } 889 } 890 891 final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType); 892 final String targetString = Settings.Secure.getString(context.getContentResolver(), 893 targetKey); 894 895 if (TextUtils.isEmpty(targetString)) { 896 return false; 897 } 898 899 sStringColonSplitter.setString(targetString); 900 while (sStringColonSplitter.hasNext()) { 901 final String name = sStringColonSplitter.next(); 902 if (MAGNIFICATION_CONTROLLER_NAME.equals(name)) { 903 return true; 904 } 905 } 906 return false; 907 } 908 getUserShortcutTypeFromSettings(Context context)909 private static int getUserShortcutTypeFromSettings(Context context) { 910 int shortcutTypes = UserShortcutType.EMPTY; 911 if (hasMagnificationValuesInSettings(context, UserShortcutType.SOFTWARE)) { 912 shortcutTypes |= UserShortcutType.SOFTWARE; 913 } 914 if (hasMagnificationValuesInSettings(context, UserShortcutType.HARDWARE)) { 915 shortcutTypes |= UserShortcutType.HARDWARE; 916 } 917 if (hasMagnificationValuesInSettings(context, UserShortcutType.TRIPLETAP)) { 918 shortcutTypes |= UserShortcutType.TRIPLETAP; 919 } 920 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 921 if (hasMagnificationValuesInSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP)) { 922 shortcutTypes |= UserShortcutType.TWOFINGER_DOUBLETAP; 923 } 924 } 925 return shortcutTypes; 926 } 927 928 /** 929 * Gets the service summary of magnification. 930 * 931 * @param context The current context. 932 */ getServiceSummary(Context context)933 public static CharSequence getServiceSummary(Context context) { 934 // Get the user shortcut type from settings provider. 935 final int userShortcutType = getUserShortcutTypeFromSettings(context); 936 final CharSequence featureState = 937 (userShortcutType != AccessibilityUtil.UserShortcutType.EMPTY) 938 ? context.getText(R.string.accessibility_summary_shortcut_enabled) 939 : context.getText(R.string.generic_accessibility_feature_shortcut_off); 940 final CharSequence featureSummary = context.getText(R.string.magnification_feature_summary); 941 return context.getString(R.string.preference_summary_default_combination, 942 featureState, featureSummary); 943 } 944 945 @Override getUserPreferredShortcutTypes()946 protected int getUserPreferredShortcutTypes() { 947 return PreferredShortcuts.retrieveUserShortcutType( 948 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME); 949 } 950 } 951