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 android.content.Context;
20 import android.content.DialogInterface;
21 import android.content.res.TypedArray;
22 import android.graphics.drawable.Drawable;
23 import android.text.Spannable;
24 import android.text.SpannableString;
25 import android.text.TextUtils;
26 import android.text.style.ImageSpan;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.widget.CheckBox;
30 import android.widget.ImageView;
31 import android.widget.LinearLayout;
32 import android.widget.ScrollView;
33 import android.widget.TextView;
34 
35 import androidx.annotation.ColorInt;
36 import androidx.annotation.IntDef;
37 import androidx.appcompat.app.AlertDialog;
38 import androidx.core.content.ContextCompat;
39 
40 import com.android.settings.R;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 
45 /**
46  * Utility class for creating the edit dialog.
47  */
48 public class AccessibilityEditDialogUtils {
49 
50     /**
51      * IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
52      * type.
53      */
54     @Retention(RetentionPolicy.SOURCE)
55     @IntDef({
56          DialogType.EDIT_SHORTCUT_GENERIC,
57          DialogType.EDIT_SHORTCUT_MAGNIFICATION,
58          DialogType.EDIT_MAGNIFICATION_MODE,
59     })
60 
61     private @interface DialogType {
62         int EDIT_SHORTCUT_GENERIC = 0;
63         int EDIT_SHORTCUT_MAGNIFICATION = 1;
64         int EDIT_MAGNIFICATION_MODE = 2;
65     }
66 
67     /**
68      * Method to show the edit shortcut dialog.
69      *
70      * @param context A valid context
71      * @param dialogTitle The title of edit shortcut dialog
72      * @param listener The listener to determine the action of edit shortcut dialog
73      * @return A edit shortcut dialog for showing
74      */
showEditShortcutDialog(Context context, CharSequence dialogTitle, DialogInterface.OnClickListener listener)75     public static AlertDialog showEditShortcutDialog(Context context, CharSequence dialogTitle,
76             DialogInterface.OnClickListener listener) {
77         final AlertDialog alertDialog = createDialog(context, DialogType.EDIT_SHORTCUT_GENERIC,
78                 dialogTitle, listener);
79         alertDialog.show();
80         setScrollIndicators(alertDialog);
81         return alertDialog;
82     }
83 
84     /**
85      * Method to show the edit shortcut dialog in Magnification.
86      *
87      * @param context A valid context
88      * @param dialogTitle The title of edit shortcut dialog
89      * @param listener The listener to determine the action of edit shortcut dialog
90      * @return A edit shortcut dialog for showing in Magnification
91      */
showMagnificationEditShortcutDialog(Context context, CharSequence dialogTitle, DialogInterface.OnClickListener listener)92     public static AlertDialog showMagnificationEditShortcutDialog(Context context,
93             CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
94         final AlertDialog alertDialog = createDialog(context,
95                 DialogType.EDIT_SHORTCUT_MAGNIFICATION, dialogTitle, listener);
96         alertDialog.show();
97         setScrollIndicators(alertDialog);
98         return alertDialog;
99     }
100 
101     /**
102      * Method to show the magnification mode dialog in Magnification.
103      *
104      * @param context A valid context
105      * @param dialogTitle The title of magnify mode dialog
106      * @param listener The listener to determine the action of magnify mode dialog
107      * @return A magnification mode dialog in Magnification
108      */
showMagnificationModeDialog(Context context, CharSequence dialogTitle, DialogInterface.OnClickListener listener)109     public static AlertDialog showMagnificationModeDialog(Context context,
110             CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
111         final AlertDialog alertDialog = createDialog(context,
112                 DialogType.EDIT_MAGNIFICATION_MODE, dialogTitle, listener);
113         alertDialog.show();
114         setScrollIndicators(alertDialog);
115         return alertDialog;
116     }
117 
createDialog(Context context, int dialogType, CharSequence dialogTitle, DialogInterface.OnClickListener listener)118     private static AlertDialog createDialog(Context context, int dialogType,
119             CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
120 
121         final AlertDialog alertDialog = new AlertDialog.Builder(context)
122                 .setView(createEditDialogContentView(context, dialogType))
123                 .setTitle(dialogTitle)
124                 .setPositiveButton(R.string.save, listener)
125                 .setNegativeButton(R.string.cancel,
126                         (DialogInterface dialog, int which) -> dialog.dismiss())
127                 .create();
128 
129         return alertDialog;
130     }
131 
132     /**
133      * Sets the scroll indicators for dialog view. The indicators appears while content view is
134      * out of vision for vertical scrolling.
135      */
setScrollIndicators(AlertDialog dialog)136     private static void setScrollIndicators(AlertDialog dialog) {
137         final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
138         scrollView.setScrollIndicators(
139                 View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM,
140                 View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
141     }
142 
143     /**
144      * Get a content View for the edit shortcut dialog.
145      *
146      * @param context A valid context
147      * @param dialogType The type of edit shortcut dialog
148      * @return A content view suitable for viewing
149      */
createEditDialogContentView(Context context, int dialogType)150     private static View createEditDialogContentView(Context context, int dialogType) {
151         final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
152                 Context.LAYOUT_INFLATER_SERVICE);
153 
154         View contentView = null;
155 
156         switch (dialogType) {
157             case DialogType.EDIT_SHORTCUT_GENERIC:
158                 contentView = inflater.inflate(
159                         R.layout.accessibility_edit_shortcut, null);
160                 initSoftwareShortcut(context, contentView);
161                 initHardwareShortcut(context, contentView);
162                 break;
163             case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
164                 contentView = inflater.inflate(
165                         R.layout.accessibility_edit_shortcut_magnification, null);
166                 initSoftwareShortcut(context, contentView);
167                 initHardwareShortcut(context, contentView);
168                 initMagnifyShortcut(context, contentView);
169                 initAdvancedWidget(contentView);
170                 break;
171             case DialogType.EDIT_MAGNIFICATION_MODE:
172                 contentView = inflater.inflate(
173                         R.layout.accessibility_edit_magnification_mode, null);
174                 initMagnifyFullScreen(context, contentView);
175                 initMagnifyWindowScreen(context, contentView);
176                 break;
177             default:
178                 throw new IllegalArgumentException();
179         }
180 
181         return contentView;
182     }
183 
initMagnifyFullScreen(Context context, View view)184     private static void initMagnifyFullScreen(Context context, View view) {
185         final View dialogView = view.findViewById(R.id.magnify_full_screen);
186         final CharSequence title = context.getText(
187                 R.string.accessibility_magnification_area_settings_full_screen);
188         // TODO(b/146019459): Use vector drawable instead of temporal png file to avoid distorted.
189         setupShortcutWidget(dialogView, title, R.drawable.accessibility_magnification_full_screen);
190     }
191 
initMagnifyWindowScreen(Context context, View view)192     private static void initMagnifyWindowScreen(Context context, View view) {
193         final View dialogView = view.findViewById(R.id.magnify_window_screen);
194         final CharSequence title = context.getText(
195                 R.string.accessibility_magnification_area_settings_window_screen);
196         // TODO(b/146019459): Use vector drawable instead of temporal png file to avoid distorted.
197         setupShortcutWidget(dialogView, title,
198                 R.drawable.accessibility_magnification_window_screen);
199     }
200 
setupShortcutWidget(View view, CharSequence titleText, int imageResId)201     private static void setupShortcutWidget(View view, CharSequence titleText, int imageResId) {
202         setupShortcutWidget(view, titleText, null, imageResId);
203     }
204 
setupShortcutWidget(View view, CharSequence titleText, CharSequence summaryText, int imageResId)205     private static void setupShortcutWidget(View view, CharSequence titleText,
206             CharSequence summaryText, int imageResId) {
207         final CheckBox checkBox = view.findViewById(R.id.checkbox);
208         checkBox.setText(titleText);
209         final TextView summary = view.findViewById(R.id.summary);
210         if (TextUtils.isEmpty(summaryText)) {
211             summary.setVisibility(View.GONE);
212         } else {
213             summary.setText(summaryText);
214         }
215         final ImageView image = view.findViewById(R.id.image);
216         image.setImageResource(imageResId);
217     }
218 
initSoftwareShortcut(Context context, View view)219     private static void initSoftwareShortcut(Context context, View view) {
220         final View dialogView = view.findViewById(R.id.software_shortcut);
221         final TextView summary = dialogView.findViewById(R.id.summary);
222         final int lineHeight = summary.getLineHeight();
223         setupShortcutWidget(dialogView, retrieveTitle(context),
224                 retrieveSummary(context, lineHeight), retrieveImageResId(context));
225     }
226 
initHardwareShortcut(Context context, View view)227     private static void initHardwareShortcut(Context context, View view) {
228         final View dialogView = view.findViewById(R.id.hardware_shortcut);
229         final CharSequence title = context.getText(
230                 R.string.accessibility_shortcut_edit_dialog_title_hardware);
231         final CharSequence summary = context.getText(
232                 R.string.accessibility_shortcut_edit_dialog_summary_hardware);
233         setupShortcutWidget(dialogView, title, summary,
234                 R.drawable.accessibility_shortcut_type_hardware);
235         // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
236     }
237 
initMagnifyShortcut(Context context, View view)238     private static void initMagnifyShortcut(Context context, View view) {
239         final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
240         final CharSequence title = context.getText(
241                 R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
242         final CharSequence summary = context.getText(
243                 R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
244         setupShortcutWidget(dialogView, title, summary,
245                 R.drawable.accessibility_shortcut_type_triple_tap);
246         // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
247     }
248 
initAdvancedWidget(View view)249     private static void initAdvancedWidget(View view) {
250         final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
251         final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
252         advanced.setOnClickListener((View v) -> {
253             advanced.setVisibility(View.GONE);
254             tripleTap.setVisibility(View.VISIBLE);
255         });
256     }
257 
retrieveTitle(Context context)258     private static CharSequence retrieveTitle(Context context) {
259         int resId = R.string.accessibility_shortcut_edit_dialog_title_software;
260         if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
261             resId = AccessibilityUtil.isTouchExploreEnabled(context)
262                     ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback
263                     : R.string.accessibility_shortcut_edit_dialog_title_software_gesture;
264         }
265         return context.getText(resId);
266     }
267 
retrieveSummary(Context context, int lineHeight)268     private static CharSequence retrieveSummary(Context context, int lineHeight) {
269         if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
270             final int resId = AccessibilityUtil.isTouchExploreEnabled(context)
271                     ? R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback
272                     : R.string.accessibility_shortcut_edit_dialog_summary_software_gesture;
273             return context.getText(resId);
274         }
275         return getSummaryStringWithIcon(context, lineHeight);
276     }
277 
retrieveImageResId(Context context)278     private static int retrieveImageResId(Context context) {
279         // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
280         int resId = R.drawable.accessibility_shortcut_type_software;
281         if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
282             resId = AccessibilityUtil.isTouchExploreEnabled(context)
283                     ? R.drawable.accessibility_shortcut_type_software_gesture_talkback
284                     : R.drawable.accessibility_shortcut_type_software_gesture;
285         }
286         return resId;
287     }
288 
getSummaryStringWithIcon(Context context, int lineHeight)289     private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
290         final String summary = context
291                 .getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
292         final SpannableString spannableMessage = SpannableString.valueOf(summary);
293 
294         // Icon
295         final int indexIconStart = summary.indexOf("%s");
296         final int indexIconEnd = indexIconStart + 2;
297         final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
298         final ImageSpan imageSpan = new ImageSpan(icon);
299         imageSpan.setContentDescription("");
300         icon.setBounds(0, 0, lineHeight, lineHeight);
301         spannableMessage.setSpan(
302                 imageSpan, indexIconStart, indexIconEnd,
303                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
304 
305         return spannableMessage;
306     }
307 
308     /**
309      * Returns the color associated with the specified attribute in the context's theme.
310      */
311     @ColorInt
getThemeAttrColor(final Context context, final int attributeColor)312     private static int getThemeAttrColor(final Context context, final int attributeColor) {
313         final int colorResId = getAttrResourceId(context, attributeColor);
314         return ContextCompat.getColor(context, colorResId);
315     }
316 
317     /**
318      * Returns the identifier of the resolved resource assigned to the given attribute.
319      */
getAttrResourceId(final Context context, final int attributeColor)320     private static int getAttrResourceId(final Context context, final int attributeColor) {
321         final int[] attrs = {attributeColor};
322         final TypedArray typedArray = context.obtainStyledAttributes(attrs);
323         final int colorResId = typedArray.getResourceId(0, 0);
324         typedArray.recycle();
325         return colorResId;
326     }
327 }
328