mShortcutTargets = Collections.emptySet();
+ private boolean mIsInSetupWizard;
+
+ public ShortcutOptionPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ if (getPreferenceKey().equals(preference.getKey())
+ && preference instanceof ShortcutOptionPreference) {
+ ((ShortcutOptionPreference) preference).setChecked(isChecked());
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (isShortcutAvailable()) {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ /**
+ * Set the targets (i.e. a11y features) to be configured with the a11y shortcut option.
+ *
+ * Note: the shortcutTargets cannot be empty, since the edit a11y shortcut option
+ * is meant to configure the shortcut options for an a11y feature.
+ * >
+ *
+ * @param shortcutTargets the a11y features, like color correction, Talkback, etc.
+ * @throws NullPointerException if the {@code shortcutTargets} was {@code null}
+ * @throws IllegalArgumentException if the {@code shortcutTargets} was empty
+ */
+ public void setShortcutTargets(Set shortcutTargets) {
+ Preconditions.checkCollectionNotEmpty(shortcutTargets, /* valueName= */ "a11y targets");
+
+ this.mShortcutTargets = shortcutTargets;
+ }
+
+ public void setInSetupWizard(boolean isInSetupWizard) {
+ this.mIsInSetupWizard = isInSetupWizard;
+ }
+
+ protected Set getShortcutTargets() {
+ return mShortcutTargets;
+ }
+
+ protected boolean isInSetupWizard() {
+ return mIsInSetupWizard;
+ }
+
+ @Override
+ public final boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
+ enableShortcutForTargets((Boolean) newValue);
+ return false;
+ }
+
+ @ShortcutConstants.UserShortcutType
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.DEFAULT;
+ }
+
+ /**
+ * Returns true if the shortcut is associated to the targets
+ */
+ protected boolean isChecked() {
+ Set targets = ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext, getShortcutType(), UserHandle.myUserId());
+
+ return !targets.isEmpty() && targets.containsAll(getShortcutTargets());
+ }
+
+ /**
+ * Enable or disable the shortcut for the given accessibility features.
+ */
+ protected void enableShortcutForTargets(boolean enable) {
+ Set shortcutTargets = getShortcutTargets();
+ @ShortcutConstants.UserShortcutType int shortcutType = getShortcutType();
+
+ if (enable) {
+ for (String target : shortcutTargets) {
+ ShortcutUtils.optInValueToSettings(mContext, shortcutType, target);
+ }
+ } else {
+ for (String target : shortcutTargets) {
+ ShortcutUtils.optOutValueFromSettings(mContext, shortcutType, target);
+ }
+ }
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ mContext, shortcutTargets, UserHandle.myUserId());
+ }
+
+ /**
+ * Returns true when the user can associate a shortcut to the targets
+ */
+ protected abstract boolean isShortcutAvailable();
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceController.java b/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceController.java
new file mode 100644
index 00000000000..24098c81f38
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceController.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.view.View;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityButtonFragment;
+import com.android.settings.accessibility.FloatingMenuSizePreferenceController;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.utils.AnnotationSpan;
+
+/**
+ * A base controller for the preference controller of software shortcuts.
+ */
+public abstract class SoftwareShortcutOptionPreferenceController
+ extends ShortcutOptionPreferenceController {
+
+ public SoftwareShortcutOptionPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.SOFTWARE;
+ }
+
+ private boolean isMagnificationInTargets() {
+ return getShortcutTargets().contains(MAGNIFICATION_CONTROLLER_NAME);
+ }
+
+ protected CharSequence getCustomizeAccessibilityButtonLink() {
+ final View.OnClickListener linkListener = v -> new SubSettingLauncher(mContext)
+ .setDestination(AccessibilityButtonFragment.class.getName())
+ .setSourceMetricsCategory(getMetricsCategory())
+ .launch();
+ final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
+ AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener);
+ return AnnotationSpan.linkify(
+ mContext.getText(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating),
+ linkInfo);
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ super.enableShortcutForTargets(enable);
+
+ if (enable) {
+ // Update the A11y FAB size to large when the Magnification shortcut is enabled
+ // and the user hasn't changed the floating button size
+ if (isMagnificationInTargets()
+ && Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSizePreferenceController.Size.UNKNOWN)
+ == FloatingMenuSizePreferenceController.Size.UNKNOWN) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSizePreferenceController.Size.LARGE);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionController.java b/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionController.java
new file mode 100644
index 00000000000..0eb1ee5acd7
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionController.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import android.content.Context;
+import android.icu.text.MessageFormat;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+import com.android.settings.accessibility.ExpandablePreference;
+
+import java.util.Set;
+
+/**
+ * A controller handles displaying the triple tap shortcut option preference and
+ * configuring the shortcut.
+ */
+public class TripleTapShortcutOptionController extends ShortcutOptionPreferenceController
+ implements ExpandablePreference {
+
+ private boolean mIsExpanded = false;
+
+ public TripleTapShortcutOptionController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ if (preference instanceof ShortcutOptionPreference shortcutOptionPreference) {
+ shortcutOptionPreference.setTitle(
+ R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
+ String summary = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
+ // Format the number '3' in the summary.
+ final Object[] arguments = {3};
+ summary = MessageFormat.format(summary, arguments);
+
+ shortcutOptionPreference.setSummary(summary);
+ shortcutOptionPreference.setIntroImageRawResId(
+ R.raw.a11y_shortcut_type_triple_tap);
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (isExpanded() && isShortcutAvailable()) {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.TRIPLETAP;
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ mIsExpanded = expanded;
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return mIsExpanded;
+ }
+
+ @Override
+ protected boolean isShortcutAvailable() {
+ Set shortcutTargets = getShortcutTargets();
+ return shortcutTargets.size() == 1
+ && shortcutTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+ }
+
+ @Override
+ protected boolean isChecked() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ AccessibilityUtil.State.OFF) == AccessibilityUtil.State.ON;
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ enable ? AccessibilityUtil.State.ON : AccessibilityUtil.State.OFF);
+ }
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionController.java b/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionController.java
new file mode 100644
index 00000000000..64ed7bdbd19
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionController.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import android.content.Context;
+import android.icu.text.MessageFormat;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.server.accessibility.Flags;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+
+import java.util.Set;
+
+/**
+ * A controller handles displaying the two fingers double tap shortcut option preference and
+ * configuring the shortcut.
+ */
+public class TwoFingersDoubleTapShortcutOptionController
+ extends ShortcutOptionPreferenceController {
+
+ public TwoFingersDoubleTapShortcutOptionController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.TRIPLETAP;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ if (preference instanceof ShortcutOptionPreference shortcutOptionPreference) {
+ // TODO (b/306153204): Update shortcut string and image when UX provides them
+ shortcutOptionPreference.setTitle(
+ R.string.accessibility_shortcut_edit_dialog_title_two_finger_double_tap);
+ String summary = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_two_finger_double_tap);
+ // Format the number '2' in the summary.
+ final Object[] arguments = {2};
+ summary = MessageFormat.format(summary, arguments);
+
+ shortcutOptionPreference.setSummary(summary);
+ shortcutOptionPreference.setIntroImageRawResId(
+ R.raw.a11y_shortcut_type_triple_tap);
+ }
+ }
+
+ @Override
+ protected boolean isShortcutAvailable() {
+ if (!Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+ return false;
+ }
+ // Only Magnification has two fingers triple tap shortcut option.
+ Set shortcutTargets = getShortcutTargets();
+ return shortcutTargets.size() == 1
+ && shortcutTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+ }
+
+ @Override
+ protected boolean isChecked() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ AccessibilityUtil.State.OFF) == AccessibilityUtil.State.ON;
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ enable ? AccessibilityUtil.State.ON : AccessibilityUtil.State.OFF);
+ }
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionController.java b/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionController.java
new file mode 100644
index 00000000000..9083e7ccc88
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+
+/**
+ * A controller handles displaying the volume keys shortcut option preference and
+ * configuring the shortcut.
+ */
+public class VolumeKeysShortcutOptionController extends ShortcutOptionPreferenceController {
+
+ public VolumeKeysShortcutOptionController(
+ Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.HARDWARE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ if (preference instanceof ShortcutOptionPreference shortcutOptionPreference) {
+ shortcutOptionPreference.setTitle(
+ R.string.accessibility_shortcut_edit_dialog_title_hardware);
+ shortcutOptionPreference.setSummary(
+ R.string.accessibility_shortcut_edit_dialog_summary_hardware);
+ shortcutOptionPreference.setIntroImageResId(
+ R.drawable.a11y_shortcut_type_hardware);
+ }
+ }
+
+ @Override
+ protected boolean isShortcutAvailable() {
+ return true;
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ super.enableShortcutForTargets(enable);
+ if (enable) {
+ AccessibilityUtil.skipVolumeShortcutDialogTimeoutRestriction(mContext);
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/AdvancedShortcutsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/AdvancedShortcutsPreferenceControllerTest.java
new file mode 100644
index 00000000000..0a8273be916
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/AdvancedShortcutsPreferenceControllerTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link AdvancedShortcutsPreferenceController}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class AdvancedShortcutsPreferenceControllerTest {
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET_MAGNIFICATION =
+ "com.android.server.accessibility.MagnificationController";
+ private static final String TARGET_FAKE =
+ new ComponentName("FakePackage", "FakeClass").flattenToString();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private AdvancedShortcutsPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ mController = new AdvancedShortcutsPreferenceController(mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+ Preference preference = new Preference(mContext);
+ preference.setKey(PREF_KEY);
+ PreferenceScreen preferenceScreen =
+ new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ preferenceScreen.addPreference(preference);
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsMagnificationAndIsExpanded_returnsConditionallyUnavailable() {
+ mController.setExpanded(true);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsMagnificationAndIsNotExpanded_returnsAvailableUnsearchable() {
+ mController.setExpanded(false);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsNotMagnificationAndIsNotExpanded_returnsConditionallyUnavailable() {
+ mController.setExpanded(false);
+ mController.setShortcutTargets(Set.of(TARGET_FAKE));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsNotMagnificationAndIsExpanded_returnsConditionallyUnavailable() {
+ mController.setExpanded(true);
+ mController.setShortcutTargets(Set.of(TARGET_FAKE));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void setExpanded_expand_updateExpandedValue() {
+ mController.setExpanded(true);
+
+ assertThat(mController.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void setExpanded_collapse_updateExpandedValue() {
+ mController.setExpanded(false);
+
+ assertThat(mController.isExpanded()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_multipleTargets_returnFalse() {
+ mController.setShortcutTargets(Set.of(TARGET_FAKE, TARGET_MAGNIFICATION));
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_magnificationTargetOnly_returnTrue() {
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ assertThat(mController.isShortcutAvailable()).isTrue();
+ }
+
+ @Test
+ public void isShortcutAvailable_nonMagnificationTarget_returnFalse() {
+ mController.setShortcutTargets(Set.of(TARGET_FAKE));
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isChecked_returnFalse() {
+ assertThat(mController.isChecked()).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java
new file mode 100644
index 00000000000..d535f1581ee
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment.SHORTCUT_SETTINGS;
+
+import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP;
+import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_FIRST_RUN;
+import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP;
+import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.testing.FragmentScenario;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.TwoStatePreference;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.util.ShortcutUtils;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SubSettings;
+import com.android.settings.accessibility.AccessibilityUtil;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import com.google.android.setupcompat.util.WizardManagerHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAccessibilityManager;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for {@link EditShortcutsPreferenceFragment}
+ */
+@Config(shadows = SettingsShadowResources.class)
+@RunWith(RobolectricTestRunner.class)
+public class EditShortcutsPreferenceFragmentTest {
+ private static final int METRICS_CATEGORY = 123;
+ private static final CharSequence SCREEN_TITLE = "Fake shortcut title";
+ private static final ComponentName TARGET_FAKE_COMPONENT =
+ new ComponentName("FakePackage", "FakeClass");
+ private static final String TARGET = MAGNIFICATION_CONTROLLER_NAME;
+ private static final Set TARGETS = Set.of(TARGET);
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private FragmentActivity mActivity;
+ private FragmentScenario mFragmentScenario;
+
+ @Before
+ public void setUp() {
+ SettingsShadowResources.overrideResource(
+ com.android.internal.R.integer.config_navBarInteractionMode,
+ NAV_BAR_MODE_GESTURAL);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE);
+
+ mActivity = Robolectric.buildActivity(FragmentActivity.class).get();
+ }
+
+ @After
+ public void cleanUp() {
+ if (mFragmentScenario != null) {
+ mFragmentScenario.close();
+ }
+ }
+
+ @Test
+ public void showEditShortcutScreen_targetIsMagnification_launchSubSetting() {
+ EditShortcutsPreferenceFragment.showEditShortcutScreen(
+ mActivity, METRICS_CATEGORY, SCREEN_TITLE,
+ MAGNIFICATION_COMPONENT_NAME, /* fromIntent= */ null);
+
+ assertLaunchSubSettingWithCurrentTargetComponents(
+ MAGNIFICATION_CONTROLLER_NAME, /* isInSuw= */ false);
+ }
+
+ @Test
+ public void showEditShortcutScreen_launchSubSetting() {
+ EditShortcutsPreferenceFragment.showEditShortcutScreen(
+ mActivity, METRICS_CATEGORY, SCREEN_TITLE,
+ TARGET_FAKE_COMPONENT, /* fromIntent= */ null);
+
+ assertLaunchSubSettingWithCurrentTargetComponents(
+ TARGET_FAKE_COMPONENT.flattenToString(), /* isInSuw= */ false);
+ }
+
+ @Test
+ public void showEditShortcutScreen_inSuw_launchSubSettingWithSuw() {
+ EditShortcutsPreferenceFragment.showEditShortcutScreen(
+ mActivity, METRICS_CATEGORY, SCREEN_TITLE,
+ TARGET_FAKE_COMPONENT, createSuwIntent(new Intent(), /* isInSuw= */ true));
+
+ assertLaunchSubSettingWithCurrentTargetComponents(
+ TARGET_FAKE_COMPONENT.flattenToString(), /* isInSuw= */ true);
+ }
+
+ @Test
+ public void fragmentCreated_inSuw_controllersTargetsSet() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ true);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ mFragmentScenario.onFragment(fragment -> {
+ List controllers =
+ getShortcutOptionPreferenceControllers(fragment);
+
+ for (ShortcutOptionPreferenceController controller : controllers) {
+ assertThat(controller.getShortcutTargets()).containsExactlyElementsIn(TARGETS);
+ assertThat(controller.isInSetupWizard()).isTrue();
+ }
+ });
+ }
+
+ @Test
+ public void fragmentCreated_notInSuw_controllersTargetsSet() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ mFragmentScenario.onFragment(fragment -> {
+ List controllers =
+ getShortcutOptionPreferenceControllers(fragment);
+
+ for (ShortcutOptionPreferenceController controller : controllers) {
+ assertThat(controller.getShortcutTargets()).containsExactlyElementsIn(TARGETS);
+ assertThat(controller.isInSetupWizard()).isFalse();
+ }
+ });
+ }
+
+ @Test
+ public void fragmentCreated_settingsObserversAreRegistered() {
+ ShadowContentResolver contentResolver = shadowOf(mContext.getContentResolver());
+ for (Uri uri : SHORTCUT_SETTINGS) {
+ assertThat(contentResolver.getContentObservers(uri)).isEmpty();
+ }
+
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ for (Uri uri : SHORTCUT_SETTINGS) {
+ assertThat(contentResolver.getContentObservers(uri)).isNotEmpty();
+ }
+ }
+
+ @Test
+ public void fragmentDestroyed_unregisterSettingsObserver() {
+ ShadowContentResolver contentResolver = shadowOf(mContext.getContentResolver());
+
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false)
+ .moveToState(Lifecycle.State.CREATED);
+ mFragmentScenario.onFragment(EditShortcutsPreferenceFragment::onDestroy);
+
+ for (Uri uri : SHORTCUT_SETTINGS) {
+ assertThat(contentResolver.getContentObservers(uri)).isEmpty();
+ }
+ }
+
+ @Test
+ public void onVolumeKeysShortcutSettingChanged_volumeKeyControllerUpdated() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ ShortcutUtils.optInValueToSettings(
+ mContext, ShortcutConstants.UserShortcutType.HARDWARE, TARGET);
+
+ mFragmentScenario.onFragment(fragment -> {
+ TwoStatePreference preference = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcut_volume_keys_pref));
+ assertThat(preference.isChecked()).isTrue();
+ });
+ }
+
+ @Test
+ public void onSoftwareShortcutSettingChanged_softwareControllersUpdated() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ ShortcutUtils.optInValueToSettings(
+ mContext, ShortcutConstants.UserShortcutType.SOFTWARE, TARGET);
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+ mFragmentScenario.onFragment(fragment -> {
+ TwoStatePreference preference = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcut_gesture_pref));
+ assertThat(preference.isChecked()).isTrue();
+ });
+ }
+
+ @Test
+ public void onSoftwareShortcutModeChanged_softwareControllersUpdated() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ ShortcutUtils.optInValueToSettings(
+ mContext, ShortcutConstants.UserShortcutType.SOFTWARE, TARGET);
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+ mFragmentScenario.onFragment(fragment -> {
+ TwoStatePreference preference = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcut_gesture_pref));
+ assertThat(preference.isChecked()).isTrue();
+ });
+ }
+
+ @Test
+ public void onTripleTapShortcutSettingChanged_tripleTapShortcutControllerUpdated() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ AccessibilityUtil.State.ON);
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+ mFragmentScenario.onFragment(fragment -> {
+ TwoStatePreference preference = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcut_triple_tap_pref));
+ assertThat(preference.isChecked()).isTrue();
+ });
+ }
+
+ @Test
+ public void onTwoFingersShortcutSettingChanged_twoFingersDoubleTapShortcutControllerUpdated() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
+
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ AccessibilityUtil.State.ON);
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+ mFragmentScenario.onFragment(fragment -> {
+ TwoStatePreference preference = fragment.findPreference(
+ mContext.getString(
+ R.string.accessibility_shortcut_two_fingers_double_tap_pref));
+ assertThat(preference.isChecked()).isTrue();
+ });
+ }
+
+ @Test
+ public void fragmentResumed_enableTouchExploration_gestureShortcutOptionSummaryUpdated() {
+ String expectedSummary = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback)
+ + "\n\n"
+ + mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating);
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.RESUMED);
+
+ ShadowAccessibilityManager am = shadowOf(
+ mContext.getSystemService(AccessibilityManager.class));
+ am.setTouchExplorationEnabled(true);
+
+ mFragmentScenario.onFragment(fragment -> {
+ Preference preference = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcut_gesture_pref));
+ assertThat(preference.getSummary().toString()).isEqualTo(expectedSummary);
+ });
+ }
+
+ @Test
+ public void fragmentPaused_enableTouchExploration_gestureShortcutOptionSummaryNotUpdated() {
+ String expectedSummary = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_gesture)
+ + "\n\n"
+ + mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating);
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.RESUMED).moveToState(Lifecycle.State.STARTED);
+
+ ShadowAccessibilityManager am = shadowOf(
+ mContext.getSystemService(AccessibilityManager.class));
+ am.setTouchExplorationEnabled(true);
+
+ mFragmentScenario.onFragment(fragment -> {
+ Preference preference = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcut_gesture_pref));
+ assertThat(preference.getSummary().toString()).isEqualTo(expectedSummary);
+ });
+ }
+
+ @Test
+ public void onAdvancedPreferenceClicked_advancedShouldBecomeInvisible() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.RESUMED);
+ mFragmentScenario.onFragment(fragment -> {
+ Preference advanced = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcuts_advanced_collapsed));
+ assertThat(advanced.isVisible()).isTrue();
+
+ fragment.onPreferenceTreeClick(advanced);
+
+ assertThat(advanced.isVisible()).isFalse();
+ });
+ }
+
+ @Test
+ public void fragmentRecreated_expanded_advancedRemainInvisible() {
+ onAdvancedPreferenceClicked_advancedShouldBecomeInvisible();
+
+ mFragmentScenario.recreate();
+
+ mFragmentScenario.onFragment(fragment -> {
+ Preference advanced = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcuts_advanced_collapsed));
+ assertThat(advanced.isVisible()).isFalse();
+ });
+ }
+
+ @Test
+ public void fragmentRecreated_collapsed_advancedRemainVisible() {
+ mFragmentScenario = createFragScenario(/* isInSuw= */ false);
+ mFragmentScenario.moveToState(Lifecycle.State.RESUMED);
+
+ mFragmentScenario.recreate();
+
+ mFragmentScenario.onFragment(fragment -> {
+ Preference advanced = fragment.findPreference(
+ mContext.getString(R.string.accessibility_shortcuts_advanced_collapsed));
+ assertThat(advanced.isVisible()).isTrue();
+ });
+ }
+
+ private void assertLaunchSubSettingWithCurrentTargetComponents(
+ String componentName, boolean isInSuw) {
+ Intent intent = shadowOf(mActivity.getApplication()).getNextStartedActivity();
+
+ assertThat(intent).isNotNull();
+ assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MAIN);
+ assertThat(intent.getComponent()).isEqualTo(
+ new ComponentName(mActivity, SubSettings.class));
+ assertThat(intent.getExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(EditShortcutsPreferenceFragment.class.getName());
+ assertThat(intent.getExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE))
+ .isEqualTo(SCREEN_TITLE.toString());
+ assertThat(intent.getExtra(
+ MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY)).isEqualTo(METRICS_CATEGORY);
+ Bundle args = (Bundle) intent.getExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(args).isNotNull();
+ assertThat(Arrays.stream(args.getStringArray(
+ EditShortcutsPreferenceFragment.ARG_KEY_SHORTCUT_TARGETS)).toList())
+ .containsExactly(componentName);
+ assertThat(WizardManagerHelper.isAnySetupWizard(intent)).isEqualTo(isInSuw);
+ }
+
+ private List getShortcutOptionPreferenceControllers(
+ EditShortcutsPreferenceFragment fragment) {
+
+ Collection> controllers =
+ ReflectionHelpers.callInstanceMethod(fragment, "getPreferenceControllers");
+ List retControllers = new ArrayList<>();
+ controllers.stream().flatMap(Collection::stream)
+ .filter(controller -> controller instanceof ShortcutOptionPreferenceController)
+ .forEach(controller ->
+ retControllers.add((ShortcutOptionPreferenceController) controller));
+
+ return retControllers;
+ }
+
+ private FragmentScenario createFragScenario(boolean isInSuw) {
+ Bundle args = new Bundle();
+ args.putStringArray(
+ EditShortcutsPreferenceFragment.ARG_KEY_SHORTCUT_TARGETS, new String[]{TARGET});
+ FragmentScenario scenario =
+ FragmentScenario.launch(
+ EditShortcutsPreferenceFragment.class, args,
+ /* themeResId= */ 0, Lifecycle.State.INITIALIZED);
+ scenario.onFragment(fragment -> {
+ Intent intent = fragment.requireActivity().getIntent();
+ fragment.requireActivity().setIntent(createSuwIntent(intent, isInSuw));
+ // Since the fragment is attached before we have a chance
+ // to modify the activity's intent; initialize controllers again
+ fragment.initializePreferenceControllerArguments();
+ });
+ return scenario;
+ }
+
+ private Intent createSuwIntent(Intent intent, boolean isInSuw) {
+
+ if (intent == null) {
+ intent = new Intent();
+ }
+ intent.putExtra(EXTRA_IS_SETUP_FLOW, isInSuw);
+ intent.putExtra(EXTRA_IS_FIRST_RUN, isInSuw);
+ intent.putExtra(EXTRA_IS_PRE_DEFERRED_SETUP, isInSuw);
+ intent.putExtra(EXTRA_IS_DEFERRED_SETUP, isInSuw);
+ return intent;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/FloatingButtonShortcutOptionControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/FloatingButtonShortcutOptionControllerTest.java
new file mode 100644
index 00000000000..b39aa226239
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/FloatingButtonShortcutOptionControllerTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link FloatingButtonShortcutOptionController}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class FloatingButtonShortcutOptionControllerTest {
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET =
+ new ComponentName("FakePackage", "FakeClass").flattenToString();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private FloatingButtonShortcutOptionController mController;
+ private ShortcutOptionPreference mShortcutOptionPreference;
+
+ private PreferenceScreen mPreferenceScreen;
+
+ @Before
+ public void setUp() {
+ mController = new FloatingButtonShortcutOptionController(
+ mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET));
+ mShortcutOptionPreference = new ShortcutOptionPreference(mContext);
+ mShortcutOptionPreference.setKey(PREF_KEY);
+ mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ mPreferenceScreen.addPreference(mShortcutOptionPreference);
+ setFloatingButtonEnabled(true);
+ }
+
+ @Test
+ public void displayPreference_verifyTitle() {
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(R.string.accessibility_shortcut_edit_dialog_title_software));
+ }
+
+ @Test
+ public void getSummary_inSuw_verifySummaryEmpty() {
+ mController.setInSetupWizard(true);
+
+ assertThat(TextUtils.isEmpty(mController.getSummary())).isTrue();
+ }
+
+ @Test
+ public void getSummary_notInSuw_verifySummary() {
+ mController.setInSetupWizard(false);
+
+ assertThat(mController.getSummary().toString()).isEqualTo(
+ mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating));
+ }
+
+ @Test
+ public void isShortcutAvailable_floatingMenuEnabled_returnTrue() {
+ setFloatingButtonEnabled(true);
+
+ assertThat(mController.isShortcutAvailable()).isTrue();
+ }
+
+ @Test
+ public void isShortcutAvailable_floatingMenuDisabled_returnFalse() {
+ setFloatingButtonEnabled(false);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ private void setFloatingButtonEnabled(boolean enable) {
+ int mode = enable
+ ? ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU : ACCESSIBILITY_BUTTON_MODE_GESTURE;
+
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mode);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/GestureShortcutOptionControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/GestureShortcutOptionControllerTest.java
new file mode 100644
index 00000000000..010386cf4c8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/GestureShortcutOptionControllerTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.testutils.AccessibilityTestUtils;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link GestureShortcutOptionController}
+ */
+@Config(shadows = SettingsShadowResources.class)
+@RunWith(RobolectricTestRunner.class)
+public class GestureShortcutOptionControllerTest {
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET =
+ new ComponentName("FakePackage", "FakeClass").flattenToString();
+ private final Context mContext = spy(ApplicationProvider.getApplicationContext());
+ private GestureShortcutOptionController mController;
+ private ShortcutOptionPreference mShortcutOptionPreference;
+
+ private PreferenceScreen mPreferenceScreen;
+
+ @Before
+ public void setUp() {
+ mController = new GestureShortcutOptionController(
+ mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET));
+ mShortcutOptionPreference = new ShortcutOptionPreference(mContext);
+ mShortcutOptionPreference.setKey(PREF_KEY);
+ mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ mPreferenceScreen.addPreference(mShortcutOptionPreference);
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ true, /* floatingButtonEnabled= */ false);
+ enableTouchExploration(false);
+ }
+
+ @Test
+ public void displayPreference_verifyTitle() {
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_title_software_by_gesture));
+ }
+
+ @Test
+ public void getSummary_touchExplorationDisabled_verifySummary() {
+ enableTouchExploration(false);
+ String expected = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_gesture)
+ + "\n\n"
+ + mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating);
+
+ assertThat(mController.getSummary().toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void getSummary_touchExplorationEnabled_verifySummary() {
+ enableTouchExploration(true);
+ String expected = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback)
+ + "\n\n"
+ + mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating);
+
+ assertThat(mController.getSummary().toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void isShortcutAvailable_inSuw_returnFalse() {
+ mController.setInSetupWizard(true);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_notInSuwUseGestureNavSystemUseFab_returnFalse() {
+ mController.setInSetupWizard(false);
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ true, /* floatingButtonEnabled= */ true);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_notInSuwUseGestureNavSystemNotUseFab_returnTrue() {
+ mController.setInSetupWizard(false);
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ true, /* floatingButtonEnabled= */ false);
+
+ assertThat(mController.isShortcutAvailable()).isTrue();
+ }
+
+ @Test
+ public void isShortcutAvailable_notInSuwUseButtonNavSystemUseFab_returnFalse() {
+ mController.setInSetupWizard(false);
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ false, /* floatingButtonEnabled= */ true);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_notInSuwUseButtonNavSystemNotUseFab_returnFalse() {
+ mController.setInSetupWizard(false);
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ false, /* floatingButtonEnabled= */ false);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ private void enableTouchExploration(boolean enable) {
+ AccessibilityManager am = mock(AccessibilityManager.class);
+ when(mContext.getSystemService(AccessibilityManager.class)).thenReturn(am);
+ when(am.isTouchExplorationEnabled()).thenReturn(enable);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/NavButtonShortcutOptionControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/NavButtonShortcutOptionControllerTest.java
new file mode 100644
index 00000000000..9106d0059cc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/NavButtonShortcutOptionControllerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.testutils.AccessibilityTestUtils;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link NavButtonShortcutOptionController}
+ */
+@Config(shadows = SettingsShadowResources.class)
+@RunWith(RobolectricTestRunner.class)
+public class NavButtonShortcutOptionControllerTest {
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET =
+ new ComponentName("FakePackage", "FakeClass").flattenToString();
+ private final Context mContext = spy(ApplicationProvider.getApplicationContext());
+ private NavButtonShortcutOptionController mController;
+ private ShortcutOptionPreference mShortcutOptionPreference;
+
+ private PreferenceScreen mPreferenceScreen;
+
+ @Before
+ public void setUp() {
+ mController = new NavButtonShortcutOptionController(
+ mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET));
+ mShortcutOptionPreference = new ShortcutOptionPreference(mContext);
+ mShortcutOptionPreference.setKey(PREF_KEY);
+ mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ mPreferenceScreen.addPreference(mShortcutOptionPreference);
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ false, /* floatingButtonEnabled= */ false);
+ }
+
+ @Test
+ public void displayPreference_verifyTitle() {
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_title_software));
+ }
+
+ @Test
+ public void displayPreference_inSuw_verifySummary() {
+ mController.setInSetupWizard(true);
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getSummary().toString()).isEqualTo(
+ mContext.getString(R.string.accessibility_shortcut_edit_dialog_summary_software));
+ }
+
+ @Test
+ public void displayPreference_notInSuw_verifySummary() {
+ mController.setInSetupWizard(false);
+ String expected = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software)
+ + "\n\n"
+ + mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating);
+
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getSummary().toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void isShortcutAvailable_useGestureNavSystemUseFab_returnFalse() {
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ true, /* floatingButtonEnabled= */ true);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_useGestureNavSystemNotUseFab_returnFalse() {
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ true, /* floatingButtonEnabled= */ false);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_useButtonNavSystemUseFab_returnFalse() {
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ false, /* floatingButtonEnabled= */ true);
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_useButtonNavSystemNotUseFab_returnTrue() {
+ AccessibilityTestUtils.setSoftwareShortcutMode(
+ mContext, /* gestureNavEnabled= */ false, /* floatingButtonEnabled= */ false);
+
+ assertThat(mController.isShortcutAvailable()).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/ShortcutOptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/ShortcutOptionPreferenceControllerTest.java
new file mode 100644
index 00000000000..d3d425ec570
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/ShortcutOptionPreferenceControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link ShortcutOptionPreferenceController}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class ShortcutOptionPreferenceControllerTest {
+
+ private static final String PREF_KEY = "prefKey";
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ShortcutOptionPreference mShortcutOptionPreference;
+ private ShortcutOptionPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ mShortcutOptionPreference = spy(new ShortcutOptionPreference(mContext));
+ mShortcutOptionPreference.setKey(PREF_KEY);
+ mController = spy(new ShortcutOptionPreferenceController(mContext, PREF_KEY) {
+ @Override
+ protected boolean isShortcutAvailable() {
+ return false;
+ }
+
+ @Override
+ protected boolean isChecked() {
+ return false;
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ // do nothing
+ }
+ });
+ }
+
+ @Test
+ public void updateState_shortcutControllerIsChecked_shouldSetPreferenceChecked() {
+ when(mController.isChecked()).thenReturn(true);
+
+ mController.updateState(mShortcutOptionPreference);
+
+ assertThat(mShortcutOptionPreference.isChecked()).isTrue();
+ }
+
+ @Test
+ public void updateState_shortcutControllerIsNotChecked_shouldSetPreferenceUnchecked() {
+ when(mController.isChecked()).thenReturn(false);
+
+ mController.updateState(mShortcutOptionPreference);
+
+ assertThat(mShortcutOptionPreference.isChecked()).isFalse();
+ }
+
+ @Test
+ public void getAvailabilityStatus_shortcutAvailable_returnAvailableUnsearchable() {
+ when(mController.isShortcutAvailable()).thenReturn(true);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_shortcutUnavailable_returnConditionallyUnavailable() {
+ when(mController.isShortcutAvailable()).thenReturn(false);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void onPreferenceChanged_callEnableShortcutForTargets() {
+ mController.onPreferenceChange(mShortcutOptionPreference, true);
+ mController.onPreferenceChange(mShortcutOptionPreference, false);
+
+ InOrder inOrder = Mockito.inOrder(mController);
+ inOrder.verify(mController).enableShortcutForTargets(true);
+ inOrder.verify(mController).enableShortcutForTargets(false);
+ }
+
+ @Test
+ public void getShortcutTargets() {
+ Set targets = Set.of("target1", "target2");
+ mController.setShortcutTargets(targets);
+
+ assertThat(mController.getShortcutTargets())
+ .containsExactlyElementsIn(targets);
+ }
+
+ @Test
+ public void isInSetupWizard() {
+ mController.setInSetupWizard(true);
+
+ assertThat(mController.isInSetupWizard()).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/ShortcutOptionPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/ShortcutOptionPreferenceTest.java
new file mode 100644
index 00000000000..981b86d03b5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/ShortcutOptionPreferenceTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Test for {@link ShortcutOptionPreferenceTest}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class ShortcutOptionPreferenceTest {
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ShortcutOptionPreference mShortcutOptionPreference;
+ private PreferenceViewHolder mViewHolder;
+ private ImageView mImageView;
+
+ @Before
+ public void setUp() {
+ mShortcutOptionPreference = new ShortcutOptionPreference(mContext);
+ int layoutId = mShortcutOptionPreference.getLayoutResource();
+ View itemView = LayoutInflater.from(mContext).inflate(layoutId, /* root= */null);
+ mViewHolder = PreferenceViewHolder.createInstanceForTests(itemView);
+ mImageView = (ImageView) mViewHolder.findViewById(R.id.image);
+ }
+
+ @Test
+ public void bindViewHolder_imageResNotSet_shouldHideImageView() {
+ mShortcutOptionPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mImageView.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void bindViewHolder_imageResIdSet_shouldShowImageView() {
+ mShortcutOptionPreference.setIntroImageResId(R.drawable.a11y_shortcut_type_hardware);
+
+ mShortcutOptionPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mImageView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void bindViewHolder_imageRawResIdSet_shouldShowImageView() {
+ mShortcutOptionPreference.setIntroImageRawResId(
+ com.android.settings.R.raw.accessibility_color_inversion_banner);
+
+ mShortcutOptionPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mImageView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void bindViewHolder_shouldUpdateSummaryTextLineHeight() {
+ assertThat(mShortcutOptionPreference.getSummaryTextLineHeight()).isEqualTo(0);
+
+ mShortcutOptionPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mShortcutOptionPreference.getSummaryTextLineHeight()).isNotEqualTo(0);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceControllerTest.java
new file mode 100644
index 00000000000..1f7e0196af8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceControllerTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.provider.Settings;
+import android.text.SpannableStringBuilder;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.util.ShortcutUtils;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SubSettings;
+import com.android.settings.accessibility.AccessibilityButtonFragment;
+import com.android.settings.accessibility.FloatingMenuSizePreferenceController;
+import com.android.settings.utils.AnnotationSpan;
+import com.android.settingslib.accessibility.AccessibilityUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for {@link SoftwareShortcutOptionPreferenceController}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class SoftwareShortcutOptionPreferenceControllerTest {
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET_MAGNIFICATION =
+ "com.android.server.accessibility.MagnificationController";
+ private static final ComponentName TARGET_ALWAYS_ON_A11Y_SERVICE =
+ new ComponentName("FakePackage", "AlwaysOnA11yService");
+ private static final ComponentName TARGET_STANDARD_A11Y_SERVICE =
+ new ComponentName("FakePackage", "StandardA11yService");
+ private static final String SOFTWARE_SHORTCUT_SETTING_NAME =
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
+
+ private Context mContext;
+ private TestSoftwareShortcutOptionPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ mContext = spy(Robolectric.buildActivity(FragmentActivity.class).get());
+
+ AccessibilityServiceInfo mAlwaysOnServiceInfo = createAccessibilityServiceInfo(
+ TARGET_ALWAYS_ON_A11Y_SERVICE, /* isAlwaysOnService= */ true);
+ AccessibilityServiceInfo mStandardServiceInfo = createAccessibilityServiceInfo(
+ TARGET_STANDARD_A11Y_SERVICE, /* isAlwaysOnService= */ false);
+ AccessibilityManager am = mock(AccessibilityManager.class);
+ when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE)).thenReturn(am);
+ when(am.getInstalledAccessibilityServiceList()).thenReturn(
+ List.of(mAlwaysOnServiceInfo, mStandardServiceInfo));
+
+ mController = new TestSoftwareShortcutOptionPreferenceController(mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+ }
+
+ @Test
+ public void isChecked_allTargetsHasShortcutConfigured_returnTrue() {
+ Settings.Secure.putString(
+ mContext.getContentResolver(), SOFTWARE_SHORTCUT_SETTING_NAME,
+ String.join(String.valueOf(SERVICES_SEPARATOR),
+ TARGET_MAGNIFICATION,
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
+ );
+ mController.setShortcutTargets(
+ Set.of(TARGET_MAGNIFICATION,
+ TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(),
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString()));
+
+ assertThat(mController.isChecked()).isTrue();
+ }
+
+ @Test
+ public void isChecked_someTargetsHasShortcutConfigured_returnFalse() {
+ Settings.Secure.putString(
+ mContext.getContentResolver(), SOFTWARE_SHORTCUT_SETTING_NAME,
+ String.join(String.valueOf(SERVICES_SEPARATOR),
+ TARGET_MAGNIFICATION,
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString())
+ );
+ mController.setShortcutTargets(
+ Set.of(TARGET_MAGNIFICATION,
+ TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(),
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString()));
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void isChecked_noTargetsHasShortcutConfigured_returnFalse() {
+ Settings.Secure.putString(
+ mContext.getContentResolver(), SOFTWARE_SHORTCUT_SETTING_NAME, "");
+ mController.setShortcutTargets(
+ Set.of(TARGET_MAGNIFICATION,
+ TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(),
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString()));
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void getCustomizedAccessibilityButtonLink_verifyText() {
+ String expected =
+ mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating);
+
+ CharSequence spannable = mController.getCustomizeAccessibilityButtonLink();
+
+ assertThat(spannable.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void getCustomizedAccessibilityButtonLink_verifyClickAction() {
+ String expected =
+ mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating);
+
+ CharSequence spannable = mController.getCustomizeAccessibilityButtonLink();
+
+ assertThat(spannable).isInstanceOf(SpannableStringBuilder.class);
+ AnnotationSpan[] spans = ((SpannableStringBuilder) spannable).getSpans(
+ 0, expected.length(), AnnotationSpan.class);
+ spans[0].onClick(new View(mContext));
+ assertLaunchSettingsPage(AccessibilityButtonFragment.class.getName());
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableShortcut_shortcutTurnedOn() {
+ String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+ mController.setShortcutTargets(Set.of(target));
+ assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+ mContext, ShortcutConstants.UserShortcutType.SOFTWARE, target
+ )).isFalse();
+
+ mController.enableShortcutForTargets(true);
+
+ assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+ mContext, ShortcutConstants.UserShortcutType.SOFTWARE, target
+ )).isTrue();
+ }
+
+ @Test
+ public void enableShortcutForTargets_disableShortcut_shortcutTurnedOff() {
+ String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+ ShortcutUtils.optInValueToSettings(
+ mContext, ShortcutConstants.UserShortcutType.SOFTWARE, target);
+ assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+ mContext, ShortcutConstants.UserShortcutType.SOFTWARE, target
+ )).isTrue();
+ mController.setShortcutTargets(Set.of(target));
+
+ mController.enableShortcutForTargets(false);
+
+ assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+ mContext, ShortcutConstants.UserShortcutType.SOFTWARE, target
+ )).isFalse();
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableShortcutWithMagnification_menuSizeIncreased() {
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ mController.enableShortcutForTargets(true);
+
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSizePreferenceController.Size.UNKNOWN))
+ .isEqualTo(FloatingMenuSizePreferenceController.Size.LARGE);
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableShortcutWithMagnification_userConfigureSmallMenuSize_menuSizeNotChanged() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSizePreferenceController.Size.SMALL);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ mController.enableShortcutForTargets(true);
+
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSizePreferenceController.Size.UNKNOWN))
+ .isEqualTo(FloatingMenuSizePreferenceController.Size.SMALL);
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableAlwaysOnServiceShortcut_turnsOnAlwaysOnService() {
+ mController.setShortcutTargets(
+ Set.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()));
+
+ mController.enableShortcutForTargets(true);
+
+ assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
+ .contains(TARGET_ALWAYS_ON_A11Y_SERVICE);
+ }
+
+ @Test
+ public void enableShortcutForTargets_disableAlwaysOnServiceShortcut_turnsOffAlwaysOnService() {
+ mController.setShortcutTargets(
+ Set.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()));
+
+ mController.enableShortcutForTargets(false);
+
+ assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
+ .doesNotContain(TARGET_ALWAYS_ON_A11Y_SERVICE);
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableStandardServiceShortcut_wontTurnOnService() {
+ mController.setShortcutTargets(
+ Set.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()));
+
+ mController.enableShortcutForTargets(true);
+
+ assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
+ .doesNotContain(TARGET_STANDARD_A11Y_SERVICE);
+ }
+
+ @Test
+ public void enableShortcutForTargets_disableStandardServiceShortcutWithServiceOn_wontTurnOffService() {
+ mController.setShortcutTargets(
+ Set.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()));
+ AccessibilityUtils.setAccessibilityServiceState(
+ mContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
+
+ mController.enableShortcutForTargets(false);
+
+ assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
+ .contains(TARGET_STANDARD_A11Y_SERVICE);
+ }
+
+ private void assertLaunchSettingsPage(String page) {
+ ContextWrapper applicationContext = (Application) mContext.getApplicationContext();
+ final Intent intent = Shadows.shadowOf(applicationContext).getNextStartedActivity();
+ assertThat(intent).isNotNull();
+ assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MAIN);
+ assertThat(intent.getComponent()).isEqualTo(
+ new ComponentName(applicationContext, SubSettings.class));
+ assertThat(intent.getExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(page);
+ }
+
+ private AccessibilityServiceInfo createAccessibilityServiceInfo(
+ ComponentName componentName, boolean isAlwaysOnService) {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ applicationInfo.packageName = componentName.getPackageName();
+ serviceInfo.packageName = componentName.getPackageName();
+ serviceInfo.name = componentName.getClassName();
+ serviceInfo.applicationInfo = applicationInfo;
+
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ try {
+ final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo,
+ mContext);
+ info.setComponentName(componentName);
+ if (isAlwaysOnService) {
+ info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ }
+ return info;
+ } catch (XmlPullParserException | IOException e) {
+ // Do nothing
+ }
+ return null;
+ }
+
+ private static class TestSoftwareShortcutOptionPreferenceController
+ extends SoftwareShortcutOptionPreferenceController {
+
+ TestSoftwareShortcutOptionPreferenceController(
+ Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ protected boolean isShortcutAvailable() {
+ return true;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionControllerTest.java
new file mode 100644
index 00000000000..800640a84f4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionControllerTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.icu.text.MessageFormat;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link TripleTapShortcutOptionController}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class TripleTapShortcutOptionControllerTest {
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET_MAGNIFICATION =
+ "com.android.server.accessibility.MagnificationController";
+ private static final String TARGET_FAKE =
+ new ComponentName("FakePackage", "FakeClass").flattenToString();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private TripleTapShortcutOptionController mController;
+ private ShortcutOptionPreference mShortcutOptionPreference;
+ private PreferenceScreen mPreferenceScreen;
+
+ @Before
+ public void setUp() {
+ mController = new TripleTapShortcutOptionController(mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+ mShortcutOptionPreference = new ShortcutOptionPreference(mContext);
+ mShortcutOptionPreference.setKey(PREF_KEY);
+ mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ mPreferenceScreen.addPreference(mShortcutOptionPreference);
+ }
+
+ @Test
+ public void displayPreference_verifyScreenTestSet() {
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(R.string.accessibility_shortcut_edit_dialog_title_triple_tap));
+ assertThat(mShortcutOptionPreference.getSummary().toString()).isEqualTo(
+ MessageFormat.format(
+ mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_triple_tap),
+ 3));
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsMagnificationAndIsExpanded_returnsAvailableUnsearchable() {
+ mController.setExpanded(true);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsMagnificationAndIsNotExpanded_returnsConditionallyUnavailable() {
+ mController.setExpanded(false);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsNotMagnificationAndIsNotExpanded_returnsConditionallyUnavailable() {
+ mController.setExpanded(false);
+ mController.setShortcutTargets(Set.of(TARGET_FAKE));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_targetIsNotMagnificationAndIsExpanded_returnsConditionallyUnavailable() {
+ mController.setExpanded(true);
+ mController.setShortcutTargets(Set.of(TARGET_FAKE));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void setExpanded_expand_updateExpandedValue() {
+ mController.setExpanded(true);
+
+ assertThat(mController.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void setExpanded_collapse_updateExpandedValue() {
+ mController.setExpanded(false);
+
+ assertThat(mController.isExpanded()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_multipleTargets_returnFalse() {
+ mController.setShortcutTargets(Set.of(TARGET_FAKE, TARGET_MAGNIFICATION));
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isShortcutAvailable_magnificationTargetOnly_returnTrue() {
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ assertThat(mController.isShortcutAvailable()).isTrue();
+ }
+
+ @Test
+ public void isShortcutAvailable_nonMagnificationTarget_returnFalse() {
+ mController.setShortcutTargets(Set.of(TARGET_FAKE));
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isChecked_tripleTapConfigured_returnTrue() {
+ mController.enableShortcutForTargets(true);
+
+ assertThat(mController.isChecked()).isTrue();
+ }
+
+ @Test
+ public void isChecked_tripleTapNotConfigured_returnFalse() {
+ mController.enableShortcutForTargets(false);
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableShortcut_settingUpdated() {
+ mController.enableShortcutForTargets(true);
+
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ AccessibilityUtil.State.OFF)
+ ).isEqualTo(AccessibilityUtil.State.ON);
+ }
+
+ @Test
+ public void enableShortcutForTargets_disableShortcut_settingUpdated() {
+ mController.enableShortcutForTargets(false);
+
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ AccessibilityUtil.State.OFF)
+ ).isEqualTo(AccessibilityUtil.State.OFF);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionControllerTest.java
new file mode 100644
index 00000000000..d27560b35c5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionControllerTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.icu.text.MessageFormat;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.accessibility.Flags;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link TwoFingersDoubleTapShortcutOptionController}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class TwoFingersDoubleTapShortcutOptionControllerTest {
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET_MAGNIFICATION =
+ "com.android.server.accessibility.MagnificationController";
+ private static final String TARGET_FAKE =
+ new ComponentName("FakePackage", "FakeClass").flattenToString();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private TwoFingersDoubleTapShortcutOptionController mController;
+ private ShortcutOptionPreference mShortcutOptionPreference;
+
+ private PreferenceScreen mPreferenceScreen;
+
+ @Before
+ public void setUp() {
+ mController = new TwoFingersDoubleTapShortcutOptionController(mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+ mShortcutOptionPreference = new ShortcutOptionPreference(mContext);
+ mShortcutOptionPreference.setKey(PREF_KEY);
+ mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ mPreferenceScreen.addPreference(mShortcutOptionPreference);
+ }
+
+ @Test
+ public void displayPreference_verifyScreenTextSet() {
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_title_two_finger_double_tap));
+ assertThat(mShortcutOptionPreference.getSummary().toString()).isEqualTo(
+ MessageFormat.format(mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_two_finger_double_tap),
+ 2));
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @Test
+ public void isShortcutAvailable_featureFlagTurnedOff_returnFalse() {
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @Test
+ public void isShortcutAvailable_multipleTargets_returnFalse() {
+ mController.setShortcutTargets(Set.of(TARGET_FAKE, TARGET_MAGNIFICATION));
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @Test
+ public void isShortcutAvailable_magnificationTargetOnly_returnTrue() {
+ mController.setShortcutTargets(Set.of(TARGET_MAGNIFICATION));
+
+ assertThat(mController.isShortcutAvailable()).isTrue();
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @Test
+ public void isShortcutAvailable_nonMagnificationTarget_returnFalse() {
+ mController.setShortcutTargets(Set.of(TARGET_FAKE));
+
+ assertThat(mController.isShortcutAvailable()).isFalse();
+ }
+
+ @Test
+ public void isChecked_twoFingersDoubleTapConfigured_returnTrue() {
+ mController.enableShortcutForTargets(true);
+
+ assertThat(mController.isChecked()).isTrue();
+ }
+
+ @Test
+ public void isChecked_twoFingersDoubleTapNotConfigured_returnFalse() {
+ mController.enableShortcutForTargets(false);
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableShortcut_settingUpdated() {
+ mController.enableShortcutForTargets(true);
+
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ AccessibilityUtil.State.OFF)
+ ).isEqualTo(AccessibilityUtil.State.ON);
+ }
+
+ @Test
+ public void enableShortcutForTargets_disableShortcut_settingUpdated() {
+ mController.enableShortcutForTargets(false);
+
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ AccessibilityUtil.State.OFF)
+ ).isEqualTo(AccessibilityUtil.State.OFF);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionControllerTest.java
new file mode 100644
index 00000000000..48a90a7e6a6
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionControllerTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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.shortcuts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.util.ShortcutUtils;
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link VolumeKeysShortcutOptionController}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class VolumeKeysShortcutOptionControllerTest {
+
+ private static final String PREF_KEY = "prefKey";
+ private static final String TARGET =
+ new ComponentName("FakePackage", "FakeClass").flattenToString();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private VolumeKeysShortcutOptionController mController;
+ private ShortcutOptionPreference mShortcutOptionPreference;
+
+ private PreferenceScreen mPreferenceScreen;
+
+ @Before
+ public void setUp() {
+ mController = new VolumeKeysShortcutOptionController(
+ mContext, PREF_KEY);
+ mController.setShortcutTargets(Set.of(TARGET));
+ mShortcutOptionPreference = new ShortcutOptionPreference(mContext);
+ mShortcutOptionPreference.setKey(PREF_KEY);
+ mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ mPreferenceScreen.addPreference(mShortcutOptionPreference);
+ }
+
+ @Test
+ public void displayPreference_verifyScreenTextSet() {
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mShortcutOptionPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(R.string.accessibility_shortcut_edit_dialog_title_hardware));
+ assertThat(mShortcutOptionPreference.getSummary().toString()).isEqualTo(
+ mContext.getString(R.string.accessibility_shortcut_edit_dialog_summary_hardware));
+ }
+
+ @Test
+ public void isShortcutAvailable_returnsTrue() {
+ assertThat(mController.isShortcutAvailable()).isTrue();
+ }
+
+ @Test
+ public void isChecked_targetUseVolumeKeyShortcut_returnTrue() {
+ ShortcutUtils.optInValueToSettings(
+ mContext, ShortcutConstants.UserShortcutType.HARDWARE, TARGET);
+
+ assertThat(mController.isChecked()).isTrue();
+ }
+
+ @Test
+ public void isChecked_targetNotUseVolumeKeyShortcut_returnFalse() {
+ ShortcutUtils.optOutValueFromSettings(
+ mContext, ShortcutConstants.UserShortcutType.HARDWARE, TARGET);
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void enableShortcutForTargets_enableVolumeKeysShortcut_shortcutSet() {
+ mController.enableShortcutForTargets(true);
+
+ assertThat(
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mContext, ShortcutConstants.UserShortcutType.HARDWARE, TARGET)).isTrue();
+ }
+
+ @Test
+ public void enableShortcutForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
+ mController.enableShortcutForTargets(false);
+
+ assertThat(
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mContext, ShortcutConstants.UserShortcutType.HARDWARE, TARGET)).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/AccessibilityTestUtils.java b/tests/robotests/src/com/android/settings/testutils/AccessibilityTestUtils.java
new file mode 100644
index 00000000000..8cda2d94686
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/AccessibilityTestUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.testutils;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+
+/**
+ * Utility class for common methods used in the accessibility feature related tests
+ */
+public class AccessibilityTestUtils {
+
+ public static void setSoftwareShortcutMode(
+ Context context, boolean gestureNavEnabled, boolean floatingButtonEnabled) {
+ int mode = floatingButtonEnabled ? ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU : -1;
+
+ Settings.Secure.putInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mode);
+
+ if (gestureNavEnabled) {
+ SettingsShadowResources.overrideResource(
+ com.android.internal.R.integer.config_navBarInteractionMode,
+ NAV_BAR_MODE_GESTURAL);
+ } else {
+ SettingsShadowResources.overrideResource(
+ com.android.internal.R.integer.config_navBarInteractionMode,
+ NAV_BAR_MODE_3BUTTON);
+ }
+ }
+}