diff --git a/res/drawable/accessibility_button_navigation.xml b/res/drawable/accessibility_button_navigation.xml
new file mode 100644
index 00000000000..30273fb4e1f
--- /dev/null
+++ b/res/drawable/accessibility_button_navigation.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/accessibility_button_preview_base.xml b/res/drawable/accessibility_button_preview_base.xml
new file mode 100644
index 00000000000..0712b9508b1
--- /dev/null
+++ b/res/drawable/accessibility_button_preview_base.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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/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..2d03c11227e 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -966,6 +966,38 @@
- -1
+
+
+ - Floating over other apps
+ - Navigation bar
+
+
+
+
+
+
+ - 1
+
+ - 0
+
+
+
+
+ - Small
+ - Large
+ - Half Circle
+
+
+
+
+
+ - 0
+
+ - 1
+
+ - 2
+
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f78120c7e78..bde52886e69 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -170,6 +170,7 @@
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 16db9088b53..79d54cf1d79 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 ed8e2302e6e..bee607c03fa 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5141,6 +5141,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/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..b4fa495f071
--- /dev/null
+++ b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
@@ -0,0 +1,199 @@
+/*
+ * 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,
+ Size.EDGE
+ })
+ @VisibleForTesting
+ @interface Size {
+ int SMALL = 0;
+ int LARGE = 1;
+ int EDGE = 2;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ EdgeMode.FULL_CIRCLE,
+ EdgeMode.HALF_CIRCLE,
+ })
+ @VisibleForTesting
+ @interface EdgeMode {
+ int FULL_CIRCLE = 0;
+ int HALF_CIRCLE = 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) {
+ writeToFloatingMenuSettings(value);
+ updateState(listPreference);
+ }
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ final ListPreference listPreference = (ListPreference) preference;
+
+ listPreference.setValue(getCurrentAccessibilityButtonSize());
+ }
+
+ @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]);
+ }
+ }
+ }
+
+ private void writeToFloatingMenuSettings(@Size int sizeValue) {
+ if (sizeValue == Size.EDGE) {
+ putAccessibilityFloatingMenuSize(Size.SMALL);
+ putAccessibilityFloatingMenuIconType(EdgeMode.HALF_CIRCLE);
+ } else {
+ putAccessibilityFloatingMenuSize(sizeValue);
+ putAccessibilityFloatingMenuIconType(EdgeMode.FULL_CIRCLE);
+ }
+ }
+
+ private String getCurrentAccessibilityButtonSize() {
+ final @EdgeMode int iconType = getAccessibilityFloatingMenuIconType(EdgeMode.FULL_CIRCLE);
+ final @Size int btnSize = getAccessibilityFloatingMenuSize(mDefaultSize);
+
+ return (iconType == EdgeMode.HALF_CIRCLE)
+ ? String.valueOf(Size.EDGE) : String.valueOf(btnSize);
+ }
+
+ @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);
+ }
+
+ @EdgeMode
+ private int getAccessibilityFloatingMenuIconType(@EdgeMode int defaultValue) {
+ return Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE, defaultValue);
+ }
+
+ private void putAccessibilityFloatingMenuIconType(@EdgeMode int value) {
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE, value);
+ }
+}
diff --git a/src/com/android/settings/widget/LabeledSeekBarPreference.java b/src/com/android/settings/widget/LabeledSeekBarPreference.java
index e2b2d686988..3a9ac18beb0 100644
--- a/src/com/android/settings/widget/LabeledSeekBarPreference.java
+++ b/src/com/android/settings/widget/LabeledSeekBarPreference.java
@@ -18,6 +18,7 @@ package com.android.settings.widget;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -32,6 +33,7 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
private final int mTextStartId;
private final int mTextEndId;
+ private final int mTickMarkId;
private OnPreferenceChangeListener mStopListener;
public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
@@ -48,6 +50,8 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
mTextEndId = styledAttrs.getResourceId(
R.styleable.LabeledSeekBarPreference_textEnd,
R.string.summary_placeholder);
+ mTickMarkId = styledAttrs.getResourceId(
+ R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0);
styledAttrs.recycle();
}
@@ -65,6 +69,13 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
final TextView endText = (TextView) holder.findViewById(android.R.id.text2);
startText.setText(mTextStartId);
endText.setText(mTextEndId);
+
+ if (mTickMarkId != 0) {
+ final Drawable tickMark = getContext().getDrawable(mTickMarkId);
+ final SeekBar seekBar = (SeekBar) holder.findViewById(
+ com.android.internal.R.id.seekbar);
+ seekBar.setTickMark(tickMark);
+ }
}
public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) {
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..70257ca6931
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuSizePreferenceControllerTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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);
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
+ FloatingMenuSizePreferenceController.EdgeMode.FULL_CIRCLE);
+
+ mController.updateState(mListPreference);
+
+ final String largeSize = String.valueOf(FloatingMenuSizePreferenceController.Size.LARGE);
+ assertThat(mListPreference.getValue()).isEqualTo(largeSize);
+ }
+
+ @Test
+ public void updateState_floatingMenuHalfCircle_edgeSizeValue() {
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
+ FloatingMenuSizePreferenceController.EdgeMode.HALF_CIRCLE);
+
+ mController.updateState(mListPreference);
+
+ final String edgeSize = String.valueOf(FloatingMenuSizePreferenceController.Size.EDGE);
+ assertThat(mListPreference.getValue()).isEqualTo(edgeSize);
+ }
+
+ @Test
+ public void onPreferenceChange_floatingMenuEdgeSize_edgeSizeValue() {
+ final String edgeSize = String.valueOf(
+ FloatingMenuSizePreferenceController.Size.EDGE);
+
+ mController.onPreferenceChange(mListPreference, edgeSize);
+
+ assertThat(mListPreference.getValue()).isEqualTo(edgeSize);
+ }
+
+ @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);
+ }
+}