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