diff --git a/res/drawable/accessibility_button_navigation.xml b/res/drawable/accessibility_button_navigation.xml new file mode 100644 index 00000000000..82e3c70174f --- /dev/null +++ b/res/drawable/accessibility_button_navigation.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/res/drawable/accessibility_button_preview_base.xml b/res/drawable/accessibility_button_preview_base.xml new file mode 100644 index 00000000000..9e3ec598586 --- /dev/null +++ b/res/drawable/accessibility_button_preview_base.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + diff --git a/res/drawable/accessibility_button_preview_large_floating_menu.xml b/res/drawable/accessibility_button_preview_large_floating_menu.xml new file mode 100644 index 00000000000..e003dc7322c --- /dev/null +++ b/res/drawable/accessibility_button_preview_large_floating_menu.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/accessibility_button_preview_small_floating_menu.xml b/res/drawable/accessibility_button_preview_small_floating_menu.xml new file mode 100644 index 00000000000..3ff8e4b25f4 --- /dev/null +++ b/res/drawable/accessibility_button_preview_small_floating_menu.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/accessibility_shortcut_type_software_floating.xml b/res/drawable/accessibility_shortcut_type_software_floating.xml new file mode 100644 index 00000000000..958201515f1 --- /dev/null +++ b/res/drawable/accessibility_shortcut_type_software_floating.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/accessibility_button_preview.xml b/res/layout/accessibility_button_preview.xml new file mode 100644 index 00000000000..07cb0ffb8ab --- /dev/null +++ b/res/layout/accessibility_button_preview.xml @@ -0,0 +1,33 @@ + + + + + + + \ No newline at end of file diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 183dd1e579d..2057c50626f 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -966,6 +966,35 @@ -1 + + + Floating over other apps + Navigation bar + + + + + + + 1 + + 0 + + + + + Small + Large + + + + + + 0 + + 1 + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 4fef726d599..dbf21fc8626 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -57,6 +57,8 @@ 320dp + 200dp + 4dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 89665de59b9..75b7958bf3d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5106,6 +5106,8 @@ Use new accessibility gesture To use this feature, tap the accessibility button %s on the bottom of your screen.\n\nTo switch between features, touch & hold the accessibility button. + + To use this feature, tap the accessibility button on your screen. To use this feature, press & hold both volume keys. @@ -5136,6 +5138,8 @@ Swipe up from the bottom of the screen with 2 fingers.\n\nTo switch between features, swipe up with 2 fingers and hold. Swipe up from the bottom of the screen with 3 fingers.\n\nTo switch between features, swipe up with 3 fingers and hold. + + Customize accessibility button Hold volume keys @@ -5164,6 +5168,26 @@ Shortcut from lock screen Allow feature shortcut to turn on from the lock screen. Hold both volume keys for a few seconds. + + Accessibility button + + 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. + + Location + + Size + + Fade when not in use + + Fades after a few seconds so it\u2019s easier to see your screen + + Transparency when not in use + + Transparent + + Non-transparent High contrast text diff --git a/res/xml/accessibility_button_settings.xml b/res/xml/accessibility_button_settings.xml new file mode 100644 index 00000000000..5e81616bffc --- /dev/null +++ b/res/xml/accessibility_button_settings.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/accessibility_shortcuts_settings.xml b/res/xml/accessibility_shortcuts_settings.xml index 35314e708bb..465f96d74df 100644 --- a/res/xml/accessibility_shortcuts_settings.xml +++ b/res/xml/accessibility_shortcuts_settings.xml @@ -21,6 +21,13 @@ android:persistent="false" android:title="@string/accessibility_shortcuts_settings_title"> + + mValueTitleMap = new ArrayMap<>(); + private int mDefaultLocation; + + public AccessibilityButtonLocationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + initValueTitleMap(); + } + + @Override + public int getAvailabilityStatus() { + return AccessibilityUtil.isGestureNavigateEnabled(mContext) + ? DISABLED_DEPENDENT_SETTING : AVAILABLE; + } + + @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, mDefaultLocation); + return String.valueOf(mode); + } + + private void initValueTitleMap() { + if (mValueTitleMap.size() == 0) { + final String[] values = 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]); + } + } + } +} diff --git a/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java new file mode 100644 index 00000000000..69a7a46f0c3 --- /dev/null +++ b/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java @@ -0,0 +1,126 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.widget.ImageView; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.widget.LayoutPreference; + +/** Preference controller that controls the preview effect in accessibility button page. */ +public class AccessibilityButtonPreviewPreferenceController extends BasePreferenceController + implements LifecycleObserver, OnResume, OnPause { + + private static final int SMALL_SIZE = 0; + private static final float DEFAULT_OPACITY = 0.55f; + private static final int DEFAULT_SIZE = 0; + + private final ContentResolver mContentResolver; + @VisibleForTesting + final ContentObserver mContentObserver; + private FloatingMenuLayerDrawable mFloatingMenuPreviewDrawable; + + @VisibleForTesting + ImageView mPreview; + + public AccessibilityButtonPreviewPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mContentResolver = context.getContentResolver(); + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updatePreviewPreference(); + } + }; + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final LayoutPreference preference = screen.findPreference(getPreferenceKey()); + mPreview = preference.findViewById(R.id.preview_image); + + updatePreviewPreference(); + } + + @Override + public void onResume() { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE), + /* notifyForDescendants= */ false, mContentObserver); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE), + /* notifyForDescendants= */ false, mContentObserver); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY), + /* notifyForDescendants= */ false, mContentObserver); + } + + @Override + public void onPause() { + mContentResolver.unregisterContentObserver(mContentObserver); + } + + private void updatePreviewPreference() { + if (AccessibilityUtil.isFloatingMenuEnabled(mContext)) { + final int size = Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, DEFAULT_SIZE); + final int opacity = (int) (Settings.Secure.getFloat(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY) * 100); + 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.invalidate(); + } else { + mPreview.setImageDrawable( + mContext.getDrawable(R.drawable.accessibility_button_navigation)); + } + } + + private Drawable getFloatingMenuPreviewDrawable(int resId, int opacity) { + if (mFloatingMenuPreviewDrawable == null) { + mFloatingMenuPreviewDrawable = FloatingMenuLayerDrawable.createLayerDrawable( + mContext, resId, opacity); + } else { + mFloatingMenuPreviewDrawable.updateLayerDrawable(mContext, resId, opacity); + } + + return mFloatingMenuPreviewDrawable; + } +} diff --git a/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java index f349a125085..6b31988ecff 100644 --- a/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java +++ b/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java @@ -17,6 +17,7 @@ package com.android.settings.accessibility; import android.app.Dialog; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; @@ -24,6 +25,7 @@ import android.graphics.drawable.Drawable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; +import android.text.method.LinkMovementMethod; import android.text.style.ImageSpan; import android.view.LayoutInflater; import android.view.View; @@ -40,6 +42,8 @@ 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; @@ -253,6 +257,8 @@ public class AccessibilityEditDialogUtils { 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); @@ -260,10 +266,13 @@ public class AccessibilityEditDialogUtils { 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, retrieveTitle(context), - retrieveSummary(context, lineHeight), retrieveImageResId(context)); + + setupShortcutWidget(dialogView, title, retrieveSummary(context, lineHeight), + retrieveImageResId(context)); } private static void initHardwareShortcut(Context context, View view) { @@ -297,35 +306,28 @@ public class AccessibilityEditDialogUtils { }); } - private static CharSequence retrieveTitle(Context context) { - int resId = R.string.accessibility_shortcut_edit_dialog_title_software; - if (AccessibilityUtil.isGestureNavigateEnabled(context)) { - resId = AccessibilityUtil.isTouchExploreEnabled(context) - ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback - : R.string.accessibility_shortcut_edit_dialog_title_software_gesture; - } - return context.getText(resId); - } - private static CharSequence retrieveSummary(Context context, int lineHeight) { - 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; - return context.getText(resId); - } - return getSummaryStringWithIcon(context, lineHeight); + return AccessibilityUtil.isFloatingMenuEnabled(context) + ? getSummaryStringWithLink(context) : getSummaryStringWithIcon(context, lineHeight); } private static int retrieveImageResId(Context context) { - // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted. - 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 resId; + return AccessibilityUtil.isFloatingMenuEnabled(context) + ? R.drawable.accessibility_shortcut_type_software_floating + : R.drawable.accessibility_shortcut_type_software; + } + + private static CharSequence getSummaryStringWithLink(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) { diff --git a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java index 482822e4f66..5ea5462c0c8 100644 --- a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java +++ b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java @@ -333,7 +333,8 @@ public final class AccessibilityGestureNavigationTutorial { } private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) { - final CharSequence title = getSoftwareTitle(context); + final CharSequence title = context.getText( + R.string.accessibility_tutorial_dialog_title_button); final ImageView image = createSoftwareImage(context); final CharSequence instruction = getSoftwareInstruction(context); final ImageView indicatorIcon = @@ -390,44 +391,19 @@ public final class AccessibilityGestureNavigationTutorial { return tutorialPages; } - private static CharSequence getSoftwareTitle(Context context) { - final boolean isGestureNavigationEnabled = - AccessibilityUtil.isGestureNavigateEnabled(context); - final int resId = isGestureNavigationEnabled - ? R.string.accessibility_tutorial_dialog_title_gesture - : R.string.accessibility_tutorial_dialog_title_button; - - return context.getText(resId); - } - 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; - } + final int resId = AccessibilityUtil.isFloatingMenuEnabled(context) + ? R.drawable.accessibility_shortcut_type_software_floating + : R.drawable.accessibility_shortcut_type_software; 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; + 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)); } private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) { diff --git a/src/com/android/settings/accessibility/AccessibilityUtil.java b/src/com/android/settings/accessibility/AccessibilityUtil.java index f5472098f90..5c316a40acd 100644 --- a/src/com/android/settings/accessibility/AccessibilityUtil.java +++ b/src/com/android/settings/accessibility/AccessibilityUtil.java @@ -16,6 +16,7 @@ package com.android.settings.accessibility; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import android.accessibilityservice.AccessibilityServiceInfo; @@ -143,6 +144,13 @@ final class AccessibilityUtil { == NAV_BAR_MODE_GESTURAL; } + /** Determines if a accessibility floating menu is being used. */ + public static boolean isFloatingMenuEnabled(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1) + == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; + } + /** Determines if a touch explore is being used. */ public static boolean isTouchExploreEnabled(Context context) { final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); diff --git a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java new file mode 100644 index 00000000000..dd419d0a6e5 --- /dev/null +++ b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java @@ -0,0 +1,116 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +/** Preference controller that controls the fade switch button in accessibility button page. */ +public class FloatingMenuFadePreferenceController extends BasePreferenceController implements + Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause { + + private static final int OFF = 0; + private static final int ON = 1; + + private final ContentResolver mContentResolver; + @VisibleForTesting + final ContentObserver mContentObserver; + + @VisibleForTesting + SwitchPreference mPreference; + + public FloatingMenuFadePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mContentResolver = context.getContentResolver(); + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updateAvailabilityStatus(); + } + }; + } + + @Override + public int getAvailabilityStatus() { + return AccessibilityUtil.isFloatingMenuEnabled(mContext) + ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean isEnabled = (boolean) newValue; + putFloatingMenuFadeValue(isEnabled); + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + final SwitchPreference switchPreference = (SwitchPreference) preference; + + switchPreference.setChecked(getFloatingMenuFadeValue() == ON); + } + + @Override + public void onResume() { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_BUTTON_MODE), + /* notifyForDescendants= */ false, mContentObserver); + } + + @Override + public void onPause() { + mContentResolver.unregisterContentObserver(mContentObserver); + } + + private void updateAvailabilityStatus() { + mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext)); + } + + private int getFloatingMenuFadeValue() { + return Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, ON); + } + + private void putFloatingMenuFadeValue(boolean isEnabled) { + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, + isEnabled ? ON : OFF); + } +} diff --git a/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java b/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java new file mode 100644 index 00000000000..bfce114ae13 --- /dev/null +++ b/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java @@ -0,0 +1,133 @@ +/* + * 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 android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +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 { + + private FloatingMenuLayerDrawableState mState; + + /** + * Creates a new layer drawable with the list of specified layers. + * + * @param layers a list of drawables to use as layers in this new drawable, + * must be non-null + */ + private FloatingMenuLayerDrawable(@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. + * + * @param context the valid context used to get the icon + * @param resId the resource ID of the floating menu icon + * @param opacity the opacity to apply to the given icon + * @return the drawable that combines the device icon and the floating menu icon + */ + public static FloatingMenuLayerDrawable createLayerDrawable(Context context, int resId, + int opacity) { + final Drawable bg = context.getDrawable(R.drawable.accessibility_button_preview_base); + final FloatingMenuLayerDrawable basicDrawable = new FloatingMenuLayerDrawable( + new Drawable[]{bg, null}); + + basicDrawable.updateLayerDrawable(context, resId, opacity); + return basicDrawable; + } + + /** + * Update the drawable with given {@code resId} drawable and {@code opacity}(alpha) + * 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 opacity the opacity to apply to the given icon + */ + public void updateLayerDrawable(Context context, int resId, int opacity) { + final Drawable icon = context.getDrawable(resId); + icon.setAlpha(opacity); + this.setDrawable(/* index= */ 1, icon); + this.setConstantState(context, resId, opacity); + } + + @Override + public ConstantState getConstantState() { + return mState; + } + + /** 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); + } + + /** {@link ConstantState} to store the data of {@link FloatingMenuLayerDrawable}. */ + @VisibleForTesting + static class FloatingMenuLayerDrawableState extends ConstantState { + + private final Context mContext; + private final int mResId; + private final int mOpacity; + + FloatingMenuLayerDrawableState(Context context, int resId, int opacity) { + mContext = context; + mResId = resId; + mOpacity = opacity; + } + + @NonNull + @Override + public Drawable newDrawable() { + return createLayerDrawable(mContext, mResId, mOpacity); + } + + @Override + public int getChangingConfigurations() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final FloatingMenuLayerDrawableState that = (FloatingMenuLayerDrawableState) o; + return mResId == that.mResId + && mOpacity == that.mOpacity + && Objects.equals(mContext, that.mContext); + } + + @Override + public int hashCode() { + return Objects.hash(mContext, mResId, mOpacity); + } + } +} diff --git a/src/com/android/settings/accessibility/FloatingMenuOpacityPreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuOpacityPreferenceController.java new file mode 100644 index 00000000000..fea6fb66c72 --- /dev/null +++ b/src/com/android/settings/accessibility/FloatingMenuOpacityPreferenceController.java @@ -0,0 +1,150 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.SliderPreferenceController; +import com.android.settings.widget.SeekBarPreference; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +/** Preference controller that controls the opacity seekbar in accessibility button page. */ +public class FloatingMenuOpacityPreferenceController extends SliderPreferenceController + implements LifecycleObserver, OnResume, OnPause { + + @VisibleForTesting + static final float DEFAULT_OPACITY = 0.55f; + private static final int FADE_ENABLED = 1; + private static final float MIN_PROGRESS = 10f; + private static final float MAX_PROGRESS = 100f; + @VisibleForTesting + static final float PRECISION = 100f; + + private final ContentResolver mContentResolver; + @VisibleForTesting + final ContentObserver mContentObserver; + + @VisibleForTesting + SeekBarPreference mPreference; + + public FloatingMenuOpacityPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + mContentResolver = context.getContentResolver(); + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updateAvailabilityStatus(); + } + }; + } + + @Override + public int getAvailabilityStatus() { + return AccessibilityUtil.isFloatingMenuEnabled(mContext) + ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = screen.findPreference(getPreferenceKey()); + mPreference.setContinuousUpdates(true); + mPreference.setMax(getMax()); + mPreference.setMin(getMin()); + mPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS); + + updateState(mPreference); + } + + @Override + public void onResume() { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */ + false, mContentObserver); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED), + /* notifyForDescendants= */ false, mContentObserver); + } + + @Override + public void onPause() { + mContentResolver.unregisterContentObserver(mContentObserver); + } + + @Override + public int getSliderPosition() { + return convertOpacityFloatToInt(getOpacity()); + } + + @Override + public boolean setSliderPosition(int position) { + final float value = convertOpacityIntToFloat(position); + + return Settings.Secure.putFloat(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, value); + } + + @Override + public int getMax() { + return (int) MAX_PROGRESS; + } + + @Override + public int getMin() { + return (int) MIN_PROGRESS; + } + + private void updateAvailabilityStatus() { + final boolean fadeEnabled = Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, FADE_ENABLED) + == FADE_ENABLED; + + mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext) && fadeEnabled); + } + + private int convertOpacityFloatToInt(float value) { + return Math.round(value * PRECISION); + } + + private float convertOpacityIntToFloat(int value) { + return (float) value / PRECISION; + } + + private float getOpacity() { + float value = Settings.Secure.getFloat(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY); + final float minValue = MIN_PROGRESS / PRECISION; + final float maxValue = MAX_PROGRESS / PRECISION; + + return (value < minValue || value > maxValue) ? DEFAULT_OPACITY : value; + } +} + diff --git a/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java new file mode 100644 index 00000000000..2f0f833772e --- /dev/null +++ b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java @@ -0,0 +1,157 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.util.ArrayMap; + +import androidx.annotation.IntDef; +import androidx.annotation.VisibleForTesting; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +import com.google.common.primitives.Ints; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Preference controller that controls the preferred size in accessibility button page. */ +public class FloatingMenuSizePreferenceController extends BasePreferenceController + implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause { + + private final ContentResolver mContentResolver; + @VisibleForTesting + final ContentObserver mContentObserver; + + @VisibleForTesting + ListPreference mPreference; + + private final ArrayMap mValueTitleMap = new ArrayMap<>(); + private int mDefaultSize; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + Size.SMALL, + Size.LARGE, + }) + @VisibleForTesting + @interface Size { + int SMALL = 0; + int LARGE = 1; + } + + public FloatingMenuSizePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mContentResolver = context.getContentResolver(); + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updateAvailabilityStatus(); + } + }; + + initValueTitleMap(); + } + + @Override + public int getAvailabilityStatus() { + return AccessibilityUtil.isFloatingMenuEnabled(mContext) + ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final ListPreference listPreference = (ListPreference) preference; + final Integer value = Ints.tryParse((String) newValue); + if (value != null) { + putAccessibilityFloatingMenuSize(value); + updateState(listPreference); + } + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + final ListPreference listPreference = (ListPreference) preference; + + listPreference.setValue(String.valueOf(getAccessibilityFloatingMenuSize(mDefaultSize))); + } + + @Override + public void onResume() { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */ + false, mContentObserver); + + } + + @Override + public void onPause() { + mContentResolver.unregisterContentObserver(mContentObserver); + } + + private void updateAvailabilityStatus() { + mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext)); + } + + private void initValueTitleMap() { + if (mValueTitleMap.size() == 0) { + final String[] values = mContext.getResources().getStringArray( + R.array.accessibility_button_size_selector_values); + final String[] titles = mContext.getResources().getStringArray( + R.array.accessibility_button_size_selector_titles); + final int mapSize = values.length; + + mDefaultSize = Integer.parseInt(values[0]); + for (int i = 0; i < mapSize; i++) { + mValueTitleMap.put(values[i], titles[i]); + } + } + } + + @Size + private int getAccessibilityFloatingMenuSize(@Size int defaultValue) { + return Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, defaultValue); + } + + private void putAccessibilityFloatingMenuSize(@Size int value) { + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, value); + } +} diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index ec22a28aeef..cf9c08b73fd 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -608,19 +608,15 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context, mComponentName.flattenToString(), UserShortcutType.SOFTWARE); - int resId = R.string.accessibility_shortcut_edit_summary_software; - if (AccessibilityUtil.isGestureNavigateEnabled(context)) { - resId = AccessibilityUtil.isTouchExploreEnabled(context) - ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback - : R.string.accessibility_shortcut_edit_dialog_title_software_gesture; - } - final CharSequence softwareTitle = context.getText(resId); - List list = new ArrayList<>(); - if ((shortcutTypes & UserShortcutType.SOFTWARE) == 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); } - if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) { + if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) { final CharSequence hardwareTitle = context.getText( R.string.accessibility_shortcut_hardware_keyword); list.add(hardwareTitle); diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index dde5be17d74..738d284bda0 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -226,27 +226,23 @@ public class ToggleScreenMagnificationPreferenceFragment extends return context.getText(R.string.switch_off_text); } - final int shortcutType = PreferredShortcuts.retrieveUserShortcutType(context, + final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context, MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE); - int resId = R.string.accessibility_shortcut_edit_summary_software; - if (AccessibilityUtil.isGestureNavigateEnabled(context)) { - resId = AccessibilityUtil.isTouchExploreEnabled(context) - ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback - : R.string.accessibility_shortcut_edit_dialog_title_software_gesture; - } - final CharSequence softwareTitle = context.getText(resId); - List list = new ArrayList<>(); - if ((shortcutType & UserShortcutType.SOFTWARE) == 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); } - if ((shortcutType & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) { + if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) { final CharSequence hardwareTitle = context.getText( R.string.accessibility_shortcut_hardware_keyword); list.add(hardwareTitle); } - if ((shortcutType & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) { + if (hasShortcutType(shortcutTypes, UserShortcutType.TRIPLETAP)) { final CharSequence tripleTapTitle = context.getText( R.string.accessibility_shortcut_triple_tap_keyword); list.add(tripleTapTitle); diff --git a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java index 14fe6a664be..388d87ae9a9 100644 --- a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java +++ b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java @@ -30,15 +30,12 @@ import android.content.om.OverlayInfo; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; -import android.text.TextUtils; import android.util.FeatureFlagUtils; -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.core.FeatureFlags; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.overlay.FeatureFactory; @@ -188,12 +185,7 @@ 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) && ( - isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) { - Intent intent = new Intent(getActivity(), SettingsTutorialDialogWrapperActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } + return true; } @@ -267,18 +259,6 @@ 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; - } - 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/AccessibilityButtonFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonFragmentTest.java new file mode 100644 index 00000000000..473b56687bf --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonFragmentTest.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 static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.testutils.XmlTestUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.List; + +/** Tests for {@link AccessibilityButtonFragment}. */ +@RunWith(RobolectricTestRunner.class) +public class AccessibilityButtonFragmentTest { + + private Context mContext = ApplicationProvider.getApplicationContext(); + + @Test + public void getNonIndexableKeys_existInXmlLayout() { + final List niks = AccessibilityButtonFragment.SEARCH_INDEX_DATA_PROVIDER + .getNonIndexableKeys(mContext); + final List keys = + XmlTestUtils.getKeysFromPreferenceXml(mContext, + R.xml.accessibility_button_settings); + + assertThat(keys).containsAtLeastElementsIn(niks); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceControllerTest.java new file mode 100644 index 00000000000..a67038a6b2d --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceControllerTest.java @@ -0,0 +1,106 @@ +/* + * 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_NAVIGATION_BAR; +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.DISABLED_DEPENDENT_SETTING; + +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 AccessibilityButtonLocationPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class AccessibilityButtonLocationPreferenceControllerTest { + + @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 AccessibilityButtonLocationPreferenceController mController; + + + @Before + public void setUp() { + mController = new AccessibilityButtonLocationPreferenceController(mContext, + "test_key"); + when(mContext.getResources()).thenReturn(mResources); + } + + @Test + public void getAvailabilityStatus_navigationGestureEnabled_returnDisabledDependentSetting() { + when(mResources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)) + .thenReturn(NAV_BAR_MODE_GESTURAL); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + public void getAvailabilityStatus_navigationGestureDisabled_returnAvailable() { + when(mResources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)) + .thenReturn(NAV_BAR_MODE_2BUTTON); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void updateState_a11yBtnModeNavigationBar_navigationBarValue() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + mController.updateState(mListPreference); + + final String navigationBarValue = String.valueOf(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + assertThat(mListPreference.getValue()).isEqualTo(navigationBarValue); + } + + @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/AccessibilityButtonPreviewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceControllerTest.java new file mode 100644 index 00000000000..eb881757b28 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceControllerTest.java @@ -0,0 +1,120 @@ +/* + * 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_NAVIGATION_BAR; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.provider.Settings; +import android.widget.ImageView; + +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 AccessibilityButtonPreviewPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class AccessibilityButtonPreviewPreferenceControllerTest { + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private ContentResolver mContentResolver; + private AccessibilityButtonPreviewPreferenceController mController; + + @Before + public void setUp() { + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mController = new AccessibilityButtonPreviewPreferenceController(mContext, "test_key"); + mController.mPreview = new ImageView(mContext); + } + + @Test + public void onChange_a11yBtnModeNavigationBar_getNavigationBarDrawable() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + mController.mContentObserver.onChange(false); + + final Drawable navigationBarDrawable = mContext.getDrawable( + R.drawable.accessibility_button_navigation); + assertThat(mController.mPreview.getDrawable().getConstantState()).isEqualTo( + navigationBarDrawable.getConstantState()); + } + + @Test + public void onChange_updatePreviewPreferenceWithConfig_expectedPreviewDrawable() { + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, /* small size */ 0); + Settings.Secure.putFloat(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, 0.1f); + + mController.mContentObserver.onChange(false); + + final Drawable smallFloatingMenuWithTenOpacityDrawable = + FloatingMenuLayerDrawable.createLayerDrawable(mContext, + R.drawable.accessibility_button_preview_small_floating_menu, 10); + assertThat(mController.mPreview.getDrawable().getConstantState()).isEqualTo( + smallFloatingMenuWithTenOpacityDrawable.getConstantState()); + } + + @Test + public void onResume_registerSpecificContentObserver() { + mController.onResume(); + + verify(mContentResolver).registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE), false, + mController.mContentObserver); + verify(mContentResolver).registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE), false, + mController.mContentObserver); + verify(mContentResolver).registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY), + false, + mController.mContentObserver); + } + + @Test + public void onPause_unregisterContentObserver() { + mController.onPause(); + + verify(mContentResolver).unregisterContentObserver(mController.mContentObserver); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuFadePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuFadePreferenceControllerTest.java new file mode 100644 index 00000000000..5cf87ee61a1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuFadePreferenceControllerTest.java @@ -0,0 +1,131 @@ +/* + * 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_NAVIGATION_BAR; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.SwitchPreference; +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.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link FloatingMenuFadePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class FloatingMenuFadePreferenceControllerTest { + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + private static final int OFF = 0; + private static final int ON = 1; + + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private ContentResolver mContentResolver; + private final SwitchPreference mSwitchPreference = new SwitchPreference(mContext); + private FloatingMenuFadePreferenceController mController; + + @Before + public void setUp() { + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mController = new FloatingMenuFadePreferenceController(mContext, "test_key"); + } + + @Test + public void getAvailabilityStatus_a11yBtnModeFloatingMenu_returnAvailable() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_a11yBtnModeNavigationBar_returnDisabledDependentSetting() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + public void updateState_keyFloatingMenuFadeDisabled_fadeIsDisabled() { + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, OFF); + + mController.updateState(mSwitchPreference); + + assertThat(mSwitchPreference.isChecked()).isFalse(); + } + + @Test + public void onPreferenceChange_floatingMenuFadeEnabled_keyFloatingMenuFadeIsOn() { + mController.onPreferenceChange(mSwitchPreference, Boolean.TRUE); + + final int actualValue = Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, OFF); + assertThat(actualValue).isEqualTo(ON); + } + + @Test + public void onChange_floatingMenuFadeChangeToDisabled_preferenceDisabled() { + mController.mPreference = mSwitchPreference; + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, OFF); + + mController.mContentObserver.onChange(false); + + assertThat(mController.mPreference.isEnabled()).isFalse(); + } + + @Test + public void onResume_registerSpecificContentObserver() { + mController.onResume(); + + verify(mContentResolver).registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE), false, + mController.mContentObserver); + } + + @Test + public void onPause_unregisterContentObserver() { + mController.onPause(); + + verify(mContentResolver).unregisterContentObserver(mController.mContentObserver); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuLayerDrawableTest.java b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuLayerDrawableTest.java new file mode 100644 index 00000000000..ec449d249b5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuLayerDrawableTest.java @@ -0,0 +1,73 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link FloatingMenuLayerDrawable}. */ +@RunWith(RobolectricTestRunner.class) +public class FloatingMenuLayerDrawableTest { + + private static final int TEST_RES_ID = + com.android.internal.R.drawable.ic_accessibility_magnification; + private static final int TEST_RES_ID_2 = + com.android.internal.R.drawable.ic_accessibility_color_inversion; + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Test + public void createLayerDrawable_configCorrect() { + final Drawable expected1stDrawable = mContext.getDrawable( + R.drawable.accessibility_button_preview_base); + final Drawable expected2ndDrawable = mContext.getDrawable(TEST_RES_ID); + + final FloatingMenuLayerDrawable actualDrawable = + FloatingMenuLayerDrawable.createLayerDrawable(mContext, TEST_RES_ID, + /* opacity= */ 27); + + final Drawable actual1stDrawable = actualDrawable.getDrawable(0); + final Drawable actual2ndDrawable = actualDrawable.getDrawable(1); + // These are VectorDrawables, so it can use getConstantState() to compare. + assertThat(actual1stDrawable.getConstantState()).isEqualTo( + expected1stDrawable.getConstantState()); + assertThat(actual2ndDrawable.getConstantState()).isEqualTo( + expected2ndDrawable.getConstantState()); + } + + @Test + public void updateLayerDrawable_expectedFloatingMenuLayerDrawableState() { + final FloatingMenuLayerDrawable originalDrawable = + FloatingMenuLayerDrawable.createLayerDrawable(mContext, TEST_RES_ID, /* opacity= */ + 72); + + originalDrawable.updateLayerDrawable(mContext, TEST_RES_ID_2, /* opacity= */ 27); + + assertThat(originalDrawable.getConstantState()).isEqualTo( + new FloatingMenuLayerDrawable.FloatingMenuLayerDrawableState(mContext, + TEST_RES_ID_2, /* opacity= */ 27)); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuOpacityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuOpacityPreferenceControllerTest.java new file mode 100644 index 00000000000..1638f907407 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuOpacityPreferenceControllerTest.java @@ -0,0 +1,141 @@ +/* + * 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_NAVIGATION_BAR; + +import static com.android.settings.accessibility.FloatingMenuOpacityPreferenceController.DEFAULT_OPACITY; +import static com.android.settings.accessibility.FloatingMenuOpacityPreferenceController.PRECISION; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.widget.SeekBarPreference; + +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 FloatingMenuOpacityPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class FloatingMenuOpacityPreferenceControllerTest { + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private ContentResolver mContentResolver; + private FloatingMenuOpacityPreferenceController mController; + + @Before + public void setUp() { + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mController = new FloatingMenuOpacityPreferenceController(mContext, "test_key"); + } + + @Test + public void getAvailabilityStatus_a11yBtnModeFloatingMenu_returnAvailable() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_a11yBtnModeNavigationBar_returnDisabledDependentSetting() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + public void onChange_a11yBtnModeChangeToNavigationBar_preferenceDisabled() { + mController.mPreference = new SeekBarPreference(mContext); + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + mController.mContentObserver.onChange(false); + + assertThat(mController.mPreference.isEnabled()).isFalse(); + } + + @Test + public void getSliderPosition_putNormalOpacityValue_expectedValue() { + Settings.Secure.putFloat(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, 0.35f); + + assertThat(mController.getSliderPosition()).isEqualTo(35); + } + + @Test + public void getSliderPosition_putOutOfBoundOpacityValue_defaultValue() { + Settings.Secure.putFloat(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, 0.01f); + + final int defaultValue = Math.round(DEFAULT_OPACITY * PRECISION); + assertThat(mController.getSliderPosition()).isEqualTo(defaultValue); + } + + @Test + public void setSliderPosition_expectedValue() { + mController.setSliderPosition(27); + + final float value = Settings.Secure.getFloat(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, -1); + assertThat(value).isEqualTo(0.27f); + } + + @Test + public void onResume_registerSpecificContentObserver() { + mController.onResume(); + + verify(mContentResolver).registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE), false, + mController.mContentObserver); + verify(mContentResolver).registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED), + false, + mController.mContentObserver); + } + + @Test + public void onPause_unregisterContentObserver() { + mController.onPause(); + + verify(mContentResolver).unregisterContentObserver(mController.mContentObserver); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuSizePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuSizePreferenceControllerTest.java new file mode 100644 index 00000000000..4d7d98d7aff --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuSizePreferenceControllerTest.java @@ -0,0 +1,120 @@ +/* + * 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_NAVIGATION_BAR; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +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.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link FloatingMenuSizePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class FloatingMenuSizePreferenceControllerTest { + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private ContentResolver mContentResolver; + private final ListPreference mListPreference = new ListPreference(mContext); + private FloatingMenuSizePreferenceController mController; + + @Before + public void setUp() { + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mController = new FloatingMenuSizePreferenceController(mContext, "test_key"); + } + + @Test + public void getAvailabilityStatus_a11yBtnModeFloatingMenu_returnAvailable() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_a11yBtnModeNavigationBar_returnDisabledDependentSetting() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + public void updateState_floatingMenuLargeSizeAndFullCircle_largeSizeValue() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, + FloatingMenuSizePreferenceController.Size.LARGE); + + mController.updateState(mListPreference); + + final String largeSize = String.valueOf(FloatingMenuSizePreferenceController.Size.LARGE); + assertThat(mListPreference.getValue()).isEqualTo(largeSize); + } + + @Test + public void onChange_a11yBtnModeChangeToNavigationBar_preferenceDisabled() { + mController.mPreference = mListPreference; + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + mController.mContentObserver.onChange(false); + + assertThat(mController.mPreference.isEnabled()).isFalse(); + } + + @Test + public void onResume_registerSpecificContentObserver() { + mController.onResume(); + + verify(mContentResolver).registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE), false, + mController.mContentObserver); + } + + @Test + public void onPause_unregisterContentObserver() { + mController.onPause(); + + verify(mContentResolver).unregisterContentObserver(mController.mContentObserver); + } +}