diff --git a/res/drawable/accessibility_button_preview_three_finger.xml b/res/drawable/accessibility_button_preview_three_finger.xml new file mode 100644 index 00000000000..0fe6710df66 --- /dev/null +++ b/res/drawable/accessibility_button_preview_three_finger.xml @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/res/drawable/accessibility_button_preview_two_finger.xml b/res/drawable/accessibility_button_preview_two_finger.xml new file mode 100644 index 00000000000..e61e3e84fc7 --- /dev/null +++ b/res/drawable/accessibility_button_preview_two_finger.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 9ee75794802..a9fbc8b360e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5427,9 +5427,9 @@ Quickly access accessibility features - Quickly access accessibility features from any screen.\n\nTo get started, go to accessibility settings and select a feature. Tap on the shortcut and select the accessibility button.\n\nTo use the accessibility button in the navigation bar instead, switch to 2-button navigation or 3-button navigation. + Quickly access accessibility features from any screen.\n\nTo get started, go to accessibility settings and select a feature. Tap on the shortcut and select the accessibility button or gesture. - Quickly access accessibility features from any screen. \n\nTo get started, go to accessibility settings and select a feature. Tap on the shortcut and select the accessibility button. + Quickly access accessibility features from any screen.\n\nTo get started, go to accessibility settings and select a feature. Tap on the shortcut and select the accessibility button. Use button or gesture diff --git a/res/xml/accessibility_button_settings.xml b/res/xml/accessibility_button_settings.xml index 02b1c7d4693..9f32714f328 100644 --- a/res/xml/accessibility_button_settings.xml +++ b/res/xml/accessibility_button_settings.xml @@ -16,8 +16,7 @@ + xmlns:settings="http://schemas.android.com/apk/res-auto"> + + + android:summary="@string/accessibility_button_summary" + settings:controller="com.android.settings.accessibility.AccessibilityButtonPreferenceController"/> mDefaultGesture = Optional.empty(); + + public AccessibilityButtonGesturePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AccessibilityUtil.isGestureNavigateEnabled(mContext) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final ListPreference listPreference = (ListPreference) preference; + final Integer value = Ints.tryParse((String) newValue); + if (value != null) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, value); + updateState(listPreference); + } + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + final ListPreference listPreference = (ListPreference) preference; + + listPreference.setValue(getCurrentAccessibilityButtonMode()); + } + + private String getCurrentAccessibilityButtonMode() { + final int mode = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, getDefaultGestureValue()); + return String.valueOf(mode); + } + + private int getDefaultGestureValue() { + if (!mDefaultGesture.isPresent()) { + final String[] valuesList = mContext.getResources().getStringArray( + R.array.accessibility_button_gesture_selector_values); + mDefaultGesture = Optional.of(Integer.parseInt(valuesList[0])); + } + return mDefaultGesture.get(); + } +} diff --git a/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceController.java index ed7cb27bff6..167e08faec7 100644 --- a/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceController.java +++ b/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceController.java @@ -18,7 +18,6 @@ package com.android.settings.accessibility; import android.content.Context; import android.provider.Settings; -import android.util.ArrayMap; import androidx.preference.ListPreference; import androidx.preference.Preference; @@ -28,16 +27,16 @@ import com.android.settings.core.BasePreferenceController; import com.google.common.primitives.Ints; +import java.util.Optional; + /** Preference controller that controls the preferred location in accessibility button page. */ public class AccessibilityButtonLocationPreferenceController extends BasePreferenceController implements Preference.OnPreferenceChangeListener { - private final ArrayMap mValueTitleMap = new ArrayMap<>(); - private int mDefaultLocation; + private Optional mDefaultLocation = Optional.empty(); public AccessibilityButtonLocationPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); - initValueTitleMap(); } @Override @@ -68,22 +67,16 @@ public class AccessibilityButtonLocationPreferenceController extends BasePrefere private String getCurrentAccessibilityButtonMode() { final int mode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mDefaultLocation); + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, getDefaultLocationValue()); return String.valueOf(mode); } - private void initValueTitleMap() { - if (mValueTitleMap.size() == 0) { - final String[] values = mContext.getResources().getStringArray( + private int getDefaultLocationValue() { + if (!mDefaultLocation.isPresent()) { + final String[] valuesList = mContext.getResources().getStringArray( R.array.accessibility_button_location_selector_values); - final String[] titles = mContext.getResources().getStringArray( - R.array.accessibility_button_location_selector_titles); - final int mapSize = values.length; - - mDefaultLocation = Integer.parseInt(values[0]); - for (int i = 0; i < mapSize; i++) { - mValueTitleMap.put(values[i], titles[i]); - } + mDefaultLocation = Optional.of(Integer.parseInt(valuesList[0])); } + return mDefaultLocation.get(); } } diff --git a/src/com/android/settings/accessibility/AccessibilityButtonPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonPreferenceController.java new file mode 100644 index 00000000000..f0764721dcd --- /dev/null +++ b/src/com/android/settings/accessibility/AccessibilityButtonPreferenceController.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 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 android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +/** + * Preference controller for accessibility button preference. + */ +public class AccessibilityButtonPreferenceController extends BasePreferenceController { + + public AccessibilityButtonPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final int titleResource = AccessibilityUtil.isGestureNavigateEnabled(mContext) + ? R.string.accessibility_button_gesture_title : R.string.accessibility_button_title; + final Preference preference = screen.findPreference(getPreferenceKey()); + preference.setTitle(titleResource); + + } +} diff --git a/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java index 69a7a46f0c3..8a840a1cb70 100644 --- a/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java +++ b/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.provider.Settings; +import android.view.accessibility.AccessibilityManager; import android.widget.ImageView; import androidx.annotation.VisibleForTesting; @@ -46,11 +47,14 @@ public class AccessibilityButtonPreviewPreferenceController extends BasePreferen private final ContentResolver mContentResolver; @VisibleForTesting final ContentObserver mContentObserver; - private FloatingMenuLayerDrawable mFloatingMenuPreviewDrawable; + private AccessibilityLayerDrawable mAccessibilityPreviewDrawable; @VisibleForTesting ImageView mPreview; + private AccessibilityManager.TouchExplorationStateChangeListener + mTouchExplorationStateChangeListener; + public AccessibilityButtonPreviewPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mContentResolver = context.getContentResolver(); @@ -60,6 +64,9 @@ public class AccessibilityButtonPreviewPreferenceController extends BasePreferen updatePreviewPreference(); } }; + mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { + updatePreviewPreference(); + }; } @Override @@ -78,6 +85,9 @@ public class AccessibilityButtonPreviewPreferenceController extends BasePreferen @Override public void onResume() { + final AccessibilityManager am = mContext.getSystemService(AccessibilityManager.class); + am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); + mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */ false, mContentObserver); @@ -91,6 +101,9 @@ public class AccessibilityButtonPreviewPreferenceController extends BasePreferen @Override public void onPause() { + final AccessibilityManager am = mContext.getSystemService(AccessibilityManager.class); + am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); + mContentResolver.unregisterContentObserver(mContentObserver); } @@ -103,9 +116,14 @@ public class AccessibilityButtonPreviewPreferenceController extends BasePreferen final int floatingMenuIconId = (size == SMALL_SIZE) ? R.drawable.accessibility_button_preview_small_floating_menu : R.drawable.accessibility_button_preview_large_floating_menu; - - mPreview.setImageDrawable(getFloatingMenuPreviewDrawable(floatingMenuIconId, opacity)); - // Only change opacity(alpha) would not invoke redraw view, need to invalidate manually. + mPreview.setImageDrawable(getAccessibilityPreviewDrawable(floatingMenuIconId, opacity)); + mPreview.invalidate(); + } else if (AccessibilityUtil.isGestureNavigateEnabled(mContext)) { + // TODO(b/193081959): Use static illustartion instead. + final int resId = AccessibilityUtil.isTouchExploreEnabled(mContext) + ? R.drawable.accessibility_button_preview_three_finger + : R.drawable.accessibility_button_preview_two_finger; + mPreview.setImageDrawable(getAccessibilityPreviewDrawable(resId, /* opacity= */ 100)); mPreview.invalidate(); } else { mPreview.setImageDrawable( @@ -113,14 +131,14 @@ public class AccessibilityButtonPreviewPreferenceController extends BasePreferen } } - private Drawable getFloatingMenuPreviewDrawable(int resId, int opacity) { - if (mFloatingMenuPreviewDrawable == null) { - mFloatingMenuPreviewDrawable = FloatingMenuLayerDrawable.createLayerDrawable( + private Drawable getAccessibilityPreviewDrawable(int resId, int opacity) { + if (mAccessibilityPreviewDrawable == null) { + mAccessibilityPreviewDrawable = AccessibilityLayerDrawable.createLayerDrawable( mContext, resId, opacity); } else { - mFloatingMenuPreviewDrawable.updateLayerDrawable(mContext, resId, opacity); + mAccessibilityPreviewDrawable.updateLayerDrawable(mContext, resId, opacity); } - return mFloatingMenuPreviewDrawable; + return mAccessibilityPreviewDrawable; } } diff --git a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java index ffe5d6db59b..a9ba14a7646 100644 --- a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java +++ b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java @@ -344,12 +344,11 @@ public class AccessibilityDialogUtils { private static void initSoftwareShortcut(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, + setupShortcutWidget(dialogView, + retrieveTitle(context), retrieveSoftwareShortcutSummary(context, lineHeight), retrieveSoftwareShortcutImageResId(context)); } @@ -398,20 +397,49 @@ public class AccessibilityDialogUtils { 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)) { + 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)); } - sb.append(getCustomizeAccessibilityButtonLink(context)); return sb; } private static int retrieveSoftwareShortcutImageResId(Context context) { - return AccessibilityUtil.isFloatingMenuEnabled(context) - ? R.drawable.accessibility_shortcut_type_software_floating - : R.drawable.accessibility_shortcut_type_software; + 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) { @@ -422,7 +450,6 @@ public class AccessibilityDialogUtils { .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); } diff --git a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java index f8cdcb3388a..bc2e05d3258 100644 --- a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java +++ b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java @@ -27,6 +27,7 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.text.Spannable; import android.text.SpannableString; +import android.text.SpannableStringBuilder; import android.text.style.ImageSpan; import android.view.Gravity; import android.view.LayoutInflater; @@ -323,8 +324,7 @@ public final class AccessibilityGestureNavigationTutorial { } private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) { - final CharSequence title = context.getText( - R.string.accessibility_tutorial_dialog_title_button); + final CharSequence title = getSoftwareTitle(context); final ImageView image = createSoftwareImage(context); final CharSequence instruction = getSoftwareInstruction(context); final ImageView indicatorIcon = @@ -382,18 +382,46 @@ public final class AccessibilityGestureNavigationTutorial { } private static ImageView createSoftwareImage(Context context) { - final int resId = AccessibilityUtil.isFloatingMenuEnabled(context) - ? R.drawable.accessibility_shortcut_type_software_floating - : R.drawable.accessibility_shortcut_type_software; - + 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 createImageView(context, resId); } + private static CharSequence getSoftwareTitle(Context context) { + int resId; + if (AccessibilityUtil.isFloatingMenuEnabled(context)) { + resId = R.string.accessibility_tutorial_dialog_title_button; + } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { + resId = R.string.accessibility_tutorial_dialog_title_gesture; + } else { + resId = R.string.accessibility_tutorial_dialog_title_button; + } + return context.getText(resId); + } + private static CharSequence getSoftwareInstruction(Context context) { - return AccessibilityUtil.isFloatingMenuEnabled(context) - ? context.getText(R.string.accessibility_tutorial_dialog_message_floating_button) - : getSoftwareInstructionWithIcon(context, - context.getText(R.string.accessibility_tutorial_dialog_message_button)); + final SpannableStringBuilder sb = new SpannableStringBuilder(); + if (AccessibilityUtil.isFloatingMenuEnabled(context)) { + final int resId = R.string.accessibility_tutorial_dialog_message_floating_button; + sb.append(context.getText(resId)); + } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { + final int resId = AccessibilityUtil.isTouchExploreEnabled(context) + ? R.string.accessibility_tutorial_dialog_message_gesture_talkback + : R.string.accessibility_tutorial_dialog_message_gesture; + sb.append(context.getText(resId)); + } else { + final int resId = R.string.accessibility_tutorial_dialog_message_button; + sb.append(getSoftwareInstructionWithIcon(context, context.getText(resId))); + } + return sb; } private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) { diff --git a/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java b/src/com/android/settings/accessibility/AccessibilityLayerDrawable.java similarity index 76% rename from src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java rename to src/com/android/settings/accessibility/AccessibilityLayerDrawable.java index bfce114ae13..b04a969885a 100644 --- a/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java +++ b/src/com/android/settings/accessibility/AccessibilityLayerDrawable.java @@ -27,10 +27,10 @@ import com.android.settings.R; import java.util.Objects; -/** LayerDrawable that contains device icon as background and floating menu icon as foreground. */ -public class FloatingMenuLayerDrawable extends LayerDrawable { +/** LayerDrawable that contains device icon as background and given icon as foreground. */ +public class AccessibilityLayerDrawable extends LayerDrawable { - private FloatingMenuLayerDrawableState mState; + private AccessibilityLayerDrawableState mState; /** * Creates a new layer drawable with the list of specified layers. @@ -38,23 +38,23 @@ public class FloatingMenuLayerDrawable extends LayerDrawable { * @param layers a list of drawables to use as layers in this new drawable, * must be non-null */ - private FloatingMenuLayerDrawable(@NonNull Drawable[] layers) { + private AccessibilityLayerDrawable(@NonNull Drawable[] layers) { super(layers); } /** - * Create the {@link LayerDrawable} that contains device icon as background and floating menu - * icon with given {@code opacity} value as foreground. + * Create the {@link LayerDrawable} that contains device icon as background and given menu icon + * with given {@code opacity} value as foreground. * * @param context the valid context used to get the icon - * @param resId the resource ID of the floating menu icon + * @param resId the resource ID of the given icon * @param opacity the opacity to apply to the given icon - * @return the drawable that combines the device icon and the floating menu icon + * @return the drawable that combines the device icon and the given icon */ - public static FloatingMenuLayerDrawable createLayerDrawable(Context context, int resId, + public static AccessibilityLayerDrawable createLayerDrawable(Context context, int resId, int opacity) { final Drawable bg = context.getDrawable(R.drawable.accessibility_button_preview_base); - final FloatingMenuLayerDrawable basicDrawable = new FloatingMenuLayerDrawable( + final AccessibilityLayerDrawable basicDrawable = new AccessibilityLayerDrawable( new Drawable[]{bg, null}); basicDrawable.updateLayerDrawable(context, resId, opacity); @@ -66,7 +66,7 @@ public class FloatingMenuLayerDrawable extends LayerDrawable { * value at index 1 layer. * * @param context the valid context used to get the icon - * @param resId the resource ID of the floating menu icon + * @param resId the resource ID of the given icon * @param opacity the opacity to apply to the given icon */ public void updateLayerDrawable(Context context, int resId, int opacity) { @@ -83,18 +83,18 @@ public class FloatingMenuLayerDrawable extends LayerDrawable { /** Stores the constant state and data to the given drawable. */ private void setConstantState(Context context, int resId, int opacity) { - mState = new FloatingMenuLayerDrawableState(context, resId, opacity); + mState = new AccessibilityLayerDrawableState(context, resId, opacity); } - /** {@link ConstantState} to store the data of {@link FloatingMenuLayerDrawable}. */ + /** {@link ConstantState} to store the data of {@link AccessibilityLayerDrawable}. */ @VisibleForTesting - static class FloatingMenuLayerDrawableState extends ConstantState { + static class AccessibilityLayerDrawableState extends ConstantState { private final Context mContext; private final int mResId; private final int mOpacity; - FloatingMenuLayerDrawableState(Context context, int resId, int opacity) { + AccessibilityLayerDrawableState(Context context, int resId, int opacity) { mContext = context; mResId = resId; mOpacity = opacity; @@ -119,7 +119,7 @@ public class FloatingMenuLayerDrawable extends LayerDrawable { if (o == null || getClass() != o.getClass()) { return false; } - final FloatingMenuLayerDrawableState that = (FloatingMenuLayerDrawableState) o; + final AccessibilityLayerDrawableState that = (AccessibilityLayerDrawableState) o; return mResId == that.mResId && mOpacity == that.mOpacity && Objects.equals(mContext, that.mContext); diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index 7cefcedff28..245f362e093 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -584,6 +584,18 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference return value; } + private static CharSequence getSoftwareShortcutTypeSummary(Context context) { + int resId; + if (AccessibilityUtil.isFloatingMenuEnabled(context)) { + resId = R.string.accessibility_shortcut_edit_summary_software; + } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { + resId = R.string.accessibility_shortcut_edit_summary_software_gesture; + } else { + resId = R.string.accessibility_shortcut_edit_summary_software; + } + return context.getText(resId); + } + protected CharSequence getShortcutTypeSummary(Context context) { if (!mShortcutPreference.isSettingsEditable()) { return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware); @@ -597,11 +609,8 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference mComponentName.flattenToString(), UserShortcutType.SOFTWARE); final List list = new ArrayList<>(); - final CharSequence softwareTitle = context.getText( - R.string.accessibility_shortcut_edit_summary_software); - if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) { - list.add(softwareTitle); + list.add(getSoftwareShortcutTypeSummary(context)); } if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) { final CharSequence hardwareTitle = context.getText( @@ -611,7 +620,7 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference // Show software shortcut if first time to use. if (list.isEmpty()) { - list.add(softwareTitle); + list.add(getSoftwareShortcutTypeSummary(context)); } return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */ diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index 4f25430e8d2..1c7ce39bab9 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -265,6 +265,18 @@ public class ToggleScreenMagnificationPreferenceFragment extends return (value & type) == type; } + private static CharSequence getSoftwareShortcutTypeSummary(Context context) { + int resId; + if (AccessibilityUtil.isFloatingMenuEnabled(context)) { + resId = R.string.accessibility_shortcut_edit_summary_software; + } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { + resId = R.string.accessibility_shortcut_edit_summary_software_gesture; + } else { + resId = R.string.accessibility_shortcut_edit_summary_software; + } + return context.getText(resId); + } + @Override protected CharSequence getShortcutTypeSummary(Context context) { if (!mShortcutPreference.isChecked()) { @@ -275,18 +287,14 @@ public class ToggleScreenMagnificationPreferenceFragment extends MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE); final List list = new ArrayList<>(); - final CharSequence softwareTitle = context.getText( - R.string.accessibility_shortcut_edit_summary_software); - if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) { - list.add(softwareTitle); + list.add(getSoftwareShortcutTypeSummary(context)); } if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) { final CharSequence hardwareTitle = context.getText( R.string.accessibility_shortcut_hardware_keyword); list.add(hardwareTitle); } - if (hasShortcutType(shortcutTypes, UserShortcutType.TRIPLETAP)) { final CharSequence tripleTapTitle = context.getText( R.string.accessibility_shortcut_triple_tap_keyword); @@ -295,7 +303,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends // Show software shortcut if first time to use. if (list.isEmpty()) { - list.add(softwareTitle); + list.add(getSoftwareShortcutTypeSummary(context)); } return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */ diff --git a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java index 4e783c96407..4b9cfa36f2f 100644 --- a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java +++ b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java @@ -17,6 +17,7 @@ package com.android.settings.gestures; import static android.os.UserHandle.USER_CURRENT; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; @@ -30,11 +31,14 @@ import android.content.om.OverlayInfo; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; +import android.text.TextUtils; +import android.view.accessibility.AccessibilityManager; import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.SettingsTutorialDialogWrapperActivity; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; @@ -173,7 +177,14 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment i protected boolean setDefaultKey(String key) { setCurrentSystemNavigationMode(mOverlayManager, key); setIllustrationVideo(mVideoPreference, key); - + if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, key) + && !isAccessibilityFloatingMenuEnabled() + && (isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) { + final Intent intent = new Intent(getActivity(), + SettingsTutorialDialogWrapperActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } return true; } @@ -246,6 +257,24 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment i } } + private boolean isAnyServiceSupportAccessibilityButton() { + final AccessibilityManager ams = getContext().getSystemService(AccessibilityManager.class); + final List targets = ams.getAccessibilityShortcutTargets( + AccessibilityManager.ACCESSIBILITY_BUTTON); + return !targets.isEmpty(); + } + + private boolean isNavBarMagnificationEnabled() { + return Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1; + } + + private boolean isAccessibilityFloatingMenuEnabled() { + return Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1) + == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.system_navigation_gesture_settings) { diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonFooterPreferenceControllerTest.java index 010b444f914..7354555a719 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonFooterPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonFooterPreferenceControllerTest.java @@ -16,6 +16,7 @@ package com.android.settings.accessibility; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.google.common.truth.Truth.assertThat; @@ -46,7 +47,6 @@ public class AccessibilityButtonFooterPreferenceControllerTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); - @Spy private final Context mContext = ApplicationProvider.getApplicationContext(); @Spy @@ -76,4 +76,15 @@ public class AccessibilityButtonFooterPreferenceControllerTest { assertThat(mPreference.getTitle()).isEqualTo( mContext.getText(R.string.accessibility_button_gesture_description)); } + + @Test + public void displayPreference_navigationGestureDisabled_setCorrectTitle() { + when(mResources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)) + .thenReturn(NAV_BAR_MODE_2BUTTON); + + mController.displayPreference(mScreen); + + assertThat(mPreference.getTitle()).isEqualTo( + mContext.getText(R.string.accessibility_button_description)); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceControllerTest.java new file mode 100644 index 00000000000..da442281e9e --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceControllerTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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 android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings; + +import androidx.preference.ListPreference; +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link AccessibilityButtonGesturePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class AccessibilityButtonGesturePreferenceControllerTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Spy + private final Resources mResources = mContext.getResources(); + private final ContentResolver mContentResolver = mContext.getContentResolver(); + private final ListPreference mListPreference = new ListPreference(mContext); + private AccessibilityButtonGesturePreferenceController mController; + + @Before + public void setUp() { + mController = new AccessibilityButtonGesturePreferenceController(mContext, + "test_key"); + when(mContext.getResources()).thenReturn(mResources); + } + + @Test + public void getAvailabilityStatus_navigationGestureEnabled_returnAvailable() { + when(mResources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)) + .thenReturn(NAV_BAR_MODE_GESTURAL); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_navigationGestureDisabled_returnConditionallyUnavailable() { + when(mResources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)) + .thenReturn(NAV_BAR_MODE_2BUTTON); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void updateState_a11yBtnModeGesture_navigationBarValue() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_GESTURE); + + mController.updateState(mListPreference); + + final String gestureValue = String.valueOf(ACCESSIBILITY_BUTTON_MODE_GESTURE); + assertThat(mListPreference.getValue()).isEqualTo(gestureValue); + } + + @Test + public void onPreferenceChange_a11yBtnModeFloatingMenu_floatingMenuValue() { + final String floatingMenuValue = String.valueOf(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + + mController.onPreferenceChange(mListPreference, floatingMenuValue); + + assertThat(mListPreference.getValue()).isEqualTo(floatingMenuValue); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreferenceControllerTest.java new file mode 100644 index 00000000000..03f7887b9d0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreferenceControllerTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 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 android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link AccessibilityButtonPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class AccessibilityButtonPreferenceControllerTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Spy + private final Resources mResources = mContext.getResources(); + @Mock + private PreferenceScreen mScreen; + private Preference mPreference; + private AccessibilityButtonPreferenceController mController; + + @Before + public void setUp() { + mController = new AccessibilityButtonPreferenceController(mContext, "test_key"); + mPreference = new Preference(mContext); + mPreference.setKey("test_key"); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + when(mContext.getResources()).thenReturn(mResources); + } + + @Test + public void displayPreference_navigationGestureEnabled_setCorrectTitle() { + when(mResources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)) + .thenReturn(NAV_BAR_MODE_GESTURAL); + + mController.displayPreference(mScreen); + + assertThat(mPreference.getTitle()).isEqualTo( + mContext.getText(R.string.accessibility_button_gesture_title)); + } + + @Test + public void displayPreference_navigationGestureDisabled_setCorrectTitle() { + when(mResources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)) + .thenReturn(NAV_BAR_MODE_2BUTTON); + + mController.displayPreference(mScreen); + + assertThat(mPreference.getTitle()).isEqualTo( + mContext.getText(R.string.accessibility_button_title)); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceControllerTest.java index 306503040ce..61808bd4c1b 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceControllerTest.java @@ -90,7 +90,7 @@ public class AccessibilityButtonPreviewPreferenceControllerTest { mController.mContentObserver.onChange(false); final Drawable smallFloatingMenuWithTenOpacityDrawable = - FloatingMenuLayerDrawable.createLayerDrawable(mContext, + AccessibilityLayerDrawable.createLayerDrawable(mContext, R.drawable.accessibility_button_preview_small_floating_menu, 10); assertThat(mController.mPreview.getDrawable().getConstantState()).isEqualTo( smallFloatingMenuWithTenOpacityDrawable.getConstantState()); diff --git a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuLayerDrawableTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityLayerDrawableTest.java similarity index 83% rename from tests/robotests/src/com/android/settings/accessibility/FloatingMenuLayerDrawableTest.java rename to tests/robotests/src/com/android/settings/accessibility/AccessibilityLayerDrawableTest.java index 45cefe482b0..915c788c3bd 100644 --- a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuLayerDrawableTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityLayerDrawableTest.java @@ -30,9 +30,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -/** Tests for {@link FloatingMenuLayerDrawable}. */ +/** Tests for {@link AccessibilityLayerDrawable}. */ @RunWith(RobolectricTestRunner.class) -public class FloatingMenuLayerDrawableTest { +public class AccessibilityLayerDrawableTest { private static final int TEST_RES_ID = com.android.internal.R.drawable.ic_accessibility_magnification; @@ -46,8 +46,8 @@ public class FloatingMenuLayerDrawableTest { R.drawable.accessibility_button_preview_base); final Drawable expected2ndDrawable = mContext.getDrawable(TEST_RES_ID); - final FloatingMenuLayerDrawable actualDrawable = - FloatingMenuLayerDrawable.createLayerDrawable(mContext, TEST_RES_ID, + final AccessibilityLayerDrawable actualDrawable = + AccessibilityLayerDrawable.createLayerDrawable(mContext, TEST_RES_ID, /* opacity= */ 27); final Drawable actual1stDrawable = actualDrawable.getDrawable(0); @@ -60,14 +60,14 @@ public class FloatingMenuLayerDrawableTest { @Test public void updateLayerDrawable_expectedFloatingMenuLayerDrawableState() { - final FloatingMenuLayerDrawable originalDrawable = - FloatingMenuLayerDrawable.createLayerDrawable(mContext, TEST_RES_ID, /* opacity= */ + final AccessibilityLayerDrawable originalDrawable = + AccessibilityLayerDrawable.createLayerDrawable(mContext, TEST_RES_ID, /* opacity= */ 72); originalDrawable.updateLayerDrawable(mContext, TEST_RES_ID_2, /* opacity= */ 27); assertThat(originalDrawable.getConstantState()).isEqualTo( - new FloatingMenuLayerDrawable.FloatingMenuLayerDrawableState(mContext, + new AccessibilityLayerDrawable.AccessibilityLayerDrawableState(mContext, TEST_RES_ID_2, /* opacity= */ 27)); } }