1 /* 2 * Copyright (C) 2019 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.AccessibilityUtil.State.OFF; 20 import static com.android.settings.accessibility.AccessibilityUtil.State.ON; 21 22 import android.app.Dialog; 23 import android.app.settings.SettingsEnums; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.os.Bundle; 27 import android.provider.Settings; 28 import android.text.method.LinkMovementMethod; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.widget.AdapterView; 33 import android.widget.ListView; 34 import android.widget.TextView; 35 36 import androidx.annotation.DrawableRes; 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 import androidx.annotation.VisibleForTesting; 40 import androidx.preference.Preference; 41 import androidx.preference.PreferenceScreen; 42 43 import com.android.settings.DialogCreatable; 44 import com.android.settings.R; 45 import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; 46 import com.android.settings.core.BasePreferenceController; 47 import com.android.settings.utils.AnnotationSpan; 48 import com.android.settingslib.core.lifecycle.LifecycleObserver; 49 import com.android.settingslib.core.lifecycle.events.OnCreate; 50 import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; 51 52 import java.util.ArrayList; 53 import java.util.List; 54 55 /** Controller that shows the magnification area mode summary and the preference click behavior. */ 56 public class MagnificationModePreferenceController extends BasePreferenceController implements 57 DialogCreatable, LifecycleObserver, OnCreate, OnSaveInstanceState { 58 59 static final String PREF_KEY = "screen_magnification_mode"; 60 private static final int DIALOG_ID_BASE = 10; 61 @VisibleForTesting 62 static final int DIALOG_MAGNIFICATION_MODE = DIALOG_ID_BASE + 1; 63 @VisibleForTesting 64 static final int DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING = DIALOG_ID_BASE + 2; 65 @VisibleForTesting 66 static final String EXTRA_MODE = "mode"; 67 68 private static final String TAG = "MagnificationModePreferenceController"; 69 70 private DialogHelper mDialogHelper; 71 // The magnification mode in the dialog. 72 @MagnificationMode 73 private int mModeCache = MagnificationMode.NONE; 74 private Preference mModePreference; 75 private ShortcutPreference mLinkPreference; 76 77 @VisibleForTesting 78 ListView mMagnificationModesListView; 79 80 private final List<MagnificationModeInfo> mModeInfos = new ArrayList<>(); 81 MagnificationModePreferenceController(Context context, String preferenceKey)82 public MagnificationModePreferenceController(Context context, String preferenceKey) { 83 super(context, preferenceKey); 84 initModeInfos(); 85 } 86 initModeInfos()87 private void initModeInfos() { 88 mModeInfos.add(new MagnificationModeInfo(mContext.getText( 89 R.string.accessibility_magnification_mode_dialog_option_full_screen), null, 90 R.drawable.a11y_magnification_mode_fullscreen, MagnificationMode.FULLSCREEN)); 91 mModeInfos.add(new MagnificationModeInfo( 92 mContext.getText(R.string.accessibility_magnification_mode_dialog_option_window), 93 null, R.drawable.a11y_magnification_mode_window, MagnificationMode.WINDOW)); 94 mModeInfos.add(new MagnificationModeInfo( 95 mContext.getText(R.string.accessibility_magnification_mode_dialog_option_switch), 96 mContext.getText( 97 R.string.accessibility_magnification_area_settings_mode_switch_summary), 98 R.drawable.a11y_magnification_mode_switch, MagnificationMode.ALL)); 99 } 100 101 @Override getAvailabilityStatus()102 public int getAvailabilityStatus() { 103 return AVAILABLE; 104 } 105 106 @Override getSummary()107 public CharSequence getSummary() { 108 final int capabilities = MagnificationCapabilities.getCapabilities(mContext); 109 return MagnificationCapabilities.getSummary(mContext, capabilities); 110 } 111 112 @Override onCreate(Bundle savedInstanceState)113 public void onCreate(Bundle savedInstanceState) { 114 if (savedInstanceState != null) { 115 mModeCache = savedInstanceState.getInt(EXTRA_MODE, MagnificationMode.NONE); 116 } 117 } 118 119 @Override displayPreference(PreferenceScreen screen)120 public void displayPreference(PreferenceScreen screen) { 121 super.displayPreference(screen); 122 mModePreference = screen.findPreference(getPreferenceKey()); 123 mLinkPreference = screen.findPreference( 124 ToggleFeaturePreferenceFragment.KEY_SHORTCUT_PREFERENCE); 125 mModePreference.setOnPreferenceClickListener(preference -> { 126 mModeCache = MagnificationCapabilities.getCapabilities(mContext); 127 mDialogHelper.showDialog(DIALOG_MAGNIFICATION_MODE); 128 return true; 129 }); 130 } 131 132 @Override onSaveInstanceState(Bundle outState)133 public void onSaveInstanceState(Bundle outState) { 134 outState.putInt(EXTRA_MODE, mModeCache); 135 } 136 137 /** 138 * Sets {@link DialogHelper} used to show the dialog. 139 */ setDialogHelper(DialogHelper dialogHelper)140 public void setDialogHelper(DialogHelper dialogHelper) { 141 mDialogHelper = dialogHelper; 142 mDialogHelper.setDialogDelegate(this); 143 } 144 145 @Override onCreateDialog(int dialogId)146 public Dialog onCreateDialog(int dialogId) { 147 switch (dialogId) { 148 case DIALOG_MAGNIFICATION_MODE: 149 return createMagnificationModeDialog(); 150 151 case DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING: 152 return createMagnificationTripleTapWarningDialog(); 153 } 154 return null; 155 } 156 157 @Override getDialogMetricsCategory(int dialogId)158 public int getDialogMetricsCategory(int dialogId) { 159 switch (dialogId) { 160 case DIALOG_MAGNIFICATION_MODE: 161 return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY; 162 case DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING: 163 return SettingsEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING; 164 default: 165 return 0; 166 } 167 } 168 createMagnificationModeDialog()169 private Dialog createMagnificationModeDialog() { 170 mMagnificationModesListView = AccessibilityDialogUtils.createSingleChoiceListView( 171 mContext, mModeInfos, this::onMagnificationModeSelected); 172 173 final View headerView = LayoutInflater.from(mContext).inflate( 174 R.layout.accessibility_magnification_mode_header, mMagnificationModesListView, 175 false); 176 mMagnificationModesListView.addHeaderView(headerView, /* data= */ null, /* isSelectable= */ 177 false); 178 179 mMagnificationModesListView.setItemChecked(computeSelectionIndex(), true); 180 final CharSequence title = mContext.getString( 181 R.string.accessibility_magnification_mode_dialog_title); 182 final CharSequence positiveBtnText = mContext.getString(R.string.save); 183 final CharSequence negativeBtnText = mContext.getString(R.string.cancel); 184 185 return AccessibilityDialogUtils.createCustomDialog(mContext, title, 186 mMagnificationModesListView, 187 positiveBtnText, this::onMagnificationModeDialogPositiveButtonClicked, 188 negativeBtnText, /* negativeListener= */ null); 189 } 190 191 @VisibleForTesting onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface, int which)192 void onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface, 193 int which) { 194 final int selectedIndex = mMagnificationModesListView.getCheckedItemPosition(); 195 if (selectedIndex == AdapterView.INVALID_POSITION) { 196 Log.w(TAG, "invalid index"); 197 return; 198 } 199 200 mModeCache = ((MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition( 201 selectedIndex)).mMagnificationMode; 202 203 // Do not save mode until user clicks positive button in triple tap warning dialog. 204 if (isTripleTapEnabled(mContext) && mModeCache != MagnificationMode.FULLSCREEN) { 205 mDialogHelper.showDialog(DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING); 206 } else { // Save mode (capabilities) value, don't need to show dialog to confirm. 207 updateCapabilitiesAndSummary(mModeCache); 208 } 209 } 210 updateCapabilitiesAndSummary(@agnificationMode int mode)211 private void updateCapabilitiesAndSummary(@MagnificationMode int mode) { 212 mModeCache = mode; 213 MagnificationCapabilities.setCapabilities(mContext, mModeCache); 214 mModePreference.setSummary( 215 MagnificationCapabilities.getSummary(mContext, mModeCache)); 216 } 217 onMagnificationModeSelected(AdapterView<?> parent, View view, int position, long id)218 private void onMagnificationModeSelected(AdapterView<?> parent, View view, int position, 219 long id) { 220 final MagnificationModeInfo modeInfo = 221 (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition( 222 position); 223 if (modeInfo.mMagnificationMode == mModeCache) { 224 return; 225 } 226 mModeCache = modeInfo.mMagnificationMode; 227 } 228 computeSelectionIndex()229 private int computeSelectionIndex() { 230 final int modesSize = mModeInfos.size(); 231 for (int i = 0; i < modesSize; i++) { 232 if (mModeInfos.get(i).mMagnificationMode == mModeCache) { 233 return i + mMagnificationModesListView.getHeaderViewsCount(); 234 } 235 } 236 Log.w(TAG, "computeSelectionIndex failed"); 237 return 0; 238 } 239 240 @VisibleForTesting isTripleTapEnabled(Context context)241 static boolean isTripleTapEnabled(Context context) { 242 return Settings.Secure.getInt(context.getContentResolver(), 243 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON; 244 } 245 createMagnificationTripleTapWarningDialog()246 private Dialog createMagnificationTripleTapWarningDialog() { 247 final View contentView = LayoutInflater.from(mContext).inflate( 248 R.layout.magnification_triple_tap_warning_dialog, /* root= */ null); 249 final CharSequence title = mContext.getString( 250 R.string.accessibility_magnification_triple_tap_warning_title); 251 final CharSequence positiveBtnText = mContext.getString( 252 R.string.accessibility_magnification_triple_tap_warning_positive_button); 253 final CharSequence negativeBtnText = mContext.getString( 254 R.string.accessibility_magnification_triple_tap_warning_negative_button); 255 256 final Dialog dialog = AccessibilityDialogUtils.createCustomDialog(mContext, title, 257 contentView, 258 positiveBtnText, this::onMagnificationTripleTapWarningDialogPositiveButtonClicked, 259 negativeBtnText, this::onMagnificationTripleTapWarningDialogNegativeButtonClicked); 260 261 updateLinkInTripleTapWarningDialog(dialog, contentView); 262 263 return dialog; 264 } 265 updateLinkInTripleTapWarningDialog(Dialog dialog, View contentView)266 private void updateLinkInTripleTapWarningDialog(Dialog dialog, View contentView) { 267 final TextView messageView = contentView.findViewById(R.id.message); 268 // TODO(b/225682559): Need to remove performClick() after refactoring accessibility dialog. 269 final View.OnClickListener linkListener = view -> { 270 updateCapabilitiesAndSummary(mModeCache); 271 mLinkPreference.performClick(); 272 dialog.dismiss(); 273 }; 274 final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo( 275 AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener); 276 final CharSequence textWithLink = AnnotationSpan.linkify(mContext.getText( 277 R.string.accessibility_magnification_triple_tap_warning_message), linkInfo); 278 279 if (messageView != null) { 280 messageView.setText(textWithLink); 281 messageView.setMovementMethod(LinkMovementMethod.getInstance()); 282 } 283 dialog.setContentView(contentView); 284 } 285 286 @VisibleForTesting onMagnificationTripleTapWarningDialogNegativeButtonClicked( DialogInterface dialogInterface, int which)287 void onMagnificationTripleTapWarningDialogNegativeButtonClicked( 288 DialogInterface dialogInterface, int which) { 289 mModeCache = MagnificationCapabilities.getCapabilities(mContext); 290 mDialogHelper.showDialog(DIALOG_MAGNIFICATION_MODE); 291 } 292 293 @VisibleForTesting onMagnificationTripleTapWarningDialogPositiveButtonClicked( DialogInterface dialogInterface, int which)294 void onMagnificationTripleTapWarningDialogPositiveButtonClicked( 295 DialogInterface dialogInterface, int which) { 296 updateCapabilitiesAndSummary(mModeCache); 297 } 298 299 /** 300 * An interface to help the delegate to show the dialog. It will be injected to the delegate. 301 */ 302 interface DialogHelper extends DialogCreatable { showDialog(int dialogId)303 void showDialog(int dialogId); setDialogDelegate(DialogCreatable delegate)304 void setDialogDelegate(DialogCreatable delegate); 305 } 306 307 @VisibleForTesting 308 static class MagnificationModeInfo extends ItemInfoArrayAdapter.ItemInfo { 309 @MagnificationMode 310 public final int mMagnificationMode; 311 MagnificationModeInfo(@onNull CharSequence title, @Nullable CharSequence summary, @DrawableRes int drawableId, @MagnificationMode int magnificationMode)312 MagnificationModeInfo(@NonNull CharSequence title, @Nullable CharSequence summary, 313 @DrawableRes int drawableId, @MagnificationMode int magnificationMode) { 314 super(title, summary, drawableId); 315 mMagnificationMode = magnificationMode; 316 } 317 } 318 } 319