• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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