• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 package com.android.settings.accessibility;
17 
18 import android.app.Dialog;
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.provider.Settings;
23 import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode;
24 import android.text.TextUtils;
25 import android.util.Log;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.widget.AdapterView;
29 import android.widget.ListView;
30 import android.widget.TextView;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 import androidx.core.util.Preconditions;
36 import androidx.preference.Preference;
37 import androidx.preference.PreferenceScreen;
38 
39 import com.android.settings.DialogCreatable;
40 import com.android.settings.R;
41 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
42 import com.android.settings.core.BasePreferenceController;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * Controller that shows the magnification cursor following mode and the preference click behavior.
49  */
50 public class MagnificationCursorFollowingModePreferenceController extends
51         BasePreferenceController implements DialogCreatable {
52     static final String PREF_KEY =
53             Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
54 
55     private static final String TAG =
56             MagnificationCursorFollowingModePreferenceController.class.getSimpleName();
57 
58     private final List<ModeInfo> mModeList = new ArrayList<>();
59     @Nullable
60     private DialogHelper mDialogHelper;
61     @VisibleForTesting
62     @Nullable
63     ListView mModeListView;
64     @Nullable
65     private Preference mModePreference;
66 
MagnificationCursorFollowingModePreferenceController(@onNull Context context, @NonNull String preferenceKey)67     public MagnificationCursorFollowingModePreferenceController(@NonNull Context context,
68             @NonNull String preferenceKey) {
69         super(context, preferenceKey);
70         initModeList();
71     }
72 
setDialogHelper(@onNull DialogHelper dialogHelper)73     public void setDialogHelper(@NonNull DialogHelper dialogHelper) {
74         mDialogHelper = dialogHelper;
75     }
76 
initModeList()77     private void initModeList() {
78         mModeList.add(new ModeInfo(mContext.getString(
79                 R.string.accessibility_magnification_cursor_following_continuous),
80                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS));
81         mModeList.add(new ModeInfo(
82                 mContext.getString(R.string.accessibility_magnification_cursor_following_center),
83                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER));
84         mModeList.add(new ModeInfo(
85                 mContext.getString(R.string.accessibility_magnification_cursor_following_edge),
86                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE));
87     }
88 
89     @Override
getAvailabilityStatus()90     public int getAvailabilityStatus() {
91         return AVAILABLE;
92     }
93 
94     @NonNull
95     @Override
getSummary()96     public CharSequence getSummary() {
97         return getCursorFollowingModeSummary(getCurrentMagnificationCursorFollowingMode());
98     }
99 
100     @Override
displayPreference(@onNull PreferenceScreen screen)101     public void displayPreference(@NonNull PreferenceScreen screen) {
102         super.displayPreference(screen);
103         mModePreference = screen.findPreference(getPreferenceKey());
104     }
105 
106     @Override
handlePreferenceTreeClick(@onNull Preference preference)107     public boolean handlePreferenceTreeClick(@NonNull Preference preference) {
108         if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) || mModePreference == null) {
109             return super.handlePreferenceTreeClick(preference);
110         }
111 
112         Preconditions.checkNotNull(mDialogHelper).showDialog(
113                     DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE);
114         return true;
115     }
116 
117     @NonNull
118     @Override
onCreateDialog(int dialogId)119     public Dialog onCreateDialog(int dialogId) {
120         Preconditions.checkArgument(
121                 dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
122                 "This only handles cursor following mode dialog");
123         return createMagnificationCursorFollowingModeDialog();
124     }
125 
126     @Override
getDialogMetricsCategory(int dialogId)127     public int getDialogMetricsCategory(int dialogId) {
128         Preconditions.checkArgument(
129                 dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
130                 "This only handles cursor following mode dialog");
131         return SettingsEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING;
132     }
133 
134     @NonNull
createMagnificationCursorFollowingModeDialog()135     private Dialog createMagnificationCursorFollowingModeDialog() {
136         mModeListView = AccessibilityDialogUtils.createSingleChoiceListView(mContext, mModeList,
137                 /* itemListener= */null);
138         final View headerView = LayoutInflater.from(mContext).inflate(
139                 R.layout.accessibility_dialog_header, mModeListView,
140                 /* attachToRoot= */false);
141         final TextView textView = Preconditions.checkNotNull(headerView.findViewById(
142                 R.id.accessibility_dialog_header_text_view));
143         textView.setText(
144                 mContext.getString(R.string.accessibility_magnification_cursor_following_header));
145         textView.setVisibility(View.VISIBLE);
146         mModeListView.addHeaderView(headerView, /* data= */null, /* isSelectable= */false);
147         final int selectionIndex = computeSelectionIndex();
148         if (selectionIndex != AdapterView.INVALID_POSITION) {
149             mModeListView.setItemChecked(selectionIndex, /* value= */true);
150         }
151         final CharSequence title = mContext.getString(
152                 R.string.accessibility_magnification_cursor_following_title);
153         final CharSequence positiveBtnText = mContext.getString(R.string.save);
154         final CharSequence negativeBtnText = mContext.getString(R.string.cancel);
155         return AccessibilityDialogUtils.createCustomDialog(mContext, title, mModeListView,
156                 positiveBtnText,
157                 this::onMagnificationCursorFollowingModeDialogPositiveButtonClicked,
158                 negativeBtnText, /* negativeListener= */null);
159     }
160 
onMagnificationCursorFollowingModeDialogPositiveButtonClicked( DialogInterface dialogInterface, int which)161     void onMagnificationCursorFollowingModeDialogPositiveButtonClicked(
162             DialogInterface dialogInterface, int which) {
163         ListView listView = Preconditions.checkNotNull(mModeListView);
164         final int selectionIndex = listView.getCheckedItemPosition();
165         if (selectionIndex == AdapterView.INVALID_POSITION) {
166             Log.w(TAG, "Selected positive button with INVALID_POSITION index");
167             return;
168         }
169         ModeInfo cursorFollowingMode = (ModeInfo) listView.getItemAtPosition(selectionIndex);
170         if (cursorFollowingMode != null) {
171             Preconditions.checkNotNull(mModePreference).setSummary(
172                     getCursorFollowingModeSummary(cursorFollowingMode.mMode));
173             Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY,
174                     cursorFollowingMode.mMode);
175         }
176     }
177 
computeSelectionIndex()178     private int computeSelectionIndex() {
179         ListView listView = Preconditions.checkNotNull(mModeListView);
180         @AccessibilityMagnificationCursorFollowingMode
181         final int currentMode = getCurrentMagnificationCursorFollowingMode();
182         for (int i = 0; i < listView.getCount(); i++) {
183             final ModeInfo mode = (ModeInfo) listView.getItemAtPosition(i);
184             if (mode != null && mode.mMode == currentMode) {
185                 return i;
186             }
187         }
188         return AdapterView.INVALID_POSITION;
189     }
190 
191     @NonNull
getCursorFollowingModeSummary( @ccessibilityMagnificationCursorFollowingMode int cursorFollowingMode)192     private CharSequence getCursorFollowingModeSummary(
193             @AccessibilityMagnificationCursorFollowingMode int cursorFollowingMode) {
194         int stringId = switch (cursorFollowingMode) {
195             case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER ->
196                     R.string.accessibility_magnification_cursor_following_center;
197             case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE ->
198                     R.string.accessibility_magnification_cursor_following_edge;
199             default ->
200                     R.string.accessibility_magnification_cursor_following_continuous;
201         };
202         return mContext.getString(stringId);
203     }
204 
205     private @AccessibilityMagnificationCursorFollowingMode int
getCurrentMagnificationCursorFollowingMode()206             getCurrentMagnificationCursorFollowingMode() {
207         return Settings.Secure.getInt(mContext.getContentResolver(), PREF_KEY,
208                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
209     }
210 
211     static class ModeInfo extends ItemInfoArrayAdapter.ItemInfo {
212         @AccessibilityMagnificationCursorFollowingMode
213         public final int mMode;
214 
ModeInfo(@onNull CharSequence title, @AccessibilityMagnificationCursorFollowingMode int mode)215         ModeInfo(@NonNull CharSequence title,
216                 @AccessibilityMagnificationCursorFollowingMode int mode) {
217             super(title, /* summary= */null, /* drawableId= */null);
218             mMode = mode;
219         }
220     }
221 }
222