1 /* 2 * Copyright (C) 2021 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.settings.accessibility.AccessibilityDialogUtils.DialogEnums; 21 import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList; 22 import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY; 23 24 import android.annotation.SuppressLint; 25 import android.app.Dialog; 26 import android.app.settings.SettingsEnums; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.DialogInterface; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.provider.Settings; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.view.accessibility.AccessibilityManager; 37 38 import androidx.annotation.Nullable; 39 import androidx.annotation.VisibleForTesting; 40 import androidx.preference.PreferenceCategory; 41 import androidx.preference.PreferenceScreen; 42 43 import com.android.internal.accessibility.common.ShortcutConstants; 44 import com.android.internal.accessibility.util.ShortcutUtils; 45 import com.android.settings.R; 46 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment; 47 import com.android.settings.dashboard.RestrictedDashboardFragment; 48 49 import com.google.android.setupcompat.util.WizardManagerHelper; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.Set; 54 55 /** 56 * Base class for accessibility fragments shortcut functions and dialog management. 57 */ 58 public abstract class AccessibilityShortcutPreferenceFragment extends RestrictedDashboardFragment 59 implements ShortcutPreference.OnClickCallback { 60 private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; 61 62 protected ShortcutPreference mShortcutPreference; 63 protected Dialog mDialog; 64 private AccessibilityManager.TouchExplorationStateChangeListener 65 mTouchExplorationStateChangeListener; 66 private AccessibilitySettingsContentObserver mSettingsContentObserver; 67 AccessibilityShortcutPreferenceFragment(String restrictionKey)68 public AccessibilityShortcutPreferenceFragment(String restrictionKey) { 69 super(restrictionKey); 70 } 71 72 /** Returns the accessibility component name. */ getComponentName()73 protected abstract ComponentName getComponentName(); 74 75 /** Returns the accessibility feature name. */ getLabelName()76 protected abstract CharSequence getLabelName(); 77 78 @Override onCreate(Bundle savedInstanceState)79 public void onCreate(Bundle savedInstanceState) { 80 super.onCreate(savedInstanceState); 81 82 final int resId = getPreferenceScreenResId(); 83 if (resId <= 0) { 84 final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( 85 getPrefContext()); 86 setPreferenceScreen(preferenceScreen); 87 } 88 89 if (showGeneralCategory()) { 90 initGeneralCategory(); 91 } 92 93 final List<String> shortcutFeatureKeys = new ArrayList<>(); 94 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); 95 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); 96 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS); 97 mSettingsContentObserver = new AccessibilitySettingsContentObserver(new Handler()); 98 mSettingsContentObserver.registerKeysToObserverCallback(shortcutFeatureKeys, key -> { 99 updateShortcutPreferenceData(); 100 updateShortcutPreference(); 101 }); 102 } 103 104 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)105 public View onCreateView(LayoutInflater inflater, ViewGroup container, 106 Bundle savedInstanceState) { 107 mShortcutPreference = 108 getPreferenceScreen().findPreference(getShortcutPreferenceKey()); 109 if (mShortcutPreference == null) { 110 mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null); 111 mShortcutPreference.setPersistent(false); 112 mShortcutPreference.setKey(getShortcutPreferenceKey()); 113 getPreferenceScreen().addPreference(mShortcutPreference); 114 } 115 116 mShortcutPreference.setOnClickCallback(this); 117 mShortcutPreference.setTitle(getShortcutTitle()); 118 mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { 119 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 120 }; 121 122 return super.onCreateView(inflater, container, savedInstanceState); 123 } 124 125 @Override onResume()126 public void onResume() { 127 super.onResume(); 128 129 final AccessibilityManager am = getPrefContext().getSystemService( 130 AccessibilityManager.class); 131 am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 132 mSettingsContentObserver.register(getContentResolver()); 133 updateShortcutPreferenceData(); 134 updateShortcutPreference(); 135 } 136 137 @Override onPause()138 public void onPause() { 139 final AccessibilityManager am = getPrefContext().getSystemService( 140 AccessibilityManager.class); 141 am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); 142 mSettingsContentObserver.unregister(getContentResolver()); 143 super.onPause(); 144 } 145 146 @Override onCreateDialog(int dialogId)147 public Dialog onCreateDialog(int dialogId) { 148 switch (dialogId) { 149 case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: 150 if (WizardManagerHelper.isAnySetupWizard(getIntent())) { 151 mDialog = AccessibilityShortcutsTutorial 152 .createAccessibilityTutorialDialogForSetupWizard( 153 getPrefContext(), getUserPreferredShortcutTypes(), 154 this::callOnTutorialDialogButtonClicked, getLabelName()); 155 } else { 156 mDialog = AccessibilityShortcutsTutorial 157 .createAccessibilityTutorialDialog( 158 getPrefContext(), getUserPreferredShortcutTypes(), 159 this::callOnTutorialDialogButtonClicked, getLabelName()); 160 } 161 mDialog.setCanceledOnTouchOutside(false); 162 return mDialog; 163 default: 164 throw new IllegalArgumentException("Unsupported dialogId " + dialogId); 165 } 166 } 167 getShortcutTitle()168 protected CharSequence getShortcutTitle() { 169 return getString(R.string.accessibility_shortcut_title, getLabelName()); 170 } 171 172 @Override getDialogMetricsCategory(int dialogId)173 public int getDialogMetricsCategory(int dialogId) { 174 switch (dialogId) { 175 case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: 176 return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL; 177 default: 178 return SettingsEnums.ACTION_UNKNOWN; 179 } 180 } 181 182 @Override onSettingsClicked(ShortcutPreference preference)183 public void onSettingsClicked(ShortcutPreference preference) { 184 EditShortcutsPreferenceFragment.showEditShortcutScreen( 185 getContext(), 186 getMetricsCategory(), 187 getShortcutTitle(), 188 getComponentName(), 189 getIntent() 190 ); 191 } 192 193 @SuppressLint("MissingPermission") 194 @Override onToggleClicked(ShortcutPreference preference)195 public void onToggleClicked(ShortcutPreference preference) { 196 if (getComponentName() == null) { 197 return; 198 } 199 200 final int shortcutTypes = getUserPreferredShortcutTypes(); 201 final boolean isChecked = preference.isChecked(); 202 getPrefContext().getSystemService(AccessibilityManager.class).enableShortcutsForTargets( 203 isChecked, shortcutTypes, 204 Set.of(getComponentName().flattenToString()), getPrefContext().getUserId()); 205 if (isChecked) { 206 showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); 207 } 208 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 209 } 210 211 /** 212 * Overrides to return specific shortcut preference key 213 * 214 * @return String The specific shortcut preference key 215 */ getShortcutPreferenceKey()216 protected String getShortcutPreferenceKey() { 217 return KEY_SHORTCUT_PREFERENCE; 218 } 219 220 /** 221 * Returns the shortcut type list which has been checked by user. 222 */ getUserShortcutTypes()223 protected int getUserShortcutTypes() { 224 return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(), 225 getComponentName()); 226 }; 227 getSoftwareShortcutTypeSummary(Context context)228 private static CharSequence getSoftwareShortcutTypeSummary(Context context) { 229 int resId; 230 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 231 resId = R.string.accessibility_shortcut_edit_summary_software; 232 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 233 resId = R.string.accessibility_shortcut_edit_summary_software_gesture; 234 } else { 235 resId = R.string.accessibility_shortcut_edit_summary_software; 236 } 237 return context.getText(resId); 238 } 239 240 /** 241 * This method will be invoked when a button in the tutorial dialog is clicked. 242 * 243 * @param dialog The dialog that received the click 244 * @param which The button that was clicked 245 */ callOnTutorialDialogButtonClicked(DialogInterface dialog, int which)246 private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) { 247 dialog.dismiss(); 248 } 249 250 @VisibleForTesting initGeneralCategory()251 void initGeneralCategory() { 252 final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext()); 253 generalCategory.setKey(KEY_GENERAL_CATEGORY); 254 generalCategory.setTitle(getGeneralCategoryDescription(null)); 255 256 getPreferenceScreen().addPreference(generalCategory); 257 } 258 259 /** 260 * Overrides to return customized description for general category above shortcut 261 * 262 * @return CharSequence The customized description for general category 263 */ getGeneralCategoryDescription(@ullable CharSequence title)264 protected CharSequence getGeneralCategoryDescription(@Nullable CharSequence title) { 265 if (title == null || title.toString().isEmpty()) { 266 // Return default 'Options' string for category 267 return getContext().getString(R.string.accessibility_screen_option); 268 } 269 return title; 270 } 271 272 /** 273 * Overrides to determinate if showing additional category description above shortcut 274 * 275 * @return boolean true to show category, false otherwise. 276 */ showGeneralCategory()277 protected boolean showGeneralCategory() { 278 return false; 279 } 280 getShortcutTypeSummary(Context context)281 protected CharSequence getShortcutTypeSummary(Context context) { 282 if (!mShortcutPreference.isSettingsEditable()) { 283 return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware); 284 } 285 286 if (!mShortcutPreference.isChecked()) { 287 return context.getText(R.string.accessibility_shortcut_state_off); 288 } 289 290 final int shortcutTypes = getUserPreferredShortcutTypes(); 291 return getShortcutSummaryList(context, shortcutTypes); 292 } 293 updateShortcutPreferenceData()294 protected void updateShortcutPreferenceData() { 295 if (getComponentName() == null) { 296 return; 297 } 298 299 final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings( 300 getPrefContext(), getComponentName()); 301 if (shortcutTypes != DEFAULT) { 302 final PreferredShortcut shortcut = new PreferredShortcut( 303 getComponentName().flattenToString(), shortcutTypes); 304 PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); 305 } 306 } 307 updateShortcutPreference()308 protected void updateShortcutPreference() { 309 if (getComponentName() == null) { 310 return; 311 } 312 313 final int shortcutTypes = getUserPreferredShortcutTypes(); 314 mShortcutPreference.setChecked( 315 ShortcutUtils.isShortcutContained( 316 getPrefContext(), shortcutTypes, getComponentName().flattenToString())); 317 mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 318 } 319 320 /** 321 * Returns the user preferred shortcut types or the default shortcut types if not set 322 */ 323 @ShortcutConstants.UserShortcutType getUserPreferredShortcutTypes()324 protected int getUserPreferredShortcutTypes() { 325 return PreferredShortcuts.retrieveUserShortcutType( 326 getPrefContext(), 327 getComponentName().flattenToString()); 328 } 329 } 330