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.common.ShortcutConstants.UserShortcutType.DEFAULT; 20 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; 21 import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; 22 import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList; 23 24 import android.app.Dialog; 25 import android.app.settings.SettingsEnums; 26 import android.content.ComponentName; 27 import android.content.ContentResolver; 28 import android.content.Context; 29 import android.content.DialogInterface; 30 import android.content.Intent; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.content.pm.ServiceInfo; 34 import android.graphics.drawable.Drawable; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.provider.Settings; 39 import android.service.quicksettings.TileService; 40 import android.text.Html; 41 import android.text.TextUtils; 42 import android.view.LayoutInflater; 43 import android.view.View; 44 import android.view.ViewGroup; 45 import android.view.accessibility.AccessibilityManager; 46 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; 47 import android.widget.CompoundButton; 48 import android.widget.CompoundButton.OnCheckedChangeListener; 49 import android.widget.ImageView; 50 51 import androidx.annotation.NonNull; 52 import androidx.annotation.Nullable; 53 import androidx.annotation.VisibleForTesting; 54 import androidx.preference.Preference; 55 import androidx.preference.PreferenceCategory; 56 import androidx.preference.PreferenceScreen; 57 import androidx.preference.PreferenceViewHolder; 58 import androidx.recyclerview.widget.RecyclerView; 59 60 import com.android.internal.accessibility.common.ShortcutConstants; 61 import com.android.internal.accessibility.util.ShortcutUtils; 62 import com.android.settings.R; 63 import com.android.settings.SettingsActivity; 64 import com.android.settings.accessibility.actionbar.FeedbackMenuController; 65 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment; 66 import com.android.settings.dashboard.DashboardFragment; 67 import com.android.settings.flags.Flags; 68 import com.android.settings.widget.SettingsMainSwitchBar; 69 import com.android.settings.widget.SettingsMainSwitchPreference; 70 import com.android.settingslib.utils.ThreadUtils; 71 import com.android.settingslib.widget.IllustrationPreference; 72 import com.android.settingslib.widget.TopIntroPreference; 73 74 import com.google.android.setupcompat.util.WizardManagerHelper; 75 import com.google.android.setupdesign.util.ThemeHelper; 76 77 import java.util.ArrayList; 78 import java.util.List; 79 import java.util.Set; 80 81 /** 82 * Base class for accessibility fragments with toggle, shortcut, some helper functions 83 * and dialog management. 84 */ 85 public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment 86 implements ShortcutPreference.OnClickCallback, OnCheckedChangeListener { 87 88 public static final String KEY_GENERAL_CATEGORY = "general_categories"; 89 public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; 90 protected static final String KEY_TOP_INTRO_PREFERENCE = "top_intro"; 91 protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description"; 92 protected static final String KEY_ANIMATED_IMAGE = "animated_image"; 93 // For html description of accessibility service, must follow the rule, such as 94 // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully. 95 private static final String IMG_PREFIX = "R.drawable."; 96 private static final String DRAWABLE_FOLDER = "drawable"; 97 98 protected TopIntroPreference mTopIntroPreference; 99 protected SettingsMainSwitchPreference mToggleServiceSwitchPreference; 100 protected ShortcutPreference mShortcutPreference; 101 protected Preference mSettingsPreference; 102 @Nullable protected AccessibilityFooterPreference mHtmlFooterPreference; 103 protected AccessibilityFooterPreferenceController mFooterPreferenceController; 104 protected String mPreferenceKey; 105 protected Dialog mDialog; 106 protected CharSequence mSettingsTitle; 107 protected Intent mSettingsIntent; 108 // The mComponentName maybe null, such as Magnify 109 protected ComponentName mComponentName; 110 protected CharSequence mFeatureName; 111 protected Uri mImageUri; 112 protected CharSequence mHtmlDescription; 113 protected CharSequence mTopIntroTitle; 114 private CharSequence mDescription; 115 private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; 116 private AccessibilitySettingsContentObserver mSettingsContentObserver; 117 private ImageView mImageGetterCacheView; 118 protected 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 133 onProcessArguments(getArguments()); 134 final int resId = getPreferenceScreenResId(); 135 if (resId <= 0) { 136 final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( 137 getPrefContext()); 138 setPreferenceScreen(preferenceScreen); 139 } 140 141 mSettingsContentObserver = new AccessibilitySettingsContentObserver(new Handler()); 142 registerKeysToObserverCallback(mSettingsContentObserver); 143 144 FeedbackMenuController.init(this, getFeedbackCategory()); 145 } 146 registerKeysToObserverCallback( AccessibilitySettingsContentObserver contentObserver)147 protected void registerKeysToObserverCallback( 148 AccessibilitySettingsContentObserver contentObserver) { 149 final List<String> shortcutFeatureKeys = getShortcutFeatureSettingsKeys(); 150 151 contentObserver.registerKeysToObserverCallback(shortcutFeatureKeys, key -> { 152 updateShortcutPreferenceData(); 153 updateShortcutPreference(); 154 }); 155 } 156 getShortcutFeatureSettingsKeys()157 protected List<String> getShortcutFeatureSettingsKeys() { 158 final List<String> shortcutFeatureKeys = new ArrayList<>(); 159 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); 160 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); 161 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS); 162 return shortcutFeatureKeys; 163 } 164 165 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)166 public View onCreateView(LayoutInflater inflater, ViewGroup container, 167 Bundle savedInstanceState) { 168 initTopIntroPreference(); 169 initAnimatedImagePreference(); 170 initToggleServiceSwitchPreference(); 171 initGeneralCategory(); 172 initShortcutPreference(); 173 initSettingsPreference(); 174 initAppInfoPreference(); 175 initHtmlTextPreference(); 176 initFooterPreference(); 177 178 installActionBarToggleSwitch(); 179 180 updateToggleServiceTitle(mToggleServiceSwitchPreference); 181 182 mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { 183 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 184 }; 185 186 updatePreferenceOrder(); 187 return super.onCreateView(inflater, container, savedInstanceState); 188 } 189 190 @Override onCreateDialog(int dialogId)191 public Dialog onCreateDialog(int dialogId) { 192 switch (dialogId) { 193 case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: 194 if (isAnySetupWizard()) { 195 mDialog = AccessibilityShortcutsTutorial 196 .createAccessibilityTutorialDialogForSetupWizard( 197 getPrefContext(), getUserPreferredShortcutTypes(), 198 this::callOnTutorialDialogButtonClicked, mFeatureName); 199 } else { 200 mDialog = AccessibilityShortcutsTutorial 201 .createAccessibilityTutorialDialog( 202 getPrefContext(), getUserPreferredShortcutTypes(), 203 this::callOnTutorialDialogButtonClicked, mFeatureName); 204 } 205 mDialog.setCanceledOnTouchOutside(false); 206 return mDialog; 207 default: 208 throw new IllegalArgumentException("Unsupported dialogId " + dialogId); 209 } 210 } 211 212 @Override onViewCreated(View view, Bundle savedInstanceState)213 public void onViewCreated(View view, Bundle savedInstanceState) { 214 super.onViewCreated(view, savedInstanceState); 215 216 final SettingsActivity settingsActivity = (SettingsActivity) getActivity(); 217 final SettingsMainSwitchBar switchBar = settingsActivity.getSwitchBar(); 218 switchBar.hide(); 219 220 writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(getContext()); 221 } 222 223 @Override onResume()224 public void onResume() { 225 super.onResume(); 226 227 final AccessibilityManager am = getPrefContext().getSystemService( 228 AccessibilityManager.class); 229 am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 230 mSettingsContentObserver.register(getContentResolver()); 231 updateShortcutPreferenceData(); 232 updateShortcutPreference(); 233 } 234 235 @Override onPause()236 public void onPause() { 237 final AccessibilityManager am = getPrefContext().getSystemService( 238 AccessibilityManager.class); 239 am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 240 mSettingsContentObserver.unregister(getContentResolver()); 241 super.onPause(); 242 } 243 244 @Override onDestroyView()245 public void onDestroyView() { 246 super.onDestroyView(); 247 removeActionBarToggleSwitch(); 248 } 249 250 @Override getDialogMetricsCategory(int dialogId)251 public int getDialogMetricsCategory(int dialogId) { 252 switch (dialogId) { 253 case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: 254 return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL; 255 default: 256 return SettingsEnums.ACTION_UNKNOWN; 257 } 258 } 259 260 @Override getMetricsCategory()261 public int getMetricsCategory() { 262 return SettingsEnums.ACCESSIBILITY_SERVICE; 263 } 264 265 /** 266 * Returns the category of the feedback page. 267 * 268 * <p>By default, this method returns {@link SettingsEnums#PAGE_UNKNOWN}. This indicates that 269 * the feedback category is unknown, and the absence of a feedback menu. 270 * 271 * @return The feedback category, which is {@link SettingsEnums#PAGE_UNKNOWN} by default. 272 */ getFeedbackCategory()273 protected int getFeedbackCategory() { 274 return SettingsEnums.PAGE_UNKNOWN; 275 } 276 277 @Override getHelpResource()278 public int getHelpResource() { 279 return 0; 280 } 281 282 @Override onCheckedChanged(CompoundButton buttonView, boolean isChecked)283 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 284 onPreferenceToggled(mPreferenceKey, isChecked); 285 } 286 287 /** 288 * Returns the shortcut type list which has been checked by user. 289 */ getUserShortcutTypes()290 abstract int getUserShortcutTypes(); 291 292 /** Returns the accessibility tile component name. */ getTileComponentName()293 abstract ComponentName getTileComponentName(); 294 updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference)295 protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) { 296 final CharSequence title = 297 getString(R.string.accessibility_service_primary_switch_title, mFeatureName); 298 switchPreference.setTitle(title); 299 } 300 getUseServicePreferenceKey()301 protected String getUseServicePreferenceKey() { 302 return "use_service"; 303 } 304 getShortcutTitle()305 protected CharSequence getShortcutTitle() { 306 return getString(R.string.accessibility_shortcut_title, mFeatureName); 307 } 308 309 @VisibleForTesting getContentDescriptionForAnimatedIllustration()310 CharSequence getContentDescriptionForAnimatedIllustration() { 311 return getString(R.string.accessibility_illustration_content_description, mFeatureName); 312 } 313 onPreferenceToggled(String preferenceKey, boolean enabled)314 protected void onPreferenceToggled(String preferenceKey, boolean enabled) { 315 } 316 onInstallSwitchPreferenceToggleSwitch()317 protected void onInstallSwitchPreferenceToggleSwitch() { 318 // Implement this to set a checked listener. 319 updateSwitchBarToggleSwitch(); 320 mToggleServiceSwitchPreference.addOnSwitchChangeListener(this); 321 } 322 onRemoveSwitchPreferenceToggleSwitch()323 protected void onRemoveSwitchPreferenceToggleSwitch() { 324 // Implement this to reset a checked listener. 325 } 326 updateSwitchBarToggleSwitch()327 protected void updateSwitchBarToggleSwitch() { 328 // Implement this to update the state of switch. 329 } 330 setTitle(String title)331 public void setTitle(String title) { 332 getActivity().setTitle(title); 333 } 334 onProcessArguments(@ullable Bundle arguments)335 protected void onProcessArguments(@Nullable Bundle arguments) { 336 if (arguments == null) { 337 return; 338 } 339 // Key. 340 mPreferenceKey = arguments.getString(AccessibilitySettings.EXTRA_PREFERENCE_KEY); 341 342 // Title. 343 if (arguments.containsKey(AccessibilitySettings.EXTRA_RESOLVE_INFO)) { 344 ResolveInfo info = arguments.getParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO); 345 getActivity().setTitle(info.loadLabel(getPackageManager()).toString()); 346 } else if (arguments.containsKey(AccessibilitySettings.EXTRA_TITLE)) { 347 setTitle(arguments.getString(AccessibilitySettings.EXTRA_TITLE)); 348 } 349 350 // Summary. 351 if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) { 352 mDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_SUMMARY); 353 } 354 355 // Settings html description. 356 if (arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) { 357 mHtmlDescription = arguments.getCharSequence( 358 AccessibilitySettings.EXTRA_HTML_DESCRIPTION); 359 } 360 361 // Intro. 362 if (arguments.containsKey(AccessibilitySettings.EXTRA_INTRO)) { 363 mTopIntroTitle = arguments.getCharSequence(AccessibilitySettings.EXTRA_INTRO); 364 } 365 } 366 installActionBarToggleSwitch()367 private void installActionBarToggleSwitch() { 368 onInstallSwitchPreferenceToggleSwitch(); 369 } 370 removeActionBarToggleSwitch()371 private void removeActionBarToggleSwitch() { 372 mToggleServiceSwitchPreference.setOnPreferenceClickListener(null); 373 onRemoveSwitchPreferenceToggleSwitch(); 374 } 375 updatePreferenceOrder()376 private void updatePreferenceOrder() { 377 final List<String> lists = getPreferenceOrderList(); 378 379 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 380 preferenceScreen.setOrderingAsAdded(false); 381 382 final int size = lists.size(); 383 for (int i = 0; i < size; i++) { 384 final Preference preference = preferenceScreen.findPreference(lists.get(i)); 385 if (preference != null) { 386 preference.setOrder(i); 387 } 388 } 389 } 390 391 /** Customizes the order by preference key. */ getPreferenceOrderList()392 protected List<String> getPreferenceOrderList() { 393 final List<String> lists = new ArrayList<>(); 394 lists.add(KEY_TOP_INTRO_PREFERENCE); 395 lists.add(KEY_ANIMATED_IMAGE); 396 lists.add(getUseServicePreferenceKey()); 397 lists.add(KEY_GENERAL_CATEGORY); 398 lists.add(KEY_HTML_DESCRIPTION_PREFERENCE); 399 return lists; 400 } 401 getDrawableFromUri(Uri imageUri)402 private Drawable getDrawableFromUri(Uri imageUri) { 403 if (mImageGetterCacheView == null) { 404 mImageGetterCacheView = new ImageView(getPrefContext()); 405 } 406 407 mImageGetterCacheView.setAdjustViewBounds(true); 408 mImageGetterCacheView.setImageURI(imageUri); 409 410 if (mImageGetterCacheView.getDrawable() == null) { 411 return null; 412 } 413 414 final Drawable drawable = 415 mImageGetterCacheView.getDrawable().mutate().getConstantState().newDrawable(); 416 mImageGetterCacheView.setImageURI(null); 417 final int imageWidth = drawable.getIntrinsicWidth(); 418 final int imageHeight = drawable.getIntrinsicHeight(); 419 final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2; 420 if ((imageWidth > AccessibilityUtil.getScreenWidthPixels(getPrefContext())) 421 || (imageHeight > screenHalfHeight)) { 422 return null; 423 } 424 425 drawable.setBounds(/* left= */0, /* top= */0, drawable.getIntrinsicWidth(), 426 drawable.getIntrinsicHeight()); 427 428 return drawable; 429 } initAnimatedImagePreference()430 private void initAnimatedImagePreference() { 431 initAnimatedImagePreference(mImageUri, new IllustrationPreference(getPrefContext()) { 432 @Override 433 public void onBindViewHolder(PreferenceViewHolder holder) { 434 super.onBindViewHolder(holder); 435 if (ThemeHelper.shouldApplyGlifExpressiveStyle(getContext()) 436 && isAnySetupWizard()) { 437 View illustrationFrame = holder.findViewById(R.id.illustration_frame); 438 final ViewGroup.LayoutParams lp = illustrationFrame.getLayoutParams(); 439 lp.width = ViewGroup.LayoutParams.MATCH_PARENT; 440 illustrationFrame.setLayoutParams(lp); 441 } 442 } 443 }); 444 } 445 446 @VisibleForTesting initAnimatedImagePreference( @ullable Uri imageUri, @NonNull IllustrationPreference preference)447 void initAnimatedImagePreference( 448 @Nullable Uri imageUri, 449 @NonNull IllustrationPreference preference) { 450 if (imageUri == null) { 451 return; 452 } 453 454 final int displayHalfHeight = 455 AccessibilityUtil.getDisplayBounds(getPrefContext()).height() / 2; 456 preference.setImageUri(imageUri); 457 preference.setSelectable(false); 458 preference.setMaxHeight(displayHalfHeight); 459 preference.setKey(KEY_ANIMATED_IMAGE); 460 preference.setOnBindListener(view -> { 461 // isAnimatable is decided in 462 // {@link IllustrationPreference#onBindViewHolder(PreferenceViewHolder)}. Therefore, we 463 // wait until the view is bond to set the content description for it. 464 // The content description is added for an animation illustration only. Since the static 465 // images are decorative. 466 ThreadUtils.getUiThreadHandler().post(() -> { 467 if (preference.isAnimatable()) { 468 preference.setContentDescription( 469 getContentDescriptionForAnimatedIllustration()); 470 } 471 }); 472 }); 473 getPreferenceScreen().addPreference(preference); 474 } 475 476 @VisibleForTesting initTopIntroPreference()477 void initTopIntroPreference() { 478 if (TextUtils.isEmpty(mTopIntroTitle)) { 479 return; 480 } 481 mTopIntroPreference = new TopIntroPreference(getPrefContext()); 482 mTopIntroPreference.setKey(KEY_TOP_INTRO_PREFERENCE); 483 mTopIntroPreference.setTitle(mTopIntroTitle); 484 getPreferenceScreen().addPreference(mTopIntroPreference); 485 } 486 initToggleServiceSwitchPreference()487 private void initToggleServiceSwitchPreference() { 488 mToggleServiceSwitchPreference = new SettingsMainSwitchPreference(getPrefContext()); 489 mToggleServiceSwitchPreference.setKey(getUseServicePreferenceKey()); 490 if (getArguments() != null 491 && getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) { 492 final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED); 493 mToggleServiceSwitchPreference.setChecked(enabled); 494 } 495 496 getPreferenceScreen().addPreference(mToggleServiceSwitchPreference); 497 } 498 initGeneralCategory()499 private void initGeneralCategory() { 500 final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext()); 501 generalCategory.setKey(KEY_GENERAL_CATEGORY); 502 generalCategory.setTitle(R.string.accessibility_screen_option); 503 504 getPreferenceScreen().addPreference(generalCategory); 505 } 506 initShortcutPreference()507 protected void initShortcutPreference() { 508 // Initial the shortcut preference. 509 mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null); 510 mShortcutPreference.setPersistent(false); 511 mShortcutPreference.setKey(getShortcutPreferenceKey()); 512 mShortcutPreference.setOnClickCallback(this); 513 mShortcutPreference.setTitle(getShortcutTitle()); 514 515 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 516 generalCategory.addPreference(mShortcutPreference); 517 } 518 initSettingsPreference()519 protected void initSettingsPreference() { 520 if (mSettingsTitle == null || mSettingsIntent == null) { 521 return; 522 } 523 524 // Show the "Settings" menu as if it were a preference screen. 525 mSettingsPreference = new Preference(getPrefContext()); 526 mSettingsPreference.setTitle(mSettingsTitle); 527 mSettingsPreference.setIconSpaceReserved(false); 528 mSettingsPreference.setIntent(mSettingsIntent); 529 530 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 531 generalCategory.addPreference(mSettingsPreference); 532 } 533 534 @VisibleForTesting 535 @Nullable createAppInfoPreference()536 Preference createAppInfoPreference() { 537 if (!Flags.accessibilityShowAppInfoButton()) { 538 return null; 539 } 540 // App Info is not available in Setup Wizard. 541 if (isAnySetupWizard()) { 542 return null; 543 } 544 // Only show the button for pages with valid component package names. 545 if (mComponentName == null) { 546 return null; 547 } 548 final String packageName = mComponentName.getPackageName(); 549 final PackageManager packageManager = getPrefContext().getPackageManager(); 550 if (!packageManager.isPackageAvailable(packageName)) { 551 return null; 552 } 553 554 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 555 intent.setPackage(getContext().getPackageName()); 556 intent.setData(Uri.parse("package:" + packageName)); 557 558 final Preference appInfoPreference = new Preference(getPrefContext()); 559 appInfoPreference.setTitle(getString(R.string.application_info_label)); 560 appInfoPreference.setIconSpaceReserved(false); 561 appInfoPreference.setIntent(intent); 562 return appInfoPreference; 563 } 564 initAppInfoPreference()565 private void initAppInfoPreference() { 566 final Preference appInfoPreference = createAppInfoPreference(); 567 if (appInfoPreference != null) { 568 final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); 569 generalCategory.addPreference(appInfoPreference); 570 } 571 } 572 initHtmlTextPreference()573 private void initHtmlTextPreference() { 574 if (TextUtils.isEmpty(getCurrentHtmlDescription())) { 575 return; 576 } 577 final PreferenceScreen screen = getPreferenceScreen(); 578 579 mHtmlFooterPreference = 580 new AccessibilityFooterPreference(screen.getContext()); 581 mHtmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE); 582 updateHtmlTextPreference(); 583 screen.addPreference(mHtmlFooterPreference); 584 585 // TODO(b/171272809): Migrate to DashboardFragment. 586 final String title = getString(R.string.accessibility_introduction_title, mFeatureName); 587 mFooterPreferenceController = new AccessibilityFooterPreferenceController( 588 screen.getContext(), mHtmlFooterPreference.getKey()); 589 mFooterPreferenceController.setIntroductionTitle(title); 590 mFooterPreferenceController.displayPreference(screen); 591 } 592 updateHtmlTextPreference()593 protected void updateHtmlTextPreference() { 594 if (mHtmlFooterPreference == null) { 595 return; 596 } 597 598 String description = getCurrentHtmlDescription().toString(); 599 final CharSequence htmlDescription = Html.fromHtml(description, 600 Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null); 601 mHtmlFooterPreference.setSummary(htmlDescription); 602 } 603 getCurrentHtmlDescription()604 CharSequence getCurrentHtmlDescription() { 605 return mHtmlDescription; 606 } 607 initFooterPreference()608 private void initFooterPreference() { 609 if (!TextUtils.isEmpty(mDescription)) { 610 createFooterPreference(getPreferenceScreen(), mDescription, 611 getString(R.string.accessibility_introduction_title, mFeatureName)); 612 } 613 } 614 615 /** 616 * Creates {@link AccessibilityFooterPreference} and append into {@link PreferenceScreen} 617 * 618 * @param screen The preference screen to add the footer preference 619 * @param summary The summary of the preference summary 620 * @param introductionTitle The title of introduction in the footer 621 */ 622 @VisibleForTesting createFooterPreference(PreferenceScreen screen, CharSequence summary, String introductionTitle)623 void createFooterPreference(PreferenceScreen screen, CharSequence summary, 624 String introductionTitle) { 625 final AccessibilityFooterPreference footerPreference = 626 new AccessibilityFooterPreference(screen.getContext()); 627 footerPreference.setSummary(summary); 628 screen.addPreference(footerPreference); 629 630 mFooterPreferenceController = new AccessibilityFooterPreferenceController( 631 screen.getContext(), footerPreference.getKey()); 632 mFooterPreferenceController.setIntroductionTitle(introductionTitle); 633 mFooterPreferenceController.displayPreference(screen); 634 } 635 getShortcutTypeSummary(Context context)636 protected CharSequence getShortcutTypeSummary(Context context) { 637 if (!mShortcutPreference.isSettingsEditable()) { 638 return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware); 639 } 640 641 if (!mShortcutPreference.isChecked()) { 642 return context.getText(R.string.accessibility_shortcut_state_off); 643 } 644 645 final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType( 646 context, mComponentName.flattenToString(), getDefaultShortcutTypes()); 647 return getShortcutSummaryList(context, shortcutTypes); 648 } 649 650 /** 651 * This method will be invoked when a button in the tutorial dialog is clicked. 652 * 653 * @param dialog The dialog that received the click 654 * @param which The button that was clicked 655 */ callOnTutorialDialogButtonClicked(DialogInterface dialog, int which)656 private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) { 657 dialog.dismiss(); 658 } 659 updateShortcutPreferenceData()660 protected void updateShortcutPreferenceData() { 661 if (mComponentName == null) { 662 return; 663 } 664 665 final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings( 666 getPrefContext(), mComponentName); 667 if (shortcutTypes != DEFAULT) { 668 final PreferredShortcut shortcut = new PreferredShortcut( 669 mComponentName.flattenToString(), shortcutTypes); 670 PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); 671 } 672 } 673 updateShortcutPreference()674 protected void updateShortcutPreference() { 675 if (mComponentName == null) { 676 return; 677 } 678 679 final int shortcutTypes = getUserPreferredShortcutTypes(); 680 mShortcutPreference.setChecked( 681 ShortcutUtils.isShortcutContained( 682 getPrefContext(), shortcutTypes, mComponentName.flattenToString())); 683 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 684 } 685 getShortcutPreferenceKey()686 protected String getShortcutPreferenceKey() { 687 return KEY_SHORTCUT_PREFERENCE; 688 } 689 690 @Override onToggleClicked(ShortcutPreference preference)691 public void onToggleClicked(ShortcutPreference preference) { 692 if (mComponentName == null) { 693 return; 694 } 695 696 final int shortcutTypes = getUserPreferredShortcutTypes(); 697 final boolean isChecked = preference.isChecked(); 698 getPrefContext().getSystemService(AccessibilityManager.class).enableShortcutsForTargets( 699 isChecked, shortcutTypes, 700 Set.of(mComponentName.flattenToString()), getPrefContext().getUserId()); 701 if (isChecked) { 702 showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); 703 } 704 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 705 } 706 707 @Override onSettingsClicked(ShortcutPreference preference)708 public void onSettingsClicked(ShortcutPreference preference) { 709 EditShortcutsPreferenceFragment.showEditShortcutScreen( 710 requireContext(), getMetricsCategory(), getShortcutTitle(), 711 mComponentName, getIntent()); 712 } 713 714 /** 715 * Setups {@link com.android.internal.R.string#config_defaultAccessibilityService} into 716 * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE} if that settings key has never 717 * been set and only write the key when user enter into corresponding page. 718 */ 719 @VisibleForTesting writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(Context context)720 void writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(Context context) { 721 if (mComponentName == null) { 722 return; 723 } 724 725 // It might be shortened form (with a leading '.'). Need to unflatten back to ComponentName 726 // first, or it will encounter errors when getting service from 727 // `ACCESSIBILITY_SHORTCUT_TARGET_SERVICE`. 728 final ComponentName configDefaultService = ComponentName.unflattenFromString( 729 getString(com.android.internal.R.string.config_defaultAccessibilityService)); 730 731 if (!mComponentName.equals(configDefaultService)) { 732 return; 733 } 734 735 final String targetKey = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; 736 final String targetString = Settings.Secure.getString(context.getContentResolver(), 737 targetKey); 738 739 // By intentional, we only need to write the config string when the Settings key has never 740 // been set (== null). Empty string also means someone already wrote it before, so we need 741 // to respect the value. 742 if (targetString == null) { 743 Settings.Secure.putString(context.getContentResolver(), targetKey, 744 configDefaultService.flattenToString()); 745 } 746 } 747 748 /** Returns user visible name of the tile by given {@link ComponentName}. */ loadTileLabel(Context context, ComponentName componentName)749 protected CharSequence loadTileLabel(Context context, ComponentName componentName) { 750 final PackageManager packageManager = context.getPackageManager(); 751 final Intent queryIntent = new Intent(TileService.ACTION_QS_TILE); 752 final List<ResolveInfo> resolveInfos = 753 packageManager.queryIntentServices(queryIntent, PackageManager.GET_META_DATA); 754 for (ResolveInfo info : resolveInfos) { 755 final ServiceInfo serviceInfo = info.serviceInfo; 756 if (TextUtils.equals(componentName.getPackageName(), serviceInfo.packageName) 757 && TextUtils.equals(componentName.getClassName(), serviceInfo.name)) { 758 return serviceInfo.loadLabel(packageManager); 759 } 760 } 761 return null; 762 } 763 764 @VisibleForTesting isAnySetupWizard()765 boolean isAnySetupWizard() { 766 return WizardManagerHelper.isAnySetupWizard(getIntent()); 767 } 768 769 /** 770 * Returns the default preferred shortcut types when the user doesn't have a preferred shortcut 771 * types 772 */ 773 @ShortcutConstants.UserShortcutType getDefaultShortcutTypes()774 protected int getDefaultShortcutTypes() { 775 return SOFTWARE; 776 } 777 778 /** 779 * Returns the user preferred shortcut types or the default shortcut types if not set 780 */ 781 @ShortcutConstants.UserShortcutType getUserPreferredShortcutTypes()782 protected int getUserPreferredShortcutTypes() { 783 return PreferredShortcuts.retrieveUserShortcutType( 784 getPrefContext(), mComponentName.flattenToString(), getDefaultShortcutTypes()); 785 } 786 787 @Override onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)788 public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, 789 Bundle savedInstanceState) { 790 RecyclerView recyclerView = 791 super.onCreateRecyclerView(inflater, parent, savedInstanceState); 792 return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView); 793 } 794 } 795