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.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT; 22 import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; 23 import static com.android.settings.accessibility.AccessibilityUtil.State.ON; 24 import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList; 25 26 import android.app.Dialog; 27 import android.app.settings.SettingsEnums; 28 import android.content.ComponentName; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.pm.PackageManager; 32 import android.icu.text.MessageFormat; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.provider.DeviceConfig; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.view.InputDevice; 39 import android.view.LayoutInflater; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.view.accessibility.AccessibilityManager; 43 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; 44 45 import androidx.annotation.NonNull; 46 import androidx.annotation.Nullable; 47 import androidx.annotation.StringRes; 48 import androidx.core.util.Preconditions; 49 import androidx.preference.Preference; 50 import androidx.preference.PreferenceCategory; 51 import androidx.preference.SwitchPreferenceCompat; 52 53 import com.android.internal.accessibility.util.ShortcutUtils; 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.server.accessibility.Flags; 56 import com.android.settings.DialogCreatable; 57 import com.android.settings.R; 58 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; 59 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment; 60 import com.android.settings.search.BaseSearchIndexProvider; 61 import com.android.settingslib.core.AbstractPreferenceController; 62 import com.android.settingslib.search.Indexable; 63 import com.android.settingslib.search.SearchIndexable; 64 import com.android.settingslib.search.SearchIndexableRaw; 65 import com.android.settingslib.widget.IllustrationPreference; 66 import com.android.settingslib.widget.SettingsThemeHelper; 67 68 import com.google.android.setupcompat.util.WizardManagerHelper; 69 import com.google.android.setupdesign.util.ThemeHelper; 70 71 import java.util.ArrayList; 72 import java.util.List; 73 import java.util.Set; 74 import java.util.stream.Stream; 75 76 /** 77 * Fragment that shows the actual UI for providing basic magnification accessibility service setup 78 * and does not have toggle bar to turn on service to use. 79 */ 80 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) 81 public class ToggleScreenMagnificationPreferenceFragment extends 82 ToggleFeaturePreferenceFragment implements DialogHelper { 83 84 private static final String TAG = 85 ToggleScreenMagnificationPreferenceFragment.class.getSimpleName(); 86 @VisibleForTesting 87 static final String KEY_MAGNIFICATION_SHORTCUT_PREFERENCE = "magnification_shortcut_preference"; 88 private static final char COMPONENT_NAME_SEPARATOR = ':'; 89 private static final TextUtils.SimpleStringSplitter sStringColonSplitter = 90 new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); 91 92 // TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util. 93 private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; 94 @Nullable 95 private DialogCreatable mMagnificationModeDialogDelegate; 96 @Nullable 97 private DialogCreatable mMagnificationCursorFollowingModeDialogDelegate; 98 99 @Nullable 100 MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController; 101 102 private boolean mInSetupWizard; 103 104 @VisibleForTesting setMagnificationModeDialogDelegate(@onNull DialogCreatable delegate)105 public void setMagnificationModeDialogDelegate(@NonNull DialogCreatable delegate) { 106 mMagnificationModeDialogDelegate = delegate; 107 } 108 109 @VisibleForTesting setMagnificationCursorFollowingModeDialogDelegate( @onNull DialogCreatable delegate)110 public void setMagnificationCursorFollowingModeDialogDelegate( 111 @NonNull DialogCreatable delegate) { 112 mMagnificationCursorFollowingModeDialogDelegate = delegate; 113 } 114 115 @Override onCreate(@ullable Bundle savedInstanceState)116 public void onCreate(@Nullable Bundle savedInstanceState) { 117 super.onCreate(savedInstanceState); 118 getActivity().setTitle(R.string.accessibility_screen_magnification_title); 119 mInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 120 } 121 122 @NonNull 123 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @NonNull Bundle savedInstanceState)124 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 125 @NonNull Bundle savedInstanceState) { 126 mFeatureName = getString(R.string.accessibility_screen_magnification_title); 127 final boolean useExpressiveTheme = WizardManagerHelper.isAnySetupWizard(getIntent()) 128 ? ThemeHelper.shouldApplyGlifExpressiveStyle(getPrefContext()) 129 : SettingsThemeHelper.isExpressiveTheme(getPrefContext()); 130 mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) 131 .authority(getPrefContext().getPackageName()) 132 .appendPath(String.valueOf(useExpressiveTheme 133 ? R.raw.accessibility_magnification_banner_expressive 134 : R.raw.a11y_magnification_banner)) 135 .build(); 136 mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { 137 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 138 }; 139 140 final View view = super.onCreateView(inflater, container, savedInstanceState); 141 updateFooterPreference(); 142 return view; 143 } 144 updateFooterPreference()145 private void updateFooterPreference() { 146 final String title = getPrefContext().getString( 147 R.string.accessibility_screen_magnification_about_title); 148 final String learnMoreText = getPrefContext().getString( 149 R.string.accessibility_screen_magnification_footer_learn_more_content_description); 150 mFooterPreferenceController.setIntroductionTitle(title); 151 mFooterPreferenceController.setupHelpLink(getHelpResource(), learnMoreText); 152 mFooterPreferenceController.displayPreference(getPreferenceScreen()); 153 } 154 155 @Override onResume()156 public void onResume() { 157 super.onResume(); 158 final IllustrationPreference illustrationPreference = 159 getPreferenceScreen().findPreference(KEY_ANIMATED_IMAGE); 160 if (illustrationPreference != null) { 161 illustrationPreference.applyDynamicColor(); 162 } 163 164 final AccessibilityManager am = getPrefContext().getSystemService( 165 AccessibilityManager.class); 166 am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 167 } 168 169 @Override getPreferenceScreenResId()170 protected int getPreferenceScreenResId() { 171 // TODO(b/171272809): Add back when controllers move to static type 172 return 0; 173 } 174 175 @Override getLogTag()176 protected String getLogTag() { 177 return TAG; 178 } 179 180 @Override onPause()181 public void onPause() { 182 final AccessibilityManager am = getPrefContext().getSystemService( 183 AccessibilityManager.class); 184 am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 185 186 super.onPause(); 187 } 188 189 @NonNull 190 @Override onCreateDialog(int dialogId)191 public Dialog onCreateDialog(int dialogId) { 192 switch (dialogId) { 193 case DialogEnums.DIALOG_MAGNIFICATION_MODE: 194 case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING: 195 return Preconditions.checkNotNull(mMagnificationModeDialogDelegate) 196 .onCreateDialog(dialogId); 197 case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE: 198 return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate) 199 .onCreateDialog(dialogId); 200 case DialogEnums.GESTURE_NAVIGATION_TUTORIAL: 201 return AccessibilityShortcutsTutorial 202 .showAccessibilityGestureTutorialDialog(getPrefContext()); 203 default: 204 return super.onCreateDialog(dialogId); 205 } 206 } 207 isWindowMagnificationSupported(Context context)208 private static boolean isWindowMagnificationSupported(Context context) { 209 return context.getResources().getBoolean( 210 com.android.internal.R.bool.config_magnification_area) 211 && context.getPackageManager().hasSystemFeature( 212 PackageManager.FEATURE_WINDOW_MAGNIFICATION); 213 } 214 isMagnificationCursorFollowingModeDialogSupported()215 private static boolean isMagnificationCursorFollowingModeDialogSupported() { 216 return com.android.settings.accessibility.Flags.enableMagnificationCursorFollowingDialog() 217 && hasMouse(); 218 } 219 220 @Override initSettingsPreference()221 protected void initSettingsPreference() { 222 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 223 if (isWindowMagnificationSupported(getContext())) { 224 // LINT.IfChange(preference_list) 225 addMagnificationModeSetting(generalCategory); 226 addFollowTypingSetting(generalCategory); 227 addOneFingerPanningSetting(generalCategory); 228 addAlwaysOnSetting(generalCategory); 229 addJoystickSetting(generalCategory); 230 // LINT.ThenChange(:search_data) 231 } 232 addCursorFollowingSetting(generalCategory); 233 addFeedbackSetting(generalCategory); 234 } 235 236 @Override onProcessArguments(Bundle arguments)237 protected void onProcessArguments(Bundle arguments) { 238 Context context = getContext(); 239 240 if (!arguments.containsKey(AccessibilitySettings.EXTRA_PREFERENCE_KEY)) { 241 arguments.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, 242 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); 243 } 244 245 if (!arguments.containsKey(AccessibilitySettings.EXTRA_INTRO)) { 246 arguments.putCharSequence(AccessibilitySettings.EXTRA_INTRO, 247 context.getString(R.string.accessibility_screen_magnification_intro_text)); 248 } 249 250 if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION) 251 && !Flags.enableMagnificationOneFingerPanningGesture()) { 252 String summary = ""; 253 boolean hasTouchscreen = hasTouchscreen(); 254 if (Flags.enableMagnificationKeyboardControl() && hasHardKeyboard()) { 255 // Include the keyboard summary when a keyboard is plugged in. 256 final String meta = context.getString(R.string.modifier_keys_meta); 257 final String alt = context.getString(R.string.modifier_keys_alt); 258 summary += MessageFormat.format( 259 context.getString( 260 R.string.accessibility_screen_magnification_keyboard_summary, 261 meta, alt, meta, alt), 262 new Object[]{1, 2, 3, 4}); 263 if (hasTouchscreen) { 264 // Add a newline before the touchscreen text. 265 summary += "<br/><br/>"; 266 } 267 268 } 269 if (hasTouchscreen || TextUtils.isEmpty(summary)) { 270 // Always show the touchscreen summary if there is no summary yet, even if the 271 // touchscreen is missing. 272 // If the keyboard summary is present and there is no touchscreen, then we can 273 // ignore the touchscreen summary. 274 summary += MessageFormat.format( 275 context.getString(R.string.accessibility_screen_magnification_summary), 276 new Object[]{1, 2, 3, 4, 5}); 277 } 278 arguments.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary); 279 } 280 281 super.onProcessArguments(arguments); 282 } 283 createMagnificationModePreference(Context context)284 private static Preference createMagnificationModePreference(Context context) { 285 final Preference pref = new Preference(context); 286 pref.setTitle(R.string.accessibility_magnification_mode_title); 287 pref.setKey(MagnificationModePreferenceController.PREF_KEY); 288 pref.setPersistent(false); 289 return pref; 290 } 291 addMagnificationModeSetting(PreferenceCategory generalCategory)292 private void addMagnificationModeSetting(PreferenceCategory generalCategory) { 293 mSettingsPreference = createMagnificationModePreference(getPrefContext()); 294 generalCategory.addPreference(mSettingsPreference); 295 296 final MagnificationModePreferenceController magnificationModePreferenceController = 297 new MagnificationModePreferenceController(Preconditions.checkNotNull(getContext()), 298 MagnificationModePreferenceController.PREF_KEY); 299 magnificationModePreferenceController.setDialogHelper(/* dialogHelper= */this); 300 mMagnificationModeDialogDelegate = magnificationModePreferenceController; 301 getSettingsLifecycle().addObserver(magnificationModePreferenceController); 302 magnificationModePreferenceController.displayPreference(getPreferenceScreen()); 303 addPreferenceController(magnificationModePreferenceController); 304 } 305 createCursorFollowingPreference(Context context)306 private static Preference createCursorFollowingPreference(Context context) { 307 final Preference pref = new Preference(context); 308 pref.setTitle(R.string.accessibility_magnification_cursor_following_title); 309 pref.setKey(MagnificationCursorFollowingModePreferenceController.PREF_KEY); 310 pref.setPersistent(false); 311 return pref; 312 } 313 addCursorFollowingSetting(PreferenceCategory generalCategory)314 private void addCursorFollowingSetting(PreferenceCategory generalCategory) { 315 if (!isMagnificationCursorFollowingModeDialogSupported()) { 316 return; 317 } 318 319 generalCategory.addPreference(createCursorFollowingPreference(getPrefContext())); 320 321 final MagnificationCursorFollowingModePreferenceController controller = 322 new MagnificationCursorFollowingModePreferenceController( 323 getContext(), 324 MagnificationCursorFollowingModePreferenceController.PREF_KEY); 325 controller.setDialogHelper(/* dialogHelper= */this); 326 mMagnificationCursorFollowingModeDialogDelegate = controller; 327 controller.displayPreference(getPreferenceScreen()); 328 addPreferenceController(controller); 329 } 330 createFollowTypingPreference(Context context)331 private static Preference createFollowTypingPreference(Context context) { 332 final Preference pref = new SwitchPreferenceCompat(context); 333 pref.setTitle(R.string.accessibility_screen_magnification_follow_typing_title); 334 pref.setSummary(R.string.accessibility_screen_magnification_follow_typing_summary); 335 pref.setKey(MagnificationFollowTypingPreferenceController.PREF_KEY); 336 return pref; 337 } 338 addFollowTypingSetting(PreferenceCategory generalCategory)339 private void addFollowTypingSetting(PreferenceCategory generalCategory) { 340 generalCategory.addPreference(createFollowTypingPreference(getPrefContext())); 341 342 var followTypingPreferenceController = new MagnificationFollowTypingPreferenceController( 343 getContext(), MagnificationFollowTypingPreferenceController.PREF_KEY); 344 followTypingPreferenceController.setInSetupWizard(mInSetupWizard); 345 followTypingPreferenceController.displayPreference(getPreferenceScreen()); 346 addPreferenceController(followTypingPreferenceController); 347 } 348 isAlwaysOnSupported(Context context)349 private static boolean isAlwaysOnSupported(Context context) { 350 final boolean defaultValue = context.getResources().getBoolean( 351 com.android.internal.R.bool.config_magnification_always_on_enabled); 352 353 return DeviceConfig.getBoolean( 354 DeviceConfig.NAMESPACE_WINDOW_MANAGER, 355 "AlwaysOnMagnifier__enable_always_on_magnifier", 356 defaultValue 357 ); 358 } 359 createAlwaysOnPreference(Context context)360 private static Preference createAlwaysOnPreference(Context context) { 361 final Preference pref = new SwitchPreferenceCompat(context); 362 pref.setTitle(R.string.accessibility_screen_magnification_always_on_title); 363 pref.setSummary(R.string.accessibility_screen_magnification_always_on_summary); 364 pref.setKey(MagnificationAlwaysOnPreferenceController.PREF_KEY); 365 return pref; 366 } 367 addAlwaysOnSetting(PreferenceCategory generalCategory)368 private void addAlwaysOnSetting(PreferenceCategory generalCategory) { 369 if (!isAlwaysOnSupported(getContext())) { 370 return; 371 } 372 373 final Preference pref = createAlwaysOnPreference(getPrefContext()); 374 generalCategory.addPreference(pref); 375 376 var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController( 377 getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY); 378 alwaysOnPreferenceController.setInSetupWizard(mInSetupWizard); 379 getSettingsLifecycle().addObserver(alwaysOnPreferenceController); 380 alwaysOnPreferenceController.displayPreference(getPreferenceScreen()); 381 addPreferenceController(alwaysOnPreferenceController); 382 } 383 createOneFingerPanningPreference(Context context)384 private static Preference createOneFingerPanningPreference(Context context) { 385 final Preference pref = new SwitchPreferenceCompat(context); 386 pref.setTitle(R.string.accessibility_magnification_one_finger_panning_title); 387 pref.setKey(MagnificationOneFingerPanningPreferenceController.PREF_KEY); 388 return pref; 389 } 390 isOneFingerPanningSupported()391 private static boolean isOneFingerPanningSupported() { 392 return Flags.enableMagnificationOneFingerPanningGesture(); 393 } 394 addOneFingerPanningSetting(PreferenceCategory generalCategory)395 private void addOneFingerPanningSetting(PreferenceCategory generalCategory) { 396 if (!isOneFingerPanningSupported()) { 397 return; 398 } 399 400 final Preference pref = createOneFingerPanningPreference(getPrefContext()); 401 generalCategory.addPreference(pref); 402 403 mOneFingerPanningPreferenceController = 404 new MagnificationOneFingerPanningPreferenceController(getContext()); 405 mOneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard); 406 getSettingsLifecycle().addObserver(mOneFingerPanningPreferenceController); 407 mOneFingerPanningPreferenceController.displayPreference(getPreferenceScreen()); 408 addPreferenceController(mOneFingerPanningPreferenceController); 409 } 410 createJoystickPreference(Context context)411 private static Preference createJoystickPreference(Context context) { 412 final Preference pref = new SwitchPreferenceCompat(context); 413 pref.setTitle(R.string.accessibility_screen_magnification_joystick_title); 414 pref.setSummary(R.string.accessibility_screen_magnification_joystick_summary); 415 pref.setKey(MagnificationJoystickPreferenceController.PREF_KEY); 416 return pref; 417 } 418 createFeedbackPreference(Context context)419 private static Preference createFeedbackPreference(Context context) { 420 final Preference pref = new Preference(context); 421 pref.setTitle(R.string.accessibility_feedback_title); 422 pref.setSummary(R.string.accessibility_feedback_summary); 423 pref.setKey(MagnificationFeedbackPreferenceController.PREF_KEY); 424 return pref; 425 } 426 isJoystickSupported()427 private static boolean isJoystickSupported() { 428 return DeviceConfig.getBoolean( 429 DeviceConfig.NAMESPACE_WINDOW_MANAGER, 430 "MagnificationJoystick__enable_magnification_joystick", 431 false); 432 } 433 addJoystickSetting(PreferenceCategory generalCategory)434 private void addJoystickSetting(PreferenceCategory generalCategory) { 435 if (!isJoystickSupported()) { 436 return; 437 } 438 439 final Preference pref = createJoystickPreference(getPrefContext()); 440 generalCategory.addPreference(pref); 441 442 MagnificationJoystickPreferenceController joystickPreferenceController = 443 new MagnificationJoystickPreferenceController( 444 getContext(), 445 MagnificationJoystickPreferenceController.PREF_KEY 446 ); 447 joystickPreferenceController.setInSetupWizard(mInSetupWizard); 448 joystickPreferenceController.displayPreference(getPreferenceScreen()); 449 addPreferenceController(joystickPreferenceController); 450 } 451 addFeedbackSetting(PreferenceCategory generalCategory)452 private void addFeedbackSetting(PreferenceCategory generalCategory) { 453 if (!Flags.enableLowVisionHats()) { 454 return; 455 } 456 457 final Preference feedbackPreference = createFeedbackPreference(getPrefContext()); 458 generalCategory.addPreference(feedbackPreference); 459 460 final MagnificationFeedbackPreferenceController magnificationFeedbackPreferenceController = 461 new MagnificationFeedbackPreferenceController(getContext(), this, 462 MagnificationFeedbackPreferenceController.PREF_KEY); 463 magnificationFeedbackPreferenceController.setInSetupWizard(mInSetupWizard); 464 magnificationFeedbackPreferenceController.displayPreference(getPreferenceScreen()); 465 addPreferenceController(magnificationFeedbackPreferenceController); 466 } 467 468 @Override showDialog(int dialogId)469 public void showDialog(int dialogId) { 470 super.showDialog(dialogId); 471 } 472 473 @Override registerKeysToObserverCallback( AccessibilitySettingsContentObserver contentObserver)474 protected void registerKeysToObserverCallback( 475 AccessibilitySettingsContentObserver contentObserver) { 476 super.registerKeysToObserverCallback(contentObserver); 477 478 var keysToObserve = List.of( 479 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, 480 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, 481 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED 482 ); 483 contentObserver.registerKeysToObserverCallback(keysToObserve, 484 key -> updatePreferencesState()); 485 486 if (Flags.enableMagnificationOneFingerPanningGesture()) { 487 contentObserver.registerKeysToObserverCallback( 488 List.of(Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED), 489 key -> updateHtmlTextPreference()); 490 } 491 } 492 updatePreferencesState()493 private void updatePreferencesState() { 494 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 495 getPreferenceControllers().forEach(controllers::addAll); 496 controllers.forEach(controller -> controller.updateState( 497 findPreference(controller.getPreferenceKey()))); 498 } 499 500 @Override getCurrentHtmlDescription()501 CharSequence getCurrentHtmlDescription() { 502 CharSequence origin = super.getCurrentHtmlDescription(); 503 if (!TextUtils.isEmpty(origin)) { 504 // If in ToggleFeaturePreferenceFragment we already have a fixed html description, we 505 // should use the fixed one, otherwise we'll dynamically decide the description. 506 return origin; 507 } 508 509 Context context = getContext(); 510 if (mOneFingerPanningPreferenceController != null && context != null) { 511 @StringRes int resId = mOneFingerPanningPreferenceController.isChecked() 512 ? R.string.accessibility_screen_magnification_summary_one_finger_panning_on 513 : R.string.accessibility_screen_magnification_summary_one_finger_panning_off; 514 return MessageFormat.format(context.getString(resId), new Object[]{1, 2, 3, 4, 5}); 515 } 516 return ""; 517 } 518 519 @Override getShortcutFeatureSettingsKeys()520 protected List<String> getShortcutFeatureSettingsKeys() { 521 final List<String> shortcutKeys = super.getShortcutFeatureSettingsKeys(); 522 shortcutKeys.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); 523 return shortcutKeys; 524 } 525 526 @Override getShortcutTypeSummary(Context context)527 protected CharSequence getShortcutTypeSummary(Context context) { 528 if (!mShortcutPreference.isChecked()) { 529 return context.getText(R.string.switch_off_text); 530 } 531 532 return getShortcutSummaryList(context, 533 PreferredShortcuts.retrieveUserShortcutType(context, 534 MAGNIFICATION_CONTROLLER_NAME)); 535 } 536 537 @Override getHelpResource()538 public int getHelpResource() { 539 return R.string.help_url_magnification; 540 } 541 542 @Override getMetricsCategory()543 public int getMetricsCategory() { 544 // TODO: Distinguish between magnification modes 545 return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION; 546 } 547 548 @Override getDialogMetricsCategory(int dialogId)549 public int getDialogMetricsCategory(int dialogId) { 550 switch (dialogId) { 551 case DialogEnums.DIALOG_MAGNIFICATION_MODE: 552 case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING: 553 return Preconditions.checkNotNull(mMagnificationModeDialogDelegate) 554 .getDialogMetricsCategory(dialogId); 555 case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE: 556 return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate) 557 .getDialogMetricsCategory(dialogId); 558 case DialogEnums.GESTURE_NAVIGATION_TUTORIAL: 559 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION; 560 case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL: 561 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON; 562 default: 563 return super.getDialogMetricsCategory(dialogId); 564 } 565 } 566 567 @Override getUserShortcutTypes()568 int getUserShortcutTypes() { 569 return ShortcutUtils.getEnabledShortcutTypes( 570 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME); 571 } 572 573 @Override getTileComponentName()574 ComponentName getTileComponentName() { 575 return null; 576 } 577 578 @Override onPreferenceToggled(String preferenceKey, boolean enabled)579 protected void onPreferenceToggled(String preferenceKey, boolean enabled) { 580 if (enabled && TextUtils.equals( 581 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 582 preferenceKey)) { 583 showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); 584 } 585 Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? ON : OFF); 586 } 587 588 @Override onInstallSwitchPreferenceToggleSwitch()589 protected void onInstallSwitchPreferenceToggleSwitch() { 590 mToggleServiceSwitchPreference.setVisible(false); 591 } 592 593 @Override onToggleClicked(@onNull ShortcutPreference preference)594 public void onToggleClicked(@NonNull ShortcutPreference preference) { 595 final int shortcutTypes = getUserPreferredShortcutTypes(); 596 getPrefContext().getSystemService(AccessibilityManager.class).enableShortcutsForTargets( 597 preference.isChecked(), shortcutTypes, 598 Set.of(MAGNIFICATION_CONTROLLER_NAME), getPrefContext().getUserId() 599 ); 600 if (preference.isChecked()) { 601 showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); 602 } 603 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 604 } 605 606 @Override onSettingsClicked(@onNull ShortcutPreference preference)607 public void onSettingsClicked(@NonNull ShortcutPreference preference) { 608 EditShortcutsPreferenceFragment.showEditShortcutScreen( 609 requireContext(), 610 getMetricsCategory(), 611 getShortcutTitle(), 612 MAGNIFICATION_COMPONENT_NAME, 613 getIntent()); 614 } 615 616 @Override updateShortcutPreferenceData()617 protected void updateShortcutPreferenceData() { 618 final int shortcutTypes = ShortcutUtils.getEnabledShortcutTypes( 619 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME); 620 if (shortcutTypes != DEFAULT) { 621 final PreferredShortcut shortcut = new PreferredShortcut( 622 MAGNIFICATION_CONTROLLER_NAME, shortcutTypes); 623 PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); 624 } 625 } 626 627 @Override initShortcutPreference()628 protected void initShortcutPreference() { 629 mShortcutPreference = new ShortcutPreference(getPrefContext(), null); 630 mShortcutPreference.setPersistent(false); 631 mShortcutPreference.setKey(getShortcutPreferenceKey()); 632 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 633 mShortcutPreference.setOnClickCallback(this); 634 mShortcutPreference.setTitle(getShortcutTitle()); 635 636 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 637 generalCategory.addPreference(mShortcutPreference); 638 } 639 640 @Override getShortcutPreferenceKey()641 protected String getShortcutPreferenceKey() { 642 return KEY_MAGNIFICATION_SHORTCUT_PREFERENCE; 643 } 644 645 @Override getShortcutTitle()646 protected CharSequence getShortcutTitle() { 647 return getText(R.string.accessibility_screen_magnification_shortcut_title); 648 } 649 650 @Override updateShortcutPreference()651 protected void updateShortcutPreference() { 652 mShortcutPreference.setChecked(getUserShortcutTypes() != DEFAULT); 653 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 654 } 655 656 /** 657 * Gets the service summary of magnification. 658 * 659 * @param context The current context. 660 */ 661 @NonNull getServiceSummary(@onNull Context context)662 public static CharSequence getServiceSummary(@NonNull Context context) { 663 // Get the user shortcut type from settings provider. 664 final int userShortcutType = ShortcutUtils.getEnabledShortcutTypes( 665 context, MAGNIFICATION_CONTROLLER_NAME); 666 final CharSequence featureState = 667 (userShortcutType != DEFAULT) 668 ? context.getText(R.string.accessibility_summary_shortcut_enabled) 669 : context.getText(R.string.generic_accessibility_feature_shortcut_off); 670 final CharSequence featureSummary = context.getText(R.string.magnification_feature_summary); 671 return context.getString( 672 com.android.settingslib.R.string.preference_summary_default_combination, 673 featureState, featureSummary); 674 } 675 676 @Override getUserPreferredShortcutTypes()677 protected int getUserPreferredShortcutTypes() { 678 return PreferredShortcuts.retrieveUserShortcutType( 679 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME); 680 } 681 682 /** 683 * Returns if a mouse is attached. 684 */ hasMouse()685 private static boolean hasMouse() { 686 final int[] devices = InputDevice.getDeviceIds(); 687 for (int i = 0; i < devices.length; i++) { 688 InputDevice device = InputDevice.getDevice(devices[i]); 689 if (device != null && (device.getSources() & InputDevice.SOURCE_MOUSE) 690 == InputDevice.SOURCE_MOUSE) { 691 return true; 692 } 693 } 694 return false; 695 } 696 hasHardKeyboard()697 private boolean hasHardKeyboard() { 698 final int[] devices = InputDevice.getDeviceIds(); 699 for (int i = 0; i < devices.length; i++) { 700 InputDevice device = InputDevice.getDevice(devices[i]); 701 if (device == null || device.isVirtual() || !device.isFullKeyboard()) { 702 continue; 703 } 704 705 return true; 706 } 707 return false; 708 } 709 hasTouchscreen()710 private boolean hasTouchscreen() { 711 return getPackageManager() 712 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) 713 || getPackageManager().hasSystemFeature(PackageManager.FEATURE_FAKETOUCH); 714 } 715 716 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 717 new BaseSearchIndexProvider() { 718 // LINT.IfChange(search_data) 719 @NonNull 720 @Override 721 public List<SearchIndexableRaw> getRawDataToIndex(@NonNull Context context, 722 boolean enabled) { 723 final List<SearchIndexableRaw> rawData = 724 super.getRawDataToIndex(context, enabled); 725 726 if (!com.android.settings.accessibility.Flags.fixA11ySettingsSearch()) { 727 return rawData; 728 } 729 730 // Add all preferences to search raw data so that they are included in 731 // indexing, which happens infrequently. Irrelevant preferences should be 732 // hidden from the live returned search results by `getNonIndexableKeys`, 733 // which is called every time a search occurs. This allows for dynamic search 734 // entries that hide or show depending on current device state. 735 rawData.add(createShortcutPreferenceSearchData(context)); 736 Stream.of( 737 createMagnificationModePreference(context), 738 createFollowTypingPreference(context), 739 createOneFingerPanningPreference(context), 740 createAlwaysOnPreference(context), 741 createJoystickPreference(context), 742 createCursorFollowingPreference(context), 743 createFeedbackPreference(context) 744 ) 745 .forEach(pref -> 746 rawData.add(createPreferenceSearchData(context, pref))); 747 return rawData; 748 } 749 750 @NonNull 751 @Override 752 public List<String> getNonIndexableKeys(@NonNull Context context) { 753 final List<String> niks = super.getNonIndexableKeys(context); 754 755 if (!com.android.settings.accessibility.Flags.fixA11ySettingsSearch()) { 756 return niks; 757 } 758 759 if (!isWindowMagnificationSupported(context)) { 760 niks.add(MagnificationModePreferenceController.PREF_KEY); 761 niks.add(MagnificationFollowTypingPreferenceController.PREF_KEY); 762 niks.add(MagnificationOneFingerPanningPreferenceController.PREF_KEY); 763 niks.add(MagnificationAlwaysOnPreferenceController.PREF_KEY); 764 niks.add(MagnificationJoystickPreferenceController.PREF_KEY); 765 } else { 766 if (!isAlwaysOnSupported(context) 767 // This preference's title "Keep on while switching apps" does not 768 // mention magnification so it may confuse users who search a term 769 // like "Keep on". 770 // So we hide it if the user has no magnification shortcut enabled. 771 || ShortcutUtils.getEnabledShortcutTypes( 772 context, MAGNIFICATION_CONTROLLER_NAME) == DEFAULT) { 773 niks.add(MagnificationAlwaysOnPreferenceController.PREF_KEY); 774 } 775 if (!isOneFingerPanningSupported()) { 776 niks.add(MagnificationOneFingerPanningPreferenceController.PREF_KEY); 777 } 778 if (!isJoystickSupported()) { 779 niks.add(MagnificationJoystickPreferenceController.PREF_KEY); 780 } 781 } 782 783 if (!isMagnificationCursorFollowingModeDialogSupported()) { 784 niks.add(MagnificationCursorFollowingModePreferenceController.PREF_KEY); 785 } 786 787 if (!Flags.enableLowVisionHats()) { 788 niks.add(MagnificationFeedbackPreferenceController.PREF_KEY); 789 } 790 791 return niks; 792 } 793 // LINT.ThenChange(:preference_list) 794 795 private SearchIndexableRaw createPreferenceSearchData( 796 Context context, Preference pref) { 797 final SearchIndexableRaw raw = new SearchIndexableRaw(context); 798 raw.key = pref.getKey(); 799 raw.title = pref.getTitle().toString(); 800 return raw; 801 } 802 803 private SearchIndexableRaw createShortcutPreferenceSearchData(Context context) { 804 final SearchIndexableRaw raw = new SearchIndexableRaw(context); 805 raw.key = KEY_MAGNIFICATION_SHORTCUT_PREFERENCE; 806 raw.title = context.getString( 807 R.string.accessibility_screen_magnification_shortcut_title); 808 return raw; 809 } 810 }; 811 } 812