• 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.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_COMPONENT_NAME;
20 import static com.android.settings.accessibility.TextReadingResetController.ResetStateListener;
21 
22 import android.app.Activity;
23 import android.app.Dialog;
24 import android.app.settings.SettingsEnums;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.os.Bundle;
29 import android.view.View;
30 import android.widget.Toast;
31 
32 import androidx.annotation.IntDef;
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 import androidx.appcompat.app.AlertDialog;
36 
37 import com.android.graphics.hwui.flags.Flags;
38 import com.android.modules.expresslog.Counter;
39 import com.android.settings.R;
40 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
41 import com.android.settings.dashboard.DashboardFragment;
42 import com.android.settings.search.BaseSearchIndexProvider;
43 import com.android.settingslib.core.AbstractPreferenceController;
44 import com.android.settingslib.search.SearchIndexable;
45 
46 import com.google.android.setupcompat.util.WizardManagerHelper;
47 import com.google.common.annotations.VisibleForTesting;
48 
49 import java.lang.annotation.Retention;
50 import java.lang.annotation.RetentionPolicy;
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.stream.Collectors;
54 
55 /**
56  * Accessibility settings for adjusting the system features which are related to the reading. For
57  * example, bold text, high contrast text, display size, font size and so on.
58  */
59 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
60 public class TextReadingPreferenceFragment extends DashboardFragment {
61     public static final String EXTRA_LAUNCHED_FROM = "launched_from";
62     private static final String TAG = "TextReadingPreferenceFragment";
63     private static final String SETUP_WIZARD_PACKAGE = "setupwizard";
64     static final String FONT_SIZE_KEY = "font_size";
65     static final String DISPLAY_SIZE_KEY = "display_size";
66     static final String BOLD_TEXT_KEY = "toggle_force_bold_text";
67     static final String HIGH_TEXT_CONTRAST_KEY = "toggle_high_text_contrast_preference";
68     static final String RESET_KEY = "reset";
69     static final String PREVIEW_KEY = "preview";
70     private static final String NEED_RESET_SETTINGS = "need_reset_settings";
71     private static final int UNKNOWN_INDEX = -1;
72 
73     private FontWeightAdjustmentPreferenceController mFontWeightAdjustmentController;
74     private TextReadingPreviewController mPreviewController;
75     private int mEntryPoint = EntryPoint.UNKNOWN_ENTRY;
76 
77     /**
78      * The entry point which launches the {@link TextReadingPreferenceFragment}.
79      *
80      * <p>This should only be used for logging.
81      */
82     @Retention(RetentionPolicy.SOURCE)
83     @IntDef({
84             EntryPoint.UNKNOWN_ENTRY,
85             EntryPoint.SUW_VISION_SETTINGS,
86             EntryPoint.SUW_ANYTHING_ELSE,
87             EntryPoint.DISPLAY_SETTINGS,
88             EntryPoint.ACCESSIBILITY_SETTINGS,
89             EntryPoint.HIGH_CONTRAST_TEXT_NOTIFICATION,
90     })
91     @interface EntryPoint {
92         int UNKNOWN_ENTRY = 0;
93         int SUW_VISION_SETTINGS = 1;
94         int SUW_ANYTHING_ELSE = 2;
95         int DISPLAY_SETTINGS = 3;
96         int ACCESSIBILITY_SETTINGS = 4;
97         int HIGH_CONTRAST_TEXT_NOTIFICATION = 5;
98     }
99 
100     @VisibleForTesting
101     List<ResetStateListener> mResetStateListeners;
102 
103     @VisibleForTesting
104     boolean mNeedResetSettings;
105 
106     @Override
onCreate(Bundle savedInstanceState)107     public void onCreate(Bundle savedInstanceState) {
108         super.onCreate(savedInstanceState);
109 
110         mNeedResetSettings = false;
111         mResetStateListeners = getResetStateListeners();
112 
113         if (savedInstanceState != null) {
114             if (savedInstanceState.getBoolean(NEED_RESET_SETTINGS)) {
115                 mResetStateListeners.forEach(ResetStateListener::resetState);
116             }
117         }
118     }
119 
120     @Override
onActivityCreated(Bundle savedInstanceState)121     public void onActivityCreated(Bundle savedInstanceState) {
122         super.onActivityCreated(savedInstanceState);
123         final View rootView = getActivity().getWindow().peekDecorView();
124         if (rootView != null) {
125             rootView.setAccessibilityPaneTitle(getString(
126                     R.string.accessibility_text_reading_options_title));
127         }
128         if (Flags.highContrastTextSmallTextRect()) {
129             updateEntryPoint();
130             if (mEntryPoint == EntryPoint.HIGH_CONTRAST_TEXT_NOTIFICATION
131                     // Only log this counter during the first launch, not during activity refresh
132                     && savedInstanceState == null) {
133                 Counter.logIncrement("accessibility.value_hct_notification_opened_settings");
134             }
135         }
136     }
137 
138     @Override
getPreferenceScreenResId()139     protected int getPreferenceScreenResId() {
140         return R.xml.accessibility_text_reading_options;
141     }
142 
143     @Override
getLogTag()144     protected String getLogTag() {
145         return TAG;
146     }
147 
148     @Override
getMetricsCategory()149     public int getMetricsCategory() {
150         return SettingsEnums.ACCESSIBILITY_TEXT_READING_OPTIONS;
151     }
152 
153     @Override
createPreferenceControllers(Context context)154     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
155         updateEntryPoint();
156 
157         final List<AbstractPreferenceController> controllers = new ArrayList<>();
158         final FontSizeData fontSizeData = new FontSizeData(context);
159         final DisplaySizeData displaySizeData = createDisplaySizeData(context);
160 
161         mPreviewController = new TextReadingPreviewController(context, PREVIEW_KEY, fontSizeData,
162                 displaySizeData);
163         mPreviewController.setEntryPoint(mEntryPoint);
164         controllers.add(mPreviewController);
165 
166         final PreviewSizeSeekBarController fontSizeController = new PreviewSizeSeekBarController(
167                 context, FONT_SIZE_KEY, fontSizeData) {
168             @Override
169             ComponentName getTileComponentName() {
170                 return FONT_SIZE_COMPONENT_NAME;
171             }
172 
173             @Override
174             CharSequence getTileTooltipContent() {
175                 return context.getText(
176                         R.string.accessibility_font_scaling_auto_added_qs_tooltip_content);
177             }
178         };
179         final String[] labelArray = new String[fontSizeData.getValues().size()];
180         for (int i = 0; i < labelArray.length; i++) {
181             labelArray[i] =
182                     context.getResources().getString(
183                             com.android.settingslib.R.string.font_scale_percentage,
184                             (int) (fontSizeData.getValues().get(i) * 100)
185                     );
186         }
187         fontSizeController.setProgressStateLabels(labelArray);
188         fontSizeController.setInteractionListener(mPreviewController);
189         getSettingsLifecycle().addObserver(fontSizeController);
190         controllers.add(fontSizeController);
191 
192         final PreviewSizeSeekBarController displaySizeController = new PreviewSizeSeekBarController(
193                 context, DISPLAY_SIZE_KEY, displaySizeData) {
194             @Override
195             ComponentName getTileComponentName() {
196                 return null;
197             }
198 
199             @Override
200             CharSequence getTileTooltipContent() {
201                 return null;
202             }
203         };
204         displaySizeController.setInteractionListener(mPreviewController);
205         controllers.add(displaySizeController);
206 
207         mFontWeightAdjustmentController =
208                 new FontWeightAdjustmentPreferenceController(context, BOLD_TEXT_KEY);
209         mFontWeightAdjustmentController.setEntryPoint(mEntryPoint);
210         controllers.add(mFontWeightAdjustmentController);
211 
212         final HighTextContrastPreferenceController highTextContrastController =
213                 new HighTextContrastPreferenceController(context, HIGH_TEXT_CONTRAST_KEY);
214         highTextContrastController.setEntryPoint(mEntryPoint);
215         controllers.add(highTextContrastController);
216 
217         final TextReadingResetController resetController =
218                 new TextReadingResetController(context, RESET_KEY,
219                         v -> showDialog(DialogEnums.DIALOG_RESET_SETTINGS));
220         resetController.setEntryPoint(mEntryPoint);
221         resetController.setVisible(!WizardManagerHelper.isAnySetupWizard(getIntent()));
222         controllers.add(resetController);
223 
224         return controllers;
225     }
226 
227     @Override
onCreateDialog(int dialogId)228     public Dialog onCreateDialog(int dialogId) {
229         if (dialogId == DialogEnums.DIALOG_RESET_SETTINGS) {
230             return new AlertDialog.Builder(getPrefContext())
231                     .setTitle(R.string.accessibility_text_reading_confirm_dialog_title)
232                     .setMessage(R.string.accessibility_text_reading_confirm_dialog_message)
233                     .setPositiveButton(
234                             R.string.accessibility_text_reading_confirm_dialog_reset_button,
235                             this::onPositiveButtonClicked)
236                     .setNegativeButton(R.string.cancel, /* listener= */ null)
237                     .create();
238         }
239 
240         throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
241     }
242 
243     @Override
getDialogMetricsCategory(int dialogId)244     public int getDialogMetricsCategory(int dialogId) {
245         if (dialogId == DialogEnums.DIALOG_RESET_SETTINGS) {
246             return SettingsEnums.DIALOG_RESET_SETTINGS;
247         }
248 
249         return super.getDialogMetricsCategory(dialogId);
250     }
251 
252     @Override
onSaveInstanceState(Bundle outState)253     public void onSaveInstanceState(Bundle outState) {
254         super.onSaveInstanceState(outState);
255 
256         if (mNeedResetSettings) {
257             outState.putBoolean(NEED_RESET_SETTINGS, true);
258         }
259     }
260 
261     @Override
onStart()262     public void onStart() {
263         super.onStart();
264     }
265 
isCallingFromAnythingElseEntryPoint()266     protected boolean isCallingFromAnythingElseEntryPoint() {
267         final Activity activity = getActivity();
268         final String callingPackage = activity != null ? activity.getCallingPackage() : null;
269 
270         return callingPackage != null && callingPackage.contains(SETUP_WIZARD_PACKAGE);
271     }
272 
273     @VisibleForTesting
createDisplaySizeData(Context context)274     DisplaySizeData createDisplaySizeData(Context context) {
275         return new DisplaySizeData(context);
276     }
277 
updateEntryPoint()278     private void updateEntryPoint() {
279         final Bundle bundle = getArguments();
280         if (bundle != null && bundle.containsKey(EXTRA_LAUNCHED_FROM)) {
281             mEntryPoint = bundle.getInt(EXTRA_LAUNCHED_FROM, EntryPoint.UNKNOWN_ENTRY);
282             return;
283         }
284 
285         mEntryPoint = isCallingFromAnythingElseEntryPoint()
286                 ? EntryPoint.SUW_ANYTHING_ELSE : EntryPoint.UNKNOWN_ENTRY;
287     }
288 
onPositiveButtonClicked(DialogInterface dialog, int which)289     private void onPositiveButtonClicked(DialogInterface dialog, int which) {
290         // To avoid showing the dialog again, probably the onDetach() of SettingsDialogFragment
291         // was interrupted by unexpectedly recreating the activity.
292         removeDialog(DialogEnums.DIALOG_RESET_SETTINGS);
293 
294         if (mFontWeightAdjustmentController.isChecked()) {
295             // TODO(b/228956791): Consider replacing or removing it once the root cause is
296             //  clarified and the better method is available.
297             // Probably has the race condition issue between "Bold text" and  the other features
298             // including "Display Size", “Font Size” if they would be enabled at the same time,
299             // so our workaround is that the “Bold text” would be reset first and then do the
300             // remaining to avoid flickering problem.
301             mNeedResetSettings = true;
302             mFontWeightAdjustmentController.resetState();
303         } else {
304             mResetStateListeners.forEach(ResetStateListener::resetState);
305         }
306 
307         Toast.makeText(getPrefContext(), R.string.accessibility_text_reading_reset_message,
308                 Toast.LENGTH_SHORT).show();
309     }
310 
getResetStateListeners()311     private List<ResetStateListener> getResetStateListeners() {
312         final List<AbstractPreferenceController> controllers = new ArrayList<>();
313         getPreferenceControllers().forEach(controllers::addAll);
314         return controllers.stream().filter(c -> c instanceof ResetStateListener).map(
315                 c -> (ResetStateListener) c).collect(Collectors.toList());
316     }
317 
318     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
319             new BaseSearchIndexProvider(R.xml.accessibility_text_reading_options);
320 
321     @Override
getPreferenceScreenBindingKey(@onNull Context context)322     public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
323         return TextReadingScreen.KEY;
324     }
325 }
326