Files
packages_apps_Settings/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
menghanli f07aa0fa90 Add back the shortcut dialog content for accessibility gesture
Below components are updated.
1. The dialog title, content, and customized link when edit shortcut.
2. The shortcut preference summary in Setting page.
3. The tutorial title and content when shortcut on.

Partial rollback from commit 5fe2cdcf87

Bug: 190563948
Test: Manual test
Change-Id: I5e39b46c9387b4bf5a10f14453be9988d8b7fc9c
2021-07-06 01:05:11 +00:00

543 lines
22 KiB
Java

/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.ItemInfoArrayAdapter.ItemInfo;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ImageSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.utils.AnnotationSpan;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Utility class for creating the edit dialog.
*/
public class AccessibilityDialogUtils {
/** Denotes the dialog emuns for show dialog. */
@Retention(RetentionPolicy.SOURCE)
public @interface DialogEnums {
/** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */
int EDIT_SHORTCUT = 1;
/** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */
int MAGNIFICATION_EDIT_SHORTCUT = 1001;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
* enable service.
*/
int ENABLE_WARNING_FROM_TOGGLE = 1002;
/** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */
int ENABLE_WARNING_FROM_SHORTCUT = 1003;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox
* toggle.
*/
int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
* disable service.
*/
int DISABLE_WARNING_FROM_TOGGLE = 1005;
/**
* OPEN: Settings > Accessibility > Magnification > Toggle user service in button
* navigation.
*/
int ACCESSIBILITY_BUTTON_TUTORIAL = 1006;
/**
* OPEN: Settings > Accessibility > Magnification > Toggle user service in gesture
* navigation.
*/
int GESTURE_NAVIGATION_TUTORIAL = 1007;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle user service > Show
* launch tutorial.
*/
int LAUNCH_ACCESSIBILITY_TUTORIAL = 1008;
}
/**
* IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
* type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DialogType.EDIT_SHORTCUT_GENERIC,
DialogType.EDIT_SHORTCUT_GENERIC_SUW,
DialogType.EDIT_SHORTCUT_MAGNIFICATION,
DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW,
DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT,
})
public @interface DialogType {
int EDIT_SHORTCUT_GENERIC = 0;
int EDIT_SHORTCUT_GENERIC_SUW = 1;
int EDIT_SHORTCUT_MAGNIFICATION = 2;
int EDIT_SHORTCUT_MAGNIFICATION_SUW = 3;
int EDIT_MAGNIFICATION_SWITCH_SHORTCUT = 4;
}
/**
* Method to show the edit shortcut dialog.
*
* @param context A valid context
* @param dialogType The type of edit shortcut dialog
* @param dialogTitle The title of edit shortcut dialog
* @param listener The listener to determine the action of edit shortcut dialog
* @return A edit shortcut dialog for showing
*/
public static AlertDialog showEditShortcutDialog(Context context, int dialogType,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = createDialog(context, dialogType, dialogTitle, listener);
alertDialog.show();
setScrollIndicators(alertDialog);
return alertDialog;
}
/**
* Method to show the magnification edit shortcut dialog in Magnification.
*
* @param context A valid context
* @param positiveBtnListener The positive button listener
* @return A magnification edit shortcut dialog in Magnification
*/
public static Dialog createMagnificationSwitchShortcutDialog(Context context,
CustomButtonsClickListener positiveBtnListener) {
final View contentView = createSwitchShortcutDialogContentView(context);
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(contentView)
.setTitle(context.getString(
R.string.accessibility_magnification_switch_shortcut_title))
.create();
setCustomButtonsClickListener(alertDialog, contentView,
positiveBtnListener, /* negativeBtnListener= */ null);
setScrollIndicators(contentView);
return alertDialog;
}
private static AlertDialog createDialog(Context context, int dialogType,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createEditDialogContentView(context, dialogType))
.setTitle(dialogTitle)
.setPositiveButton(R.string.save, listener)
.setNegativeButton(R.string.cancel,
(DialogInterface dialog, int which) -> dialog.dismiss())
.create();
return alertDialog;
}
/**
* Sets the scroll indicators for dialog view. The indicators appears while content view is
* out of vision for vertical scrolling.
*/
private static void setScrollIndicators(AlertDialog dialog) {
final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
setScrollIndicators(scrollView);
}
/**
* Sets the scroll indicators for dialog view. The indicators appear while content view is
* out of vision for vertical scrolling.
*
* @param view The view contains customized dialog content. Usually it is {@link ScrollView} or
* {@link AbsListView}
*/
private static void setScrollIndicators(@NonNull View view) {
view.setScrollIndicators(
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM,
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
}
interface CustomButtonsClickListener {
void onClick(@CustomButton int which);
}
/**
* Annotation for customized dialog button type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
CustomButton.POSITIVE,
CustomButton.NEGATIVE,
})
public @interface CustomButton {
int POSITIVE = 1;
int NEGATIVE = 2;
}
private static void setCustomButtonsClickListener(Dialog dialog, View contentView,
CustomButtonsClickListener positiveBtnListener,
CustomButtonsClickListener negativeBtnListener) {
final Button positiveButton = contentView.findViewById(
R.id.custom_positive_button);
final Button negativeButton = contentView.findViewById(
R.id.custom_negative_button);
if (positiveButton != null) {
positiveButton.setOnClickListener(v -> {
if (positiveBtnListener != null) {
positiveBtnListener.onClick(CustomButton.POSITIVE);
}
dialog.dismiss();
});
}
if (negativeButton != null) {
negativeButton.setOnClickListener(v -> {
if (negativeBtnListener != null) {
negativeBtnListener.onClick(CustomButton.NEGATIVE);
}
dialog.dismiss();
});
}
}
private static View createSwitchShortcutDialogContentView(Context context) {
return createEditDialogContentView(context, DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT);
}
/**
* Get a content View for the edit shortcut dialog.
*
* @param context A valid context
* @param dialogType The type of edit shortcut dialog
* @return A content view suitable for viewing
*/
private static View createEditDialogContentView(Context context, int dialogType) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View contentView = null;
switch (dialogType) {
case DialogType.EDIT_SHORTCUT_GENERIC:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
break;
case DialogType.EDIT_SHORTCUT_GENERIC_SUW:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut, null);
initSoftwareShortcutForSUW(context, contentView);
initHardwareShortcut(context, contentView);
break;
case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut_magnification, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
initMagnifyShortcut(context, contentView);
initAdvancedWidget(contentView);
break;
case DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut_magnification, null);
initSoftwareShortcutForSUW(context, contentView);
initHardwareShortcut(context, contentView);
initMagnifyShortcut(context, contentView);
initAdvancedWidget(contentView);
break;
case DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT:
contentView = inflater.inflate(
R.layout.accessibility_edit_magnification_shortcut, null);
final ImageView image = contentView.findViewById(R.id.image);
image.setImageResource(retrieveSoftwareShortcutImageResId(context));
break;
default:
throw new IllegalArgumentException();
}
return contentView;
}
private static void setupShortcutWidget(View view, CharSequence titleText,
CharSequence summaryText, int imageResId) {
final CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(titleText);
final TextView summary = view.findViewById(R.id.summary);
if (TextUtils.isEmpty(summaryText)) {
summary.setVisibility(View.GONE);
} else {
summary.setText(summaryText);
summary.setMovementMethod(LinkMovementMethod.getInstance());
summary.setFocusable(false);
}
final ImageView image = view.findViewById(R.id.image);
image.setImageResource(imageResId);
}
private static void initSoftwareShortcutForSUW(Context context, View view) {
final View dialogView = view.findViewById(R.id.software_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_software);
final TextView summary = dialogView.findViewById(R.id.summary);
final int lineHeight = summary.getLineHeight();
setupShortcutWidget(dialogView, title,
retrieveSoftwareShortcutSummaryForSUW(context, lineHeight),
retrieveSoftwareShortcutImageResId(context));
}
private static void initSoftwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.software_shortcut);
final TextView summary = dialogView.findViewById(R.id.summary);
final int lineHeight = summary.getLineHeight();
setupShortcutWidget(dialogView,
retrieveTitle(context),
retrieveSoftwareShortcutSummary(context, lineHeight),
retrieveSoftwareShortcutImageResId(context));
}
private static void initHardwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.hardware_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_hardware);
final CharSequence summary = context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_hardware);
setupShortcutWidget(dialogView, title, summary,
R.drawable.accessibility_shortcut_type_hardware);
// TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
}
private static void initMagnifyShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
String summary = context.getString(
R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
// Format the number '3' in the summary.
final Object[] arguments = {3};
summary = MessageFormat.format(summary, arguments);
setupShortcutWidget(dialogView, title, summary,
R.drawable.accessibility_shortcut_type_triple_tap);
// TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
}
private static void initAdvancedWidget(View view) {
final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
advanced.setOnClickListener((View v) -> {
advanced.setVisibility(View.GONE);
tripleTap.setVisibility(View.VISIBLE);
});
}
private static CharSequence retrieveSoftwareShortcutSummaryForSUW(Context context,
int lineHeight) {
final SpannableStringBuilder sb = new SpannableStringBuilder();
if (!AccessibilityUtil.isFloatingMenuEnabled(context)) {
sb.append(getSummaryStringWithIcon(context, lineHeight));
}
return sb;
}
private static CharSequence retrieveTitle(Context context) {
int resId;
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
resId = R.string.accessibility_shortcut_edit_dialog_title_software;
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = R.string.accessibility_shortcut_edit_dialog_title_software_by_gesture;
} else {
resId = R.string.accessibility_shortcut_edit_dialog_title_software;
}
return context.getText(resId);
}
private static CharSequence retrieveSoftwareShortcutSummary(Context context, int lineHeight) {
final SpannableStringBuilder sb = new SpannableStringBuilder();
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
sb.append(getCustomizeAccessibilityButtonLink(context));
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
final int resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback
: R.string.accessibility_shortcut_edit_dialog_summary_software_gesture;
sb.append(context.getText(resId));
sb.append("\n\n");
sb.append(getCustomizeAccessibilityButtonLink(context));
} else {
sb.append(getSummaryStringWithIcon(context, lineHeight));
sb.append("\n\n");
sb.append(getCustomizeAccessibilityButtonLink(context));
}
return sb;
}
private static int retrieveSoftwareShortcutImageResId(Context context) {
int resId;
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
resId = R.drawable.accessibility_shortcut_type_software_floating;
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.drawable.accessibility_shortcut_type_software_gesture_talkback
: R.drawable.accessibility_shortcut_type_software_gesture;
} else {
resId = R.drawable.accessibility_shortcut_type_software;
}
return resId;
}
private static CharSequence getCustomizeAccessibilityButtonLink(Context context) {
final View.OnClickListener linkListener = v -> new SubSettingLauncher(context)
.setDestination(AccessibilityButtonFragment.class.getName())
.setSourceMetricsCategory(
SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS)
.launch();
final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener);
return AnnotationSpan.linkify(context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_software_floating), linkInfo);
}
private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
final String summary = context
.getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
final SpannableString spannableMessage = SpannableString.valueOf(summary);
// Icon
final int indexIconStart = summary.indexOf("%s");
final int indexIconEnd = indexIconStart + 2;
final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
final ImageSpan imageSpan = new ImageSpan(icon);
imageSpan.setContentDescription("");
icon.setBounds(0, 0, lineHeight, lineHeight);
spannableMessage.setSpan(
imageSpan, indexIconStart, indexIconEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableMessage;
}
/**
* Returns the color associated with the specified attribute in the context's theme.
*/
@ColorInt
private static int getThemeAttrColor(final Context context, final int attributeColor) {
final int colorResId = getAttrResourceId(context, attributeColor);
return ContextCompat.getColor(context, colorResId);
}
/**
* Returns the identifier of the resolved resource assigned to the given attribute.
*/
private static int getAttrResourceId(final Context context, final int attributeColor) {
final int[] attrs = {attributeColor};
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
final int colorResId = typedArray.getResourceId(0, 0);
typedArray.recycle();
return colorResId;
}
/**
* Creates a dialog with the given view.
*
* @param context A valid context
* @param dialogTitle The title of the dialog
* @param customView The customized view
* @param listener This listener will be invoked when the positive button in the dialog is
* clicked
* @return the {@link Dialog} with the given view
*/
public static Dialog createCustomDialog(Context context, CharSequence dialogTitle,
View customView, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(customView)
.setTitle(dialogTitle)
.setCancelable(true)
.setPositiveButton(R.string.save, listener)
.setNegativeButton(R.string.cancel, null)
.create();
if (customView instanceof ScrollView || customView instanceof AbsListView) {
setScrollIndicators(customView);
}
return alertDialog;
}
/**
* Creates a single choice {@link ListView} with given {@link ItemInfo} list.
*
* @param context A context.
* @param itemInfoList A {@link ItemInfo} list.
* @param itemListener The listener will be invoked when the item is clicked.
*/
@NonNull
public static ListView createSingleChoiceListView(@NonNull Context context,
@NonNull List<? extends ItemInfo> itemInfoList,
@Nullable AdapterView.OnItemClickListener itemListener) {
final ListView list = new ListView(context);
// Set an id to save its state.
list.setId(android.R.id.list);
list.setDivider(/* divider= */ null);
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
final ItemInfoArrayAdapter
adapter = new ItemInfoArrayAdapter(context, itemInfoList);
list.setAdapter(adapter);
list.setOnItemClickListener(itemListener);
return list;
}
}