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.settings.accessibility.AccessibilityDialogUtils.DialogEnums; 20 21 import android.app.Dialog; 22 import android.app.settings.SettingsEnums; 23 import android.content.ComponentName; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.pm.ResolveInfo; 29 import android.graphics.drawable.Drawable; 30 import android.icu.text.CaseMap; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.text.Html; 37 import android.text.TextUtils; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.view.ViewGroup; 41 import android.view.accessibility.AccessibilityManager; 42 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; 43 import android.widget.CheckBox; 44 import android.widget.ImageView; 45 import android.widget.Switch; 46 47 import androidx.annotation.VisibleForTesting; 48 import androidx.preference.Preference; 49 import androidx.preference.PreferenceCategory; 50 import androidx.preference.PreferenceScreen; 51 52 import com.android.settings.R; 53 import com.android.settings.SettingsActivity; 54 import com.android.settings.SettingsPreferenceFragment; 55 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType; 56 import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType; 57 import com.android.settings.utils.LocaleUtils; 58 import com.android.settings.widget.SettingsMainSwitchBar; 59 import com.android.settings.widget.SettingsMainSwitchPreference; 60 import com.android.settingslib.accessibility.AccessibilityUtils; 61 import com.android.settingslib.widget.IllustrationPreference; 62 import com.android.settingslib.widget.OnMainSwitchChangeListener; 63 64 import com.google.android.setupcompat.util.WizardManagerHelper; 65 66 import java.util.ArrayList; 67 import java.util.List; 68 import java.util.Locale; 69 70 /** 71 * Base class for accessibility fragments with toggle, shortcut, some helper functions 72 * and dialog management. 73 */ 74 public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment 75 implements ShortcutPreference.OnClickCallback, OnMainSwitchChangeListener { 76 77 protected SettingsMainSwitchPreference mToggleServiceSwitchPreference; 78 protected ShortcutPreference mShortcutPreference; 79 protected Preference mSettingsPreference; 80 protected AccessibilityFooterPreferenceController mFooterPreferenceController; 81 protected String mPreferenceKey; 82 protected Dialog mDialog; 83 84 protected CharSequence mSettingsTitle; 85 protected Intent mSettingsIntent; 86 // The mComponentName maybe null, such as Magnify 87 protected ComponentName mComponentName; 88 protected CharSequence mPackageName; 89 protected Uri mImageUri; 90 private CharSequence mDescription; 91 protected CharSequence mHtmlDescription; 92 93 private static final String DRAWABLE_FOLDER = "drawable"; 94 protected static final String KEY_USE_SERVICE_PREFERENCE = "use_service"; 95 public static final String KEY_GENERAL_CATEGORY = "general_categories"; 96 protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description"; 97 private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; 98 protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type"; 99 protected static final String KEY_ANIMATED_IMAGE = "animated_image"; 100 101 private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; 102 private SettingsContentObserver mSettingsContentObserver; 103 104 private CheckBox mSoftwareTypeCheckBox; 105 private CheckBox mHardwareTypeCheckBox; 106 107 public static final int NOT_SET = -1; 108 // Save user's shortcutType value when savedInstance has value (e.g. device rotated). 109 protected int mSavedCheckBoxValue = NOT_SET; 110 private boolean mSavedAccessibilityFloatingMenuEnabled; 111 112 // For html description of accessibility service, must follow the rule, such as 113 // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully. 114 private static final String IMG_PREFIX = "R.drawable."; 115 116 private ImageView mImageGetterCacheView; 117 118 private final Html.ImageGetter mImageGetter = (String str) -> { 119 if (str != null && str.startsWith(IMG_PREFIX)) { 120 final String fileName = str.substring(IMG_PREFIX.length()); 121 return getDrawableFromUri(Uri.parse( 122 ContentResolver.SCHEME_ANDROID_RESOURCE + "://" 123 + mComponentName.getPackageName() + "/" + DRAWABLE_FOLDER + "/" 124 + fileName)); 125 } 126 return null; 127 }; 128 129 @Override onCreate(Bundle savedInstanceState)130 public void onCreate(Bundle savedInstanceState) { 131 super.onCreate(savedInstanceState); 132 // Restore the user shortcut type. 133 if (savedInstanceState != null && savedInstanceState.containsKey( 134 KEY_SAVED_USER_SHORTCUT_TYPE)) { 135 mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE, NOT_SET); 136 } 137 138 setupDefaultShortcutIfNecessary(getPrefContext()); 139 final int resId = getPreferenceScreenResId(); 140 if (resId <= 0) { 141 final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( 142 getPrefContext()); 143 setPreferenceScreen(preferenceScreen); 144 } 145 146 final List<String> shortcutFeatureKeys = getFeatureSettingsKeys(); 147 mSettingsContentObserver = new SettingsContentObserver(new Handler(), shortcutFeatureKeys) { 148 @Override 149 public void onChange(boolean selfChange, Uri uri) { 150 updateShortcutPreferenceData(); 151 updateShortcutPreference(); 152 } 153 }; 154 } 155 getFeatureSettingsKeys()156 protected List<String> getFeatureSettingsKeys() { 157 final List<String> shortcutFeatureKeys = new ArrayList<>(); 158 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); 159 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); 160 return shortcutFeatureKeys; 161 } 162 163 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)164 public View onCreateView(LayoutInflater inflater, ViewGroup container, 165 Bundle savedInstanceState) { 166 // Need to be called as early as possible. Protected variables will be assigned here. 167 onProcessArguments(getArguments()); 168 169 initAnimatedImagePreference(); 170 initToggleServiceSwitchPreference(); 171 initGeneralCategory(); 172 initShortcutPreference(); 173 initSettingsPreference(); 174 initHtmlTextPreference(); 175 initFooterPreference(); 176 177 installActionBarToggleSwitch(); 178 179 updateToggleServiceTitle(mToggleServiceSwitchPreference); 180 181 mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { 182 removeDialog(DialogEnums.EDIT_SHORTCUT); 183 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 184 }; 185 return super.onCreateView(inflater, container, savedInstanceState); 186 } 187 188 @Override onViewCreated(View view, Bundle savedInstanceState)189 public void onViewCreated(View view, Bundle savedInstanceState) { 190 super.onViewCreated(view, savedInstanceState); 191 192 final SettingsActivity activity = (SettingsActivity) getActivity(); 193 final SettingsMainSwitchBar switchBar = activity.getSwitchBar(); 194 switchBar.hide(); 195 196 updatePreferenceOrder(); 197 } 198 199 @Override onResume()200 public void onResume() { 201 super.onResume(); 202 203 final AccessibilityManager am = getPrefContext().getSystemService( 204 AccessibilityManager.class); 205 am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 206 mSettingsContentObserver.register(getContentResolver()); 207 updateShortcutPreferenceData(); 208 updateShortcutPreference(); 209 210 updateEditShortcutDialogIfNeeded(); 211 } 212 213 @Override onPause()214 public void onPause() { 215 final AccessibilityManager am = getPrefContext().getSystemService( 216 AccessibilityManager.class); 217 am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 218 mSettingsContentObserver.unregister(getContentResolver()); 219 mSavedAccessibilityFloatingMenuEnabled = AccessibilityUtil.isFloatingMenuEnabled( 220 getContext()); 221 super.onPause(); 222 } 223 224 @Override onSaveInstanceState(Bundle outState)225 public void onSaveInstanceState(Bundle outState) { 226 final int value = getShortcutTypeCheckBoxValue(); 227 if (value != NOT_SET) { 228 outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value); 229 } 230 super.onSaveInstanceState(outState); 231 } 232 233 @Override onCreateDialog(int dialogId)234 public Dialog onCreateDialog(int dialogId) { 235 switch (dialogId) { 236 case DialogEnums.EDIT_SHORTCUT: 237 final CharSequence dialogTitle = getPrefContext().getString( 238 R.string.accessibility_shortcut_title, mPackageName); 239 final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent()) 240 ? DialogType.EDIT_SHORTCUT_GENERIC_SUW : DialogType.EDIT_SHORTCUT_GENERIC; 241 mDialog = AccessibilityDialogUtils.showEditShortcutDialog( 242 getPrefContext(), dialogType, dialogTitle, 243 this::callOnAlertDialogCheckboxClicked); 244 setupEditShortcutDialog(mDialog); 245 return mDialog; 246 case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: 247 mDialog = AccessibilityGestureNavigationTutorial 248 .createAccessibilityTutorialDialog(getPrefContext(), 249 getUserShortcutTypes()); 250 mDialog.setCanceledOnTouchOutside(false); 251 return mDialog; 252 default: 253 throw new IllegalArgumentException("Unsupported dialogId " + dialogId); 254 } 255 } 256 257 @Override getDialogMetricsCategory(int dialogId)258 public int getDialogMetricsCategory(int dialogId) { 259 switch (dialogId) { 260 case DialogEnums.EDIT_SHORTCUT: 261 return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT; 262 case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: 263 return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL; 264 default: 265 return SettingsEnums.ACTION_UNKNOWN; 266 } 267 } 268 269 @Override getMetricsCategory()270 public int getMetricsCategory() { 271 return SettingsEnums.ACCESSIBILITY_SERVICE; 272 } 273 274 @Override getHelpResource()275 public int getHelpResource() { 276 return 0; 277 } 278 279 @Override onDestroyView()280 public void onDestroyView() { 281 super.onDestroyView(); 282 removeActionBarToggleSwitch(); 283 } 284 285 @Override onSwitchChanged(Switch switchView, boolean isChecked)286 public void onSwitchChanged(Switch switchView, boolean isChecked) { 287 onPreferenceToggled(mPreferenceKey, isChecked); 288 } 289 290 /** 291 * Returns the shortcut type list which has been checked by user. 292 */ getUserShortcutTypes()293 abstract int getUserShortcutTypes(); 294 updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference)295 protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) { 296 final CharSequence title = 297 getString(R.string.accessibility_service_primary_switch_title, mPackageName); 298 switchPreference.setTitle(title); 299 } 300 updateShortcutTitle(ShortcutPreference shortcutPreference)301 protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { 302 final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName); 303 shortcutPreference.setTitle(title); 304 } 305 onPreferenceToggled(String preferenceKey, boolean enabled)306 protected abstract void onPreferenceToggled(String preferenceKey, boolean enabled); 307 onInstallSwitchPreferenceToggleSwitch()308 protected void onInstallSwitchPreferenceToggleSwitch() { 309 // Implement this to set a checked listener. 310 updateSwitchBarToggleSwitch(); 311 mToggleServiceSwitchPreference.addOnSwitchChangeListener(this); 312 } 313 onRemoveSwitchPreferenceToggleSwitch()314 protected void onRemoveSwitchPreferenceToggleSwitch() { 315 // Implement this to reset a checked listener. 316 } 317 updateSwitchBarToggleSwitch()318 protected void updateSwitchBarToggleSwitch() { 319 // Implement this to update the state of switch. 320 } 321 installActionBarToggleSwitch()322 private void installActionBarToggleSwitch() { 323 onInstallSwitchPreferenceToggleSwitch(); 324 } 325 removeActionBarToggleSwitch()326 private void removeActionBarToggleSwitch() { 327 mToggleServiceSwitchPreference.setOnPreferenceClickListener(null); 328 onRemoveSwitchPreferenceToggleSwitch(); 329 } 330 setTitle(String title)331 public void setTitle(String title) { 332 getActivity().setTitle(title); 333 } 334 onProcessArguments(Bundle arguments)335 protected void onProcessArguments(Bundle arguments) { 336 // Key. 337 mPreferenceKey = arguments.getString(AccessibilitySettings.EXTRA_PREFERENCE_KEY); 338 339 // Title. 340 if (arguments.containsKey(AccessibilitySettings.EXTRA_RESOLVE_INFO)) { 341 ResolveInfo info = arguments.getParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO); 342 getActivity().setTitle(info.loadLabel(getPackageManager()).toString()); 343 } else if (arguments.containsKey(AccessibilitySettings.EXTRA_TITLE)) { 344 setTitle(arguments.getString(AccessibilitySettings.EXTRA_TITLE)); 345 } 346 347 // Summary. 348 if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) { 349 mDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_SUMMARY); 350 } 351 352 // Settings html description. 353 if (arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) { 354 mHtmlDescription = arguments.getCharSequence( 355 AccessibilitySettings.EXTRA_HTML_DESCRIPTION); 356 } 357 } 358 359 /** Customizes the order by preference key. */ getPreferenceOrderList()360 protected List<String> getPreferenceOrderList() { 361 final List<String> lists = new ArrayList<>(); 362 lists.add(KEY_ANIMATED_IMAGE); 363 lists.add(KEY_USE_SERVICE_PREFERENCE); 364 lists.add(KEY_GENERAL_CATEGORY); 365 lists.add(KEY_HTML_DESCRIPTION_PREFERENCE); 366 return lists; 367 } 368 updatePreferenceOrder()369 private void updatePreferenceOrder() { 370 final List<String> lists = getPreferenceOrderList(); 371 372 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 373 preferenceScreen.setOrderingAsAdded(false); 374 375 final int size = lists.size(); 376 for (int i = 0; i < size; i++) { 377 final Preference preference = preferenceScreen.findPreference(lists.get(i)); 378 if (preference != null) { 379 preference.setOrder(i); 380 } 381 } 382 } 383 getDrawableFromUri(Uri imageUri)384 private Drawable getDrawableFromUri(Uri imageUri) { 385 if (mImageGetterCacheView == null) { 386 mImageGetterCacheView = new ImageView(getPrefContext()); 387 } 388 389 mImageGetterCacheView.setAdjustViewBounds(true); 390 mImageGetterCacheView.setImageURI(imageUri); 391 392 if (mImageGetterCacheView.getDrawable() == null) { 393 return null; 394 } 395 396 final Drawable drawable = 397 mImageGetterCacheView.getDrawable().mutate().getConstantState().newDrawable(); 398 mImageGetterCacheView.setImageURI(null); 399 final int imageWidth = drawable.getIntrinsicWidth(); 400 final int imageHeight = drawable.getIntrinsicHeight(); 401 final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2; 402 if ((imageWidth > AccessibilityUtil.getScreenWidthPixels(getPrefContext())) 403 || (imageHeight > screenHalfHeight)) { 404 return null; 405 } 406 407 drawable.setBounds(/* left= */0, /* top= */0, drawable.getIntrinsicWidth(), 408 drawable.getIntrinsicHeight()); 409 410 return drawable; 411 } 412 initAnimatedImagePreference()413 private void initAnimatedImagePreference() { 414 if (mImageUri == null) { 415 return; 416 } 417 418 final int displayHalfHeight = 419 AccessibilityUtil.getDisplayBounds(getPrefContext()).height() / 2; 420 final IllustrationPreference illustrationPreference = 421 new IllustrationPreference(getPrefContext()); 422 illustrationPreference.setImageUri(mImageUri); 423 illustrationPreference.setSelectable(false); 424 illustrationPreference.setMaxHeight(displayHalfHeight); 425 illustrationPreference.setKey(KEY_ANIMATED_IMAGE); 426 427 getPreferenceScreen().addPreference(illustrationPreference); 428 } 429 initToggleServiceSwitchPreference()430 private void initToggleServiceSwitchPreference() { 431 mToggleServiceSwitchPreference = new SettingsMainSwitchPreference(getPrefContext()); 432 mToggleServiceSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE); 433 if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) { 434 final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED); 435 mToggleServiceSwitchPreference.setChecked(enabled); 436 } 437 438 getPreferenceScreen().addPreference(mToggleServiceSwitchPreference); 439 } 440 initGeneralCategory()441 private void initGeneralCategory() { 442 final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext()); 443 generalCategory.setKey(KEY_GENERAL_CATEGORY); 444 generalCategory.setTitle(R.string.accessibility_screen_option); 445 446 getPreferenceScreen().addPreference(generalCategory); 447 } 448 initShortcutPreference()449 protected void initShortcutPreference() { 450 // Initial the shortcut preference. 451 mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null); 452 mShortcutPreference.setPersistent(false); 453 mShortcutPreference.setKey(getShortcutPreferenceKey()); 454 mShortcutPreference.setOnClickCallback(this); 455 456 updateShortcutTitle(mShortcutPreference); 457 458 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 459 generalCategory.addPreference(mShortcutPreference); 460 } 461 initSettingsPreference()462 protected void initSettingsPreference() { 463 if (mSettingsTitle == null || mSettingsIntent == null) { 464 return; 465 } 466 467 // Show the "Settings" menu as if it were a preference screen. 468 mSettingsPreference = new Preference(getPrefContext()); 469 mSettingsPreference.setTitle(mSettingsTitle); 470 mSettingsPreference.setIconSpaceReserved(false); 471 mSettingsPreference.setIntent(mSettingsIntent); 472 473 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 474 generalCategory.addPreference(mSettingsPreference); 475 } 476 initHtmlTextPreference()477 private void initHtmlTextPreference() { 478 if (TextUtils.isEmpty(mHtmlDescription)) { 479 return; 480 } 481 final PreferenceScreen screen = getPreferenceScreen(); 482 final CharSequence htmlDescription = Html.fromHtml(mHtmlDescription.toString(), 483 Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null); 484 485 final AccessibilityFooterPreference htmlFooterPreference = 486 new AccessibilityFooterPreference(screen.getContext()); 487 htmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE); 488 htmlFooterPreference.setSummary(htmlDescription); 489 screen.addPreference(htmlFooterPreference); 490 491 // TODO(b/171272809): Migrate to DashboardFragment. 492 final String title = getString(R.string.accessibility_introduction_title, mPackageName); 493 mFooterPreferenceController = new AccessibilityFooterPreferenceController( 494 screen.getContext(), htmlFooterPreference.getKey()); 495 mFooterPreferenceController.setIntroductionTitle(title); 496 mFooterPreferenceController.displayPreference(screen); 497 } 498 initFooterPreference()499 private void initFooterPreference() { 500 if (!TextUtils.isEmpty(mDescription)) { 501 createFooterPreference(getPreferenceScreen(), mDescription, 502 getString(R.string.accessibility_introduction_title, mPackageName)); 503 } 504 505 if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) { 506 final CharSequence defaultDescription = 507 getText(R.string.accessibility_service_default_description); 508 createFooterPreference(getPreferenceScreen(), defaultDescription, 509 getString(R.string.accessibility_introduction_title, mPackageName)); 510 } 511 } 512 513 514 /** 515 * Creates {@link AccessibilityFooterPreference} and append into {@link PreferenceScreen} 516 * 517 * @param screen The preference screen to add the footer preference 518 * @param summary The summary of the preference summary. 519 * @param introductionTitle The title of introduction in the footer. 520 */ 521 @VisibleForTesting createFooterPreference(PreferenceScreen screen, CharSequence summary, String introductionTitle)522 void createFooterPreference(PreferenceScreen screen, CharSequence summary, 523 String introductionTitle) { 524 final AccessibilityFooterPreference footerPreference = 525 new AccessibilityFooterPreference(screen.getContext()); 526 footerPreference.setSummary(summary); 527 screen.addPreference(footerPreference); 528 529 mFooterPreferenceController = new AccessibilityFooterPreferenceController( 530 screen.getContext(), footerPreference.getKey()); 531 mFooterPreferenceController.setIntroductionTitle(introductionTitle); 532 mFooterPreferenceController.displayPreference(screen); 533 } 534 535 @VisibleForTesting setupEditShortcutDialog(Dialog dialog)536 void setupEditShortcutDialog(Dialog dialog) { 537 final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut); 538 mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox); 539 setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox); 540 541 final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut); 542 mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox); 543 setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox); 544 545 updateEditShortcutDialogCheckBox(); 546 } 547 setDialogTextAreaClickListener(View dialogView, CheckBox checkBox)548 private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) { 549 final View dialogTextArea = dialogView.findViewById(R.id.container); 550 dialogTextArea.setOnClickListener(v -> checkBox.toggle()); 551 } 552 updateEditShortcutDialogCheckBox()553 private void updateEditShortcutDialogCheckBox() { 554 // If it is during onConfigChanged process then restore the value, or get the saved value 555 // when shortcutPreference is checked. 556 int value = restoreOnConfigChangedValue(); 557 if (value == NOT_SET) { 558 final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType( 559 getPrefContext(), mComponentName.flattenToString(), UserShortcutType.SOFTWARE); 560 value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType 561 : UserShortcutType.EMPTY; 562 } 563 564 mSoftwareTypeCheckBox.setChecked( 565 hasShortcutType(value, UserShortcutType.SOFTWARE)); 566 mHardwareTypeCheckBox.setChecked( 567 hasShortcutType(value, UserShortcutType.HARDWARE)); 568 } 569 restoreOnConfigChangedValue()570 private int restoreOnConfigChangedValue() { 571 final int savedValue = mSavedCheckBoxValue; 572 mSavedCheckBoxValue = NOT_SET; 573 return savedValue; 574 } 575 hasShortcutType(int value, @UserShortcutType int type)576 private boolean hasShortcutType(int value, @UserShortcutType int type) { 577 return (value & type) == type; 578 } 579 580 /** 581 * Returns accumulated {@link UserShortcutType} checkbox value or {@code NOT_SET} if checkboxes 582 * did not exist. 583 */ getShortcutTypeCheckBoxValue()584 protected int getShortcutTypeCheckBoxValue() { 585 if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) { 586 return NOT_SET; 587 } 588 589 int value = UserShortcutType.EMPTY; 590 if (mSoftwareTypeCheckBox.isChecked()) { 591 value |= UserShortcutType.SOFTWARE; 592 } 593 if (mHardwareTypeCheckBox.isChecked()) { 594 value |= UserShortcutType.HARDWARE; 595 } 596 return value; 597 } 598 getSoftwareShortcutTypeSummary(Context context)599 private static CharSequence getSoftwareShortcutTypeSummary(Context context) { 600 int resId; 601 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 602 resId = R.string.accessibility_shortcut_edit_summary_software; 603 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 604 resId = R.string.accessibility_shortcut_edit_summary_software_gesture; 605 } else { 606 resId = R.string.accessibility_shortcut_edit_summary_software; 607 } 608 return context.getText(resId); 609 } 610 getShortcutTypeSummary(Context context)611 protected CharSequence getShortcutTypeSummary(Context context) { 612 if (!mShortcutPreference.isSettingsEditable()) { 613 return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware); 614 } 615 616 if (!mShortcutPreference.isChecked()) { 617 return context.getText(R.string.switch_off_text); 618 } 619 620 final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context, 621 mComponentName.flattenToString(), UserShortcutType.SOFTWARE); 622 623 final List<CharSequence> list = new ArrayList<>(); 624 if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) { 625 list.add(getSoftwareShortcutTypeSummary(context)); 626 } 627 if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) { 628 final CharSequence hardwareTitle = context.getText( 629 R.string.accessibility_shortcut_hardware_keyword); 630 list.add(hardwareTitle); 631 } 632 633 // Show software shortcut if first time to use. 634 if (list.isEmpty()) { 635 list.add(getSoftwareShortcutTypeSummary(context)); 636 } 637 638 return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */ 639 null, LocaleUtils.getConcatenatedString(list)); 640 } 641 642 /** 643 * This method will be invoked when a button in the edit shortcut dialog is clicked. 644 * 645 * @param dialog The dialog that received the click 646 * @param which The button that was clicked 647 */ callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which)648 protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) { 649 if (mComponentName == null) { 650 return; 651 } 652 653 final int value = getShortcutTypeCheckBoxValue(); 654 655 saveNonEmptyUserShortcutType(value); 656 AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, mComponentName); 657 AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, mComponentName); 658 mShortcutPreference.setChecked(value != UserShortcutType.EMPTY); 659 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 660 } 661 updateShortcutPreferenceData()662 protected void updateShortcutPreferenceData() { 663 if (mComponentName == null) { 664 return; 665 } 666 667 final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings( 668 getPrefContext(), mComponentName); 669 if (shortcutTypes != UserShortcutType.EMPTY) { 670 final PreferredShortcut shortcut = new PreferredShortcut( 671 mComponentName.flattenToString(), shortcutTypes); 672 PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); 673 } 674 } 675 updateShortcutPreference()676 protected void updateShortcutPreference() { 677 if (mComponentName == null) { 678 return; 679 } 680 681 final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(), 682 mComponentName.flattenToString(), UserShortcutType.SOFTWARE); 683 mShortcutPreference.setChecked( 684 AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes, 685 mComponentName)); 686 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 687 } 688 getShortcutPreferenceKey()689 protected String getShortcutPreferenceKey() { 690 return KEY_SHORTCUT_PREFERENCE; 691 } 692 693 @Override onToggleClicked(ShortcutPreference preference)694 public void onToggleClicked(ShortcutPreference preference) { 695 if (mComponentName == null) { 696 return; 697 } 698 699 final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(), 700 mComponentName.flattenToString(), UserShortcutType.SOFTWARE); 701 if (preference.isChecked()) { 702 AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes, 703 mComponentName); 704 showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); 705 } else { 706 AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes, 707 mComponentName); 708 } 709 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 710 } 711 712 @Override onSettingsClicked(ShortcutPreference preference)713 public void onSettingsClicked(ShortcutPreference preference) { 714 showDialog(DialogEnums.EDIT_SHORTCUT); 715 } 716 717 /** 718 * Setups a configurable default if the setting has never been set. 719 */ setupDefaultShortcutIfNecessary(Context context)720 private static void setupDefaultShortcutIfNecessary(Context context) { 721 final String targetKey = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; 722 String targetString = Settings.Secure.getString(context.getContentResolver(), targetKey); 723 if (!TextUtils.isEmpty(targetString)) { 724 // The shortcut setting has been set 725 return; 726 } 727 728 // AccessibilityManager#getAccessibilityShortcutTargets may not return correct shortcut 729 // targets during boot. Needs to read settings directly here. 730 targetString = AccessibilityUtils.getShortcutTargetServiceComponentNameString(context, 731 UserHandle.myUserId()); 732 if (TextUtils.isEmpty(targetString)) { 733 // No configurable default accessibility service 734 return; 735 } 736 737 // Only fallback to default accessibility service when setting is never updated. 738 final ComponentName shortcutName = ComponentName.unflattenFromString(targetString); 739 if (shortcutName != null) { 740 Settings.Secure.putString(context.getContentResolver(), targetKey, 741 shortcutName.flattenToString()); 742 } 743 } 744 updateEditShortcutDialogIfNeeded()745 private void updateEditShortcutDialogIfNeeded() { 746 if (mDialog == null || !mDialog.isShowing()) { 747 return; 748 } 749 750 // Content in software shortcut need to be adjusted depend on the accessibility button 751 // mode status which can be changed in background. 752 final boolean valueChanged = mSavedAccessibilityFloatingMenuEnabled 753 != AccessibilityUtil.isFloatingMenuEnabled(getContext()); 754 if (valueChanged) { 755 AccessibilityDialogUtils.updateSoftwareShortcutInDialog(getContext(), mDialog); 756 } 757 } 758 759 @VisibleForTesting saveNonEmptyUserShortcutType(int type)760 void saveNonEmptyUserShortcutType(int type) { 761 if (type == UserShortcutType.EMPTY) { 762 return; 763 } 764 765 final PreferredShortcut shortcut = new PreferredShortcut( 766 mComponentName.flattenToString(), type); 767 PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); 768 } 769 } 770