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