From 72aa60ae9593fa0356bfdbddbd9f4376bff29801 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Fri, 6 Mar 2020 22:54:00 +0800 Subject: Tutorial improvement for Accessibility shortcut (2/n). Update UI widgets to meet the new design. Bug: 148989018 Test: manual test Change-Id: Ife42995af193db6746135d29f6fa1ad452d265a6 --- .../AccessibilityGestureNavigationTutorial.java | 329 ++++++++++++++++++++- ...ggleAccessibilityServicePreferenceFragment.java | 14 +- .../ToggleFeaturePreferenceFragment.java | 13 +- ...oggleScreenMagnificationPreferenceFragment.java | 17 +- 4 files changed, 349 insertions(+), 24 deletions(-) (limited to 'src/com/android/settings/accessibility') diff --git a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java index ddf7bd442f..c3320ad156 100644 --- a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java +++ b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java @@ -16,35 +16,54 @@ package com.android.settings.accessibility; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType; + import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ImageSpan; +import android.util.TypedValue; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.TextureView; import android.view.View; +import android.view.ViewGroup; import android.view.Window; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextSwitcher; import android.widget.TextView; +import androidx.annotation.AnimRes; import androidx.annotation.ColorInt; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; +import androidx.core.util.Preconditions; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; import com.android.settings.R; +import com.android.settings.Utils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * Utility class for creating the dialog that guides users for gesture navigation for * accessibility services. */ -public class AccessibilityGestureNavigationTutorial { - +public final class AccessibilityGestureNavigationTutorial { /** IntDef enum for dialog type. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -59,6 +78,8 @@ public class AccessibilityGestureNavigationTutorial { int GESTURE_NAVIGATION_SETTINGS = 2; } + private AccessibilityGestureNavigationTutorial() {} + private static final DialogInterface.OnClickListener mOnClickListener = (DialogInterface dialog, int which) -> dialog.dismiss(); @@ -91,6 +112,13 @@ public class AccessibilityGestureNavigationTutorial { return createDialog(context, DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION); } + static AlertDialog createAccessibilityTutorialDialog(Context context, int shortcutTypes) { + return new AlertDialog.Builder(context) + .setView(createShortcutNavigationContentView(context, shortcutTypes)) + .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener) + .create(); + } + /** * Get a content View for a dialog to confirm that they want to enable a service. * @@ -201,4 +229,301 @@ public class AccessibilityGestureNavigationTutorial { typedArray.recycle(); return colorResId; } + + private static class TutorialPagerAdapter extends PagerAdapter { + private final List mTutorialPages; + private TutorialPagerAdapter(List tutorialPages) { + this.mTutorialPages = tutorialPages; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + final View itemView = mTutorialPages.get(position).getImageView(); + container.addView(itemView); + return itemView; + } + + @Override + public int getCount() { + return mTutorialPages.size(); + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object o) { + return view == o; + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, + @NonNull Object object) { + final View itemView = mTutorialPages.get(position).getImageView(); + container.removeView(itemView); + } + } + + private static ImageView createImageView(Context context, int imageRes) { + final ImageView imageView = new ImageView(context); + imageView.setImageResource(imageRes); + imageView.setAdjustViewBounds(true); + + return imageView; + } + + private static View createShortcutNavigationContentView(Context context, int shortcutTypes) { + final LayoutInflater inflater = context.getSystemService(LayoutInflater.class); + final View contentView = inflater.inflate( + R.layout.accessibility_shortcut_tutorial_dialog, /* root= */ null); + final List tutorialPages = + createShortcutTutorialPages(context, shortcutTypes); + Preconditions.checkArgument(!tutorialPages.isEmpty(), + /* errorMessage= */ "Unexpected tutorial pages size"); + + final LinearLayout indicatorContainer = contentView.findViewById(R.id.indicator_container); + indicatorContainer.setVisibility(tutorialPages.size() > 1 ? VISIBLE : GONE); + for (TutorialPage page : tutorialPages) { + indicatorContainer.addView(page.getIndicatorIcon()); + } + tutorialPages.get(/* firstIndex */ 0).getIndicatorIcon().setEnabled(true); + + final TextSwitcher title = contentView.findViewById(R.id.title); + title.setFactory(() -> makeTitleView(context)); + title.setText(tutorialPages.get(/* firstIndex */ 0).getTitle()); + + final TextSwitcher instruction = contentView.findViewById(R.id.instruction); + instruction.setFactory(() -> makeInstructionView(context)); + instruction.setText(tutorialPages.get(/* firstIndex */ 0).getInstruction()); + + final ViewPager viewPager = contentView.findViewById(R.id.view_pager); + viewPager.setAdapter(new TutorialPagerAdapter(tutorialPages)); + viewPager.addOnPageChangeListener( + new TutorialPageChangeListener(context, title, instruction, tutorialPages)); + + return contentView; + } + + private static View makeTitleView(Context context) { + final String familyName = + context.getString( + com.android.internal.R.string.config_headlineFontFamilyMedium); + final TextView textView = new TextView(context); + + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, /* size= */ 20); + textView.setTextColor(Utils.getColorAttr(context, android.R.attr.textColorPrimary)); + textView.setGravity(Gravity.CENTER); + textView.setTypeface(Typeface.create(familyName, Typeface.NORMAL)); + + return textView; + } + + private static View makeInstructionView(Context context) { + final TextView textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, /* size= */ 16); + textView.setTextColor(Utils.getColorAttr(context, android.R.attr.textColorPrimary)); + textView.setTypeface( + Typeface.create(/* familyName= */ "sans-serif", Typeface.NORMAL)); + return textView; + } + + private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) { + final CharSequence title = getSoftwareTitle(context); + final ImageView image = createSoftwareImage(context); + final CharSequence instruction = getSoftwareInstruction(context); + final ImageView indicatorIcon = + createImageView(context, R.drawable.ic_accessibility_page_indicator); + indicatorIcon.setEnabled(false); + + return new TutorialPage(title, image, indicatorIcon, instruction); + } + + private static TutorialPage createHardwareTutorialPage(@NonNull Context context) { + final CharSequence title = + context.getText(R.string.accessibility_tutorial_dialog_title_volume); + final ImageView image = + createImageView(context, R.drawable.accessibility_shortcut_type_hardware); + final ImageView indicatorIcon = + createImageView(context, R.drawable.ic_accessibility_page_indicator); + final CharSequence instruction = + context.getText(R.string.accessibility_tutorial_dialog_message_volume); + indicatorIcon.setEnabled(false); + + return new TutorialPage(title, image, indicatorIcon, instruction); + } + + private static TutorialPage createTripleTapTutorialPage(@NonNull Context context) { + final CharSequence title = + context.getText(R.string.accessibility_tutorial_dialog_title_triple); + final ImageView image = + createImageView(context, R.drawable.accessibility_shortcut_type_triple_tap); + final CharSequence instruction = + context.getText(R.string.accessibility_tutorial_dialog_message_triple); + final ImageView indicatorIcon = + createImageView(context, R.drawable.ic_accessibility_page_indicator); + indicatorIcon.setEnabled(false); + + return new TutorialPage(title, image, indicatorIcon, instruction); + } + + private static List createShortcutTutorialPages(@NonNull Context context, + int shortcutTypes) { + final List tutorialPages = new ArrayList<>(); + if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) { + tutorialPages.add(createSoftwareTutorialPage(context)); + } + + if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) { + tutorialPages.add(createHardwareTutorialPage(context)); + } + + if ((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) { + tutorialPages.add(createTripleTapTutorialPage(context)); + } + + return tutorialPages; + } + + private static CharSequence getSoftwareTitle(Context context) { + final boolean isGestureNavigationEnabled = + AccessibilityUtil.isGestureNavigateEnabled(context); + final boolean isTouchExploreEnabled = AccessibilityUtil.isTouchExploreEnabled(context); + + return (isGestureNavigationEnabled || isTouchExploreEnabled) + ? context.getText(R.string.accessibility_tutorial_dialog_title_gesture) + : context.getText(R.string.accessibility_tutorial_dialog_title_button); + } + + private static ImageView createSoftwareImage(Context context) { + int resId = R.drawable.accessibility_shortcut_type_software; + if (AccessibilityUtil.isGestureNavigateEnabled(context)) { + resId = AccessibilityUtil.isTouchExploreEnabled(context) + ? R.drawable.accessibility_shortcut_type_software_gesture_talkback + : R.drawable.accessibility_shortcut_type_software_gesture; + } + + return createImageView(context, resId); + } + + private static CharSequence getSoftwareInstruction(Context context) { + final boolean isGestureNavigateEnabled = + AccessibilityUtil.isGestureNavigateEnabled(context); + final boolean isTouchExploreEnabled = AccessibilityUtil.isTouchExploreEnabled(context); + int resId = R.string.accessibility_tutorial_dialog_message_button; + if (isGestureNavigateEnabled) { + resId = isTouchExploreEnabled + ? R.string.accessibility_tutorial_dialog_message_gesture_talkback + : R.string.accessibility_tutorial_dialog_message_gesture; + } + + CharSequence text = context.getText(resId); + if (resId == R.string.accessibility_tutorial_dialog_message_button) { + text = getSoftwareInstructionWithIcon(context, text); + } + + return text; + } + + private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) { + final String message = text.toString(); + final SpannableString spannableInstruction = SpannableString.valueOf(message); + final int indexIconStart = message.indexOf("%s"); + final int indexIconEnd = indexIconStart + 2; + final ImageView iconView = new ImageView(context); + iconView.setImageDrawable(context.getDrawable(R.drawable.ic_accessibility_new)); + final Drawable icon = iconView.getDrawable().mutate(); + final ImageSpan imageSpan = new ImageSpan(icon); + imageSpan.setContentDescription(""); + icon.setBounds(/* left= */ 0, /* top= */ 0, + icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + spannableInstruction.setSpan(imageSpan, indexIconStart, indexIconEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + return spannableInstruction; + } + + private static class TutorialPage { + private final CharSequence mTitle; + private final ImageView mImageView; + private final ImageView mIndicatorIcon; + private final CharSequence mInstruction; + + TutorialPage(CharSequence title, ImageView imageView, ImageView indicatorIcon, + CharSequence instruction) { + this.mTitle = title; + this.mImageView = imageView; + this.mIndicatorIcon = indicatorIcon; + this.mInstruction = instruction; + } + + public CharSequence getTitle() { + return mTitle; + } + + public ImageView getImageView() { + return mImageView; + } + + public ImageView getIndicatorIcon() { + return mIndicatorIcon; + } + + public CharSequence getInstruction() { + return mInstruction; + } + } + + private static class TutorialPageChangeListener implements ViewPager.OnPageChangeListener { + private int mLastTutorialPagePosition = 0; + private final Context mContext; + private final TextSwitcher mTitle; + private final TextSwitcher mInstruction; + private final List mTutorialPages; + + TutorialPageChangeListener(Context context, ViewGroup title, ViewGroup instruction, + List tutorialPages) { + this.mContext = context; + this.mTitle = (TextSwitcher) title; + this.mInstruction = (TextSwitcher) instruction; + this.mTutorialPages = tutorialPages; + } + + @Override + public void onPageScrolled(int position, float positionOffset, + int positionOffsetPixels) { + // Do nothing. + } + + @Override + public void onPageSelected(int position) { + final boolean isPreviousPosition = + mLastTutorialPagePosition > position; + @AnimRes + final int inAnimationResId = isPreviousPosition + ? android.R.anim.slide_in_left + : com.android.internal.R.anim.slide_in_right; + + @AnimRes + final int outAnimationResId = isPreviousPosition + ? android.R.anim.slide_out_right + : com.android.internal.R.anim.slide_out_left; + + mTitle.setInAnimation(mContext, inAnimationResId); + mTitle.setOutAnimation(mContext, outAnimationResId); + mTitle.setText(mTutorialPages.get(position).getTitle()); + + mInstruction.setInAnimation(mContext, inAnimationResId); + mInstruction.setOutAnimation(mContext, outAnimationResId); + mInstruction.setText(mTutorialPages.get(position).getInstruction()); + + for (TutorialPage page : mTutorialPages) { + page.getIndicatorIcon().setEnabled(false); + } + mTutorialPages.get(position).getIndicatorIcon().setEnabled(true); + mLastTutorialPagePosition = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + // Do nothing. + } + } } diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java index 6fdbe0310e..86181e9fe3 100644 --- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java @@ -164,16 +164,6 @@ public class ToggleAccessibilityServicePreferenceFragment extends this::onDialogButtonFromDisableToggleClicked); break; } - case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: { - if (AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())) { - mDialog = AccessibilityGestureNavigationTutorial - .showGestureNavigationTutorialDialog(getPrefContext()); - } else { - mDialog = AccessibilityGestureNavigationTutorial - .showAccessibilityButtonTutorialDialog(getPrefContext()); - } - break; - } default: { mDialog = super.onCreateDialog(dialogId); } @@ -307,6 +297,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends } else { AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes, mComponentName); + showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); } } else { AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes, @@ -420,6 +411,9 @@ public class ToggleAccessibilityServicePreferenceFragment extends final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE); AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes, mComponentName); + mIsDialogShown.set(false); + showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); + mDialog.dismiss(); mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index 7fc4623e33..8b4f3f25bd 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -42,7 +42,6 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import android.widget.CheckBox; import android.widget.ImageView; -import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; @@ -250,14 +249,21 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference @Override public Dialog onCreateDialog(int dialogId) { + Dialog dialog; switch (dialogId) { case DialogEnums.EDIT_SHORTCUT: final CharSequence dialogTitle = getPrefContext().getString( R.string.accessibility_shortcut_title, mPackageName); - final AlertDialog dialog = AccessibilityEditDialogUtils.showEditShortcutDialog( + dialog = AccessibilityEditDialogUtils.showEditShortcutDialog( getPrefContext(), dialogTitle, this::callOnAlertDialogCheckboxClicked); initializeDialogCheckBox(dialog); return dialog; + case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: + dialog = AccessibilityGestureNavigationTutorial + .createAccessibilityTutorialDialog(getPrefContext(), + getUserShortcutTypes()); + dialog.setCanceledOnTouchOutside(false); + return dialog; default: throw new IllegalArgumentException("Unsupported dialogId " + dialogId); } @@ -268,6 +274,8 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference switch (dialogId) { case DialogEnums.EDIT_SHORTCUT: return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT; + case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: + return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL; default: return SettingsEnums.ACTION_UNKNOWN; } @@ -663,6 +671,7 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference if (preference.isChecked()) { AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes, mComponentName); + showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); } else { AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes, mComponentName); diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index 11f25b4dd4..b550434fc8 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -224,24 +224,22 @@ public class ToggleScreenMagnificationPreferenceFragment extends @Override public Dialog onCreateDialog(int dialogId) { + final AlertDialog dialog; switch (dialogId) { case DialogEnums.GESTURE_NAVIGATION_TUTORIAL: return AccessibilityGestureNavigationTutorial .showGestureNavigationTutorialDialog(getPrefContext()); - case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL: - return AccessibilityGestureNavigationTutorial - .showAccessibilityButtonTutorialDialog(getPrefContext()); case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT: final CharSequence dialogTitle = getPrefContext().getString( R.string.accessibility_shortcut_title, mPackageName); - final AlertDialog dialog = - AccessibilityEditDialogUtils.showMagnificationEditShortcutDialog( + dialog = AccessibilityEditDialogUtils.showMagnificationEditShortcutDialog( getPrefContext(), dialogTitle, this::callOnAlertDialogCheckboxClicked); initializeDialogCheckBox(dialog); return dialog; + default: + return super.onCreateDialog(dialogId); } - throw new IllegalArgumentException("Unsupported dialogId " + dialogId); } private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) { @@ -408,7 +406,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT: return SettingsEnums.DIALOG_MAGNIFICATION_EDIT_SHORTCUT; default: - return 0; + return super.getDialogMetricsCategory(dialogId); } } @@ -422,9 +420,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends if (enabled && TextUtils.equals( Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, preferenceKey)) { - showDialog(AccessibilityUtil.isGestureNavigateEnabled(getPrefContext()) - ? DialogEnums.GESTURE_NAVIGATION_TUTORIAL - : DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL); + showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); } MagnificationPreferenceFragment.setChecked(getContentResolver(), preferenceKey, enabled); } @@ -454,6 +450,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE); if (preference.isChecked()) { optInAllMagnificationValuesToSettings(getPrefContext(), shortcutTypes); + showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL); } else { optOutAllMagnificationValuesFromSettings(getPrefContext(), shortcutTypes); } -- cgit v1.2.3