1 /* 2 * Copyright (C) 2022 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.TextReadingResetController.ResetStateListener; 20 21 import android.app.Activity; 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.widget.Toast; 28 29 import androidx.annotation.IntDef; 30 import androidx.appcompat.app.AlertDialog; 31 32 import com.android.settings.R; 33 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; 34 import com.android.settings.dashboard.DashboardFragment; 35 import com.android.settings.search.BaseSearchIndexProvider; 36 import com.android.settingslib.core.AbstractPreferenceController; 37 import com.android.settingslib.search.SearchIndexable; 38 39 import com.google.android.setupcompat.util.WizardManagerHelper; 40 import com.google.common.annotations.VisibleForTesting; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.ArrayList; 45 import java.util.List; 46 import java.util.stream.Collectors; 47 48 /** 49 * Accessibility settings for adjusting the system features which are related to the reading. For 50 * example, bold text, high contrast text, display size, font size and so on. 51 */ 52 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) 53 public class TextReadingPreferenceFragment extends DashboardFragment { 54 public static final String EXTRA_LAUNCHED_FROM = "launched_from"; 55 private static final String TAG = "TextReadingPreferenceFragment"; 56 private static final String SETUP_WIZARD_PACKAGE = "setupwizard"; 57 static final String FONT_SIZE_KEY = "font_size"; 58 static final String DISPLAY_SIZE_KEY = "display_size"; 59 static final String BOLD_TEXT_KEY = "toggle_force_bold_text"; 60 static final String HIGH_TEXT_CONTRAST_KEY = "toggle_high_text_contrast_preference"; 61 static final String RESET_KEY = "reset"; 62 private static final String PREVIEW_KEY = "preview"; 63 private static final String NEED_RESET_SETTINGS = "need_reset_settings"; 64 private FontWeightAdjustmentPreferenceController mFontWeightAdjustmentController; 65 private int mEntryPoint = EntryPoint.UNKNOWN_ENTRY; 66 67 /** 68 * The entry point which launches the {@link TextReadingPreferenceFragment}. 69 * 70 * <p>This should only be used for logging. 71 */ 72 @Retention(RetentionPolicy.SOURCE) 73 @IntDef({ 74 EntryPoint.UNKNOWN_ENTRY, 75 EntryPoint.SUW_VISION_SETTINGS, 76 EntryPoint.SUW_ANYTHING_ELSE, 77 EntryPoint.DISPLAY_SETTINGS, 78 EntryPoint.ACCESSIBILITY_SETTINGS, 79 }) 80 @interface EntryPoint { 81 int UNKNOWN_ENTRY = 0; 82 int SUW_VISION_SETTINGS = 1; 83 int SUW_ANYTHING_ELSE = 2; 84 int DISPLAY_SETTINGS = 3; 85 int ACCESSIBILITY_SETTINGS = 4; 86 } 87 88 @VisibleForTesting 89 List<ResetStateListener> mResetStateListeners; 90 91 @VisibleForTesting 92 boolean mNeedResetSettings; 93 94 @Override onCreate(Bundle icicle)95 public void onCreate(Bundle icicle) { 96 super.onCreate(icicle); 97 98 mNeedResetSettings = false; 99 mResetStateListeners = getResetStateListeners(); 100 101 if (icicle != null && icicle.getBoolean(NEED_RESET_SETTINGS)) { 102 mResetStateListeners.forEach(ResetStateListener::resetState); 103 } 104 } 105 106 @Override getPreferenceScreenResId()107 protected int getPreferenceScreenResId() { 108 return R.xml.accessibility_text_reading_options; 109 } 110 111 @Override getLogTag()112 protected String getLogTag() { 113 return TAG; 114 } 115 116 @Override getMetricsCategory()117 public int getMetricsCategory() { 118 return SettingsEnums.ACCESSIBILITY_TEXT_READING_OPTIONS; 119 } 120 121 @Override createPreferenceControllers(Context context)122 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 123 updateEntryPoint(); 124 125 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 126 final FontSizeData fontSizeData = new FontSizeData(context); 127 final DisplaySizeData displaySizeData = createDisplaySizeData(context); 128 129 final TextReadingPreviewController previewController = new TextReadingPreviewController( 130 context, PREVIEW_KEY, fontSizeData, displaySizeData); 131 previewController.setEntryPoint(mEntryPoint); 132 controllers.add(previewController); 133 134 final PreviewSizeSeekBarController fontSizeController = new PreviewSizeSeekBarController( 135 context, FONT_SIZE_KEY, fontSizeData); 136 fontSizeController.setInteractionListener(previewController); 137 controllers.add(fontSizeController); 138 139 final PreviewSizeSeekBarController displaySizeController = new PreviewSizeSeekBarController( 140 context, DISPLAY_SIZE_KEY, displaySizeData); 141 displaySizeController.setInteractionListener(previewController); 142 controllers.add(displaySizeController); 143 144 mFontWeightAdjustmentController = 145 new FontWeightAdjustmentPreferenceController(context, BOLD_TEXT_KEY); 146 mFontWeightAdjustmentController.setEntryPoint(mEntryPoint); 147 controllers.add(mFontWeightAdjustmentController); 148 149 final HighTextContrastPreferenceController highTextContrastController = 150 new HighTextContrastPreferenceController(context, HIGH_TEXT_CONTRAST_KEY); 151 highTextContrastController.setEntryPoint(mEntryPoint); 152 controllers.add(highTextContrastController); 153 154 final TextReadingResetController resetController = 155 new TextReadingResetController(context, RESET_KEY, 156 v -> showDialog(DialogEnums.DIALOG_RESET_SETTINGS)); 157 resetController.setEntryPoint(mEntryPoint); 158 resetController.setVisible(!WizardManagerHelper.isAnySetupWizard(getIntent())); 159 controllers.add(resetController); 160 161 return controllers; 162 } 163 164 @Override onCreateDialog(int dialogId)165 public Dialog onCreateDialog(int dialogId) { 166 if (dialogId == DialogEnums.DIALOG_RESET_SETTINGS) { 167 return new AlertDialog.Builder(getPrefContext()) 168 .setTitle(R.string.accessibility_text_reading_confirm_dialog_title) 169 .setMessage(R.string.accessibility_text_reading_confirm_dialog_message) 170 .setPositiveButton( 171 R.string.accessibility_text_reading_confirm_dialog_reset_button, 172 this::onPositiveButtonClicked) 173 .setNegativeButton(R.string.cancel, /* listener= */ null) 174 .create(); 175 } 176 177 throw new IllegalArgumentException("Unsupported dialogId " + dialogId); 178 } 179 180 @Override getDialogMetricsCategory(int dialogId)181 public int getDialogMetricsCategory(int dialogId) { 182 if (dialogId == DialogEnums.DIALOG_RESET_SETTINGS) { 183 return SettingsEnums.DIALOG_RESET_SETTINGS; 184 } 185 186 return super.getDialogMetricsCategory(dialogId); 187 } 188 189 @Override onSaveInstanceState(Bundle outState)190 public void onSaveInstanceState(Bundle outState) { 191 if (mNeedResetSettings) { 192 outState.putBoolean(NEED_RESET_SETTINGS, true); 193 } 194 } 195 isCallingFromAnythingElseEntryPoint()196 protected boolean isCallingFromAnythingElseEntryPoint() { 197 final Activity activity = getActivity(); 198 final String callingPackage = activity != null ? activity.getCallingPackage() : null; 199 200 return callingPackage != null && callingPackage.contains(SETUP_WIZARD_PACKAGE); 201 } 202 203 @VisibleForTesting createDisplaySizeData(Context context)204 DisplaySizeData createDisplaySizeData(Context context) { 205 return new DisplaySizeData(context); 206 } 207 updateEntryPoint()208 private void updateEntryPoint() { 209 final Bundle bundle = getArguments(); 210 if (bundle != null && bundle.containsKey(EXTRA_LAUNCHED_FROM)) { 211 mEntryPoint = bundle.getInt(EXTRA_LAUNCHED_FROM, EntryPoint.UNKNOWN_ENTRY); 212 return; 213 } 214 215 mEntryPoint = isCallingFromAnythingElseEntryPoint() 216 ? EntryPoint.SUW_ANYTHING_ELSE : EntryPoint.UNKNOWN_ENTRY; 217 } 218 onPositiveButtonClicked(DialogInterface dialog, int which)219 private void onPositiveButtonClicked(DialogInterface dialog, int which) { 220 // To avoid showing the dialog again, probably the onDetach() of SettingsDialogFragment 221 // was interrupted by unexpectedly recreating the activity. 222 removeDialog(DialogEnums.DIALOG_RESET_SETTINGS); 223 224 if (mFontWeightAdjustmentController.isChecked()) { 225 // TODO(b/228956791): Consider replacing or removing it once the root cause is 226 // clarified and the better method is available. 227 // Probably has the race condition issue between "Bold text" and the other features 228 // including "Display Size", “Font Size” if they would be enabled at the same time, 229 // so our workaround is that the “Bold text” would be reset first and then do the 230 // remaining to avoid flickering problem. 231 mNeedResetSettings = true; 232 mFontWeightAdjustmentController.resetState(); 233 } else { 234 mResetStateListeners.forEach(ResetStateListener::resetState); 235 } 236 237 Toast.makeText(getPrefContext(), R.string.accessibility_text_reading_reset_message, 238 Toast.LENGTH_SHORT).show(); 239 } 240 getResetStateListeners()241 private List<ResetStateListener> getResetStateListeners() { 242 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 243 getPreferenceControllers().forEach(controllers::addAll); 244 return controllers.stream().filter(c -> c instanceof ResetStateListener).map( 245 c -> (ResetStateListener) c).collect(Collectors.toList()); 246 } 247 248 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 249 new BaseSearchIndexProvider(R.xml.accessibility_text_reading_options); 250 } 251