• 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.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
20 import static com.android.settings.accessibility.AccessibilityDialogUtils.CustomButton;
21 import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
22 import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
23 
24 import android.app.Dialog;
25 import android.app.settings.SettingsEnums;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.os.Bundle;
29 import android.provider.Settings;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.widget.AdapterView;
35 import android.widget.ListView;
36 
37 import androidx.annotation.DrawableRes;
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.VisibleForTesting;
41 import androidx.preference.Preference;
42 import androidx.preference.PreferenceScreen;
43 
44 import com.android.settings.DialogCreatable;
45 import com.android.settings.R;
46 import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode;
47 import com.android.settings.core.BasePreferenceController;
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 import java.util.StringJoiner;
56 
57 /** Controller that shows the magnification area mode summary and the preference click behavior. */
58 public class MagnificationModePreferenceController extends BasePreferenceController implements
59         DialogCreatable, LifecycleObserver, OnCreate, OnResume, OnSaveInstanceState {
60 
61     static final String PREF_KEY = "screen_magnification_mode";
62     private static final int DIALOG_ID_BASE = 10;
63     @VisibleForTesting
64     static final int DIALOG_MAGNIFICATION_MODE = DIALOG_ID_BASE + 1;
65     @VisibleForTesting
66     static final int DIALOG_MAGNIFICATION_SWITCH_SHORTCUT = DIALOG_ID_BASE + 2;
67     @VisibleForTesting
68     static final String EXTRA_MODE = "mode";
69 
70     private static final String TAG = "MagnificationModePreferenceController";
71     private static final char COMPONENT_NAME_SEPARATOR = ':';
72 
73     private DialogHelper mDialogHelper;
74     // The magnification mode in the dialog.
75     private int mMode = MagnificationMode.NONE;
76     private Preference mModePreference;
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.ic_illustration_fullscreen, MagnificationMode.FULLSCREEN));
92         mModeInfos.add(new MagnificationModeInfo(
93                 mContext.getText(R.string.accessibility_magnification_mode_dialog_option_window),
94                 null, R.drawable.ic_illustration_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.ic_illustration_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             mMode = 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         mModePreference.setOnPreferenceClickListener(preference -> {
125             mMode = MagnificationCapabilities.getCapabilities(mContext);
126             mDialogHelper.showDialog(DIALOG_MAGNIFICATION_MODE);
127             return true;
128         });
129     }
130 
131     @Override
onSaveInstanceState(Bundle outState)132     public void onSaveInstanceState(Bundle outState) {
133         outState.putInt(EXTRA_MODE, mMode);
134     }
135 
136     /**
137      * Sets {@link DialogHelper} used to show the dialog.
138      */
setDialogHelper(DialogHelper dialogHelper)139     public void setDialogHelper(DialogHelper dialogHelper) {
140         mDialogHelper = dialogHelper;
141         mDialogHelper.setDialogDelegate(this);
142     }
143 
144     @Override
onCreateDialog(int dialogId)145     public Dialog onCreateDialog(int dialogId) {
146         switch (dialogId) {
147             case DIALOG_MAGNIFICATION_MODE:
148                 return createMagnificationModeDialog();
149 
150             case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT:
151                 return createMagnificationShortCutConfirmDialog();
152         }
153         return null;
154     }
155 
156     @Override
getDialogMetricsCategory(int dialogId)157     public int getDialogMetricsCategory(int dialogId) {
158         switch (dialogId) {
159             case DIALOG_MAGNIFICATION_MODE:
160                 return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY;
161             case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT:
162                 return SettingsEnums.DIALOG_MAGNIFICATION_SWITCH_SHORTCUT;
163             default:
164                 return 0;
165         }
166     }
167 
createMagnificationModeDialog()168     private Dialog createMagnificationModeDialog() {
169         mMagnificationModesListView = AccessibilityDialogUtils.createSingleChoiceListView(
170                 mContext, mModeInfos, this::onMagnificationModeSelected);
171 
172         final View headerView = LayoutInflater.from(mContext).inflate(
173                 R.layout.accessibility_magnification_mode_header, mMagnificationModesListView,
174                 false);
175         mMagnificationModesListView.addHeaderView(headerView, /* data= */ null, /* isSelectable= */
176                 false);
177 
178         mMagnificationModesListView.setItemChecked(computeSelectionIndex(), true);
179         final CharSequence title = mContext.getString(
180                 R.string.accessibility_magnification_mode_dialog_title);
181 
182         return AccessibilityDialogUtils.createCustomDialog(mContext, title,
183                 mMagnificationModesListView, this::onMagnificationModeDialogPositiveButtonClicked);
184     }
185 
onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface, int which)186     private void onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface,
187             int which) {
188         final int selectedIndex = mMagnificationModesListView.getCheckedItemPosition();
189         if (selectedIndex != AdapterView.INVALID_POSITION) {
190             final MagnificationModeInfo modeInfo =
191                     (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition(
192                             selectedIndex);
193             setMode(modeInfo.mMagnificationMode);
194         } else {
195             Log.w(TAG, "invalid index");
196         }
197     }
198 
setMode(int mode)199     private void setMode(int mode) {
200         mMode = mode;
201         MagnificationCapabilities.setCapabilities(mContext, mMode);
202         mModePreference.setSummary(
203                 MagnificationCapabilities.getSummary(mContext, mMode));
204     }
205 
onMagnificationModeSelected(AdapterView<?> parent, View view, int position, long id)206     private void onMagnificationModeSelected(AdapterView<?> parent, View view, int position,
207             long id) {
208         final MagnificationModeInfo modeInfo =
209                 (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition(
210                         position);
211         if (modeInfo.mMagnificationMode == mMode) {
212             return;
213         }
214         mMode = modeInfo.mMagnificationMode;
215         if (isTripleTapEnabled(mContext) && mMode != MagnificationMode.FULLSCREEN) {
216             mDialogHelper.showDialog(DIALOG_MAGNIFICATION_SWITCH_SHORTCUT);
217         }
218     }
219 
computeSelectionIndex()220     private int computeSelectionIndex() {
221         final int modesSize = mModeInfos.size();
222         for (int i = 0; i < modesSize; i++) {
223             if (mModeInfos.get(i).mMagnificationMode == mMode) {
224                 return i + mMagnificationModesListView.getHeaderViewsCount();
225             }
226         }
227         Log.w(TAG, "computeSelectionIndex failed");
228         return 0;
229     }
230 
231     @VisibleForTesting
isTripleTapEnabled(Context context)232     static boolean isTripleTapEnabled(Context context) {
233         return Settings.Secure.getInt(context.getContentResolver(),
234                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON;
235     }
236 
createMagnificationShortCutConfirmDialog()237     private Dialog createMagnificationShortCutConfirmDialog() {
238         return AccessibilityDialogUtils.createMagnificationSwitchShortcutDialog(mContext,
239                 this::onSwitchShortcutDialogButtonClicked);
240     }
241 
242     @VisibleForTesting
onSwitchShortcutDialogButtonClicked(@ustomButton int which)243     void onSwitchShortcutDialogButtonClicked(@CustomButton int which) {
244         optOutMagnificationFromTripleTap();
245         //TODO(b/147990389): Merge this function into AccessibilityUtils after the format of
246         // magnification target is changed to ComponentName.
247         optInMagnificationToAccessibilityButton();
248     }
249 
optOutMagnificationFromTripleTap()250     private void optOutMagnificationFromTripleTap() {
251         Settings.Secure.putInt(mContext.getContentResolver(),
252                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF);
253     }
254 
optInMagnificationToAccessibilityButton()255     private void optInMagnificationToAccessibilityButton() {
256         final String targetKey = Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
257         final String targetString = Settings.Secure.getString(mContext.getContentResolver(),
258                 targetKey);
259         if (targetString != null && targetString.contains(MAGNIFICATION_CONTROLLER_NAME)) {
260             return;
261         }
262 
263         final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
264 
265         if (!TextUtils.isEmpty(targetString)) {
266             joiner.add(targetString);
267         }
268         joiner.add(MAGNIFICATION_CONTROLLER_NAME);
269 
270         Settings.Secure.putString(mContext.getContentResolver(), targetKey,
271                 joiner.toString());
272     }
273 
274     // TODO(b/186731461): Remove it when this controller is used in DashBoardFragment only.
275     @Override
onResume()276     public void onResume() {
277         updateState(mModePreference);
278     }
279 
280 
281     /**
282      * An interface to help the delegate to show the dialog. It will be injected to the delegate.
283      */
284     interface DialogHelper extends DialogCreatable {
showDialog(int dialogId)285         void showDialog(int dialogId);
setDialogDelegate(DialogCreatable delegate)286         void setDialogDelegate(DialogCreatable delegate);
287     }
288 
289     @VisibleForTesting
290     static class MagnificationModeInfo extends ItemInfoArrayAdapter.ItemInfo {
291         @MagnificationMode
292         public final int mMagnificationMode;
293 
MagnificationModeInfo(@onNull CharSequence title, @Nullable CharSequence summary, @DrawableRes int drawableId, @MagnificationMode int magnificationMode)294         MagnificationModeInfo(@NonNull CharSequence title, @Nullable CharSequence summary,
295                 @DrawableRes int drawableId, @MagnificationMode int magnificationMode) {
296             super(title, summary, drawableId);
297             mMagnificationMode = magnificationMode;
298         }
299     }
300 }
301