diff options
author | menghanli <menghanli@google.com> | 2022-12-20 15:30:53 +0800 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-02-13 00:53:39 +0000 |
commit | 996ff01b7586c4d224e5d8729df1728afabc76e3 (patch) | |
tree | 97244f8e07edcc68052c7f0d8bfdba5e10f9bcc3 | |
parent | 156b63b859d4eb831e4fbdde9a1bf85e2190300e (diff) | |
download | base-996ff01b7586c4d224e5d8729df1728afabc76e3.tar.gz |
Avoid users add one-handed mode by edit shortcut dialog in unsupported devices
Root cause: We show preinstalled framework features in edit shortcut dialog
Solution: Only show one-handed mode if the device supports
Bug: 260182478
Test: atest AccessibilityTargetHelperTest
Change-Id: Id0696bff522cb7e19c5a16a47bbb76a794472c52
merged-in: Id0696bff522cb7e19c5a16a47bbb76a794472c52
(cherry picked from commit 1a9081a982970d812676a3ee3e3aa7fe27f3b449)
(cherry picked from commit e30a26970eef3a64009f7692c1cf31c5aceafbc1)
Merged-In: Id0696bff522cb7e19c5a16a47bbb76a794472c52
7 files changed, 299 insertions, 22 deletions
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 43be0312245e..1b901f5fd09c 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; +import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE; import static com.android.internal.util.ArrayUtils.convertToLongArray; import android.accessibilityservice.AccessibilityServiceInfo; @@ -147,11 +148,13 @@ public class AccessibilityShortcutController { Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, "1" /* Value to enable */, "0" /* Value to disable */, R.string.color_correction_feature_name)); - featuresMap.put(ONE_HANDED_COMPONENT_NAME, - new ToggleableFrameworkFeatureInfo( - Settings.Secure.ONE_HANDED_MODE_ACTIVATED, - "1" /* Value to enable */, "0" /* Value to disable */, - R.string.one_handed_mode_feature_name)); + if (SUPPORT_ONE_HANDED_MODE) { + featuresMap.put(ONE_HANDED_COMPONENT_NAME, + new ToggleableFrameworkFeatureInfo( + Settings.Secure.ONE_HANDED_MODE_ACTIVATED, + "1" /* Value to enable */, "0" /* Value to disable */, + R.string.one_handed_mode_feature_name)); + } featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME, new ToggleableFrameworkFeatureInfo( Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java index fc2c8cca9796..2d87745fcadc 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java @@ -25,6 +25,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME; import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType; import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained; +import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityShortcutInfo; @@ -209,6 +210,7 @@ public final class AccessibilityTargetHelper { context.getString(R.string.accessibility_magnification_chooser_text), context.getDrawable(R.drawable.ic_accessibility_magnification), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); + targets.add(magnification); final ToggleAllowListingFeatureTarget daltonizer = new ToggleAllowListingFeatureTarget(context, @@ -219,6 +221,7 @@ public final class AccessibilityTargetHelper { context.getString(R.string.color_correction_feature_name), context.getDrawable(R.drawable.ic_accessibility_color_correction), Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED); + targets.add(daltonizer); final ToggleAllowListingFeatureTarget colorInversion = new ToggleAllowListingFeatureTarget(context, @@ -229,16 +232,20 @@ public final class AccessibilityTargetHelper { context.getString(R.string.color_inversion_feature_name), context.getDrawable(R.drawable.ic_accessibility_color_inversion), Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); + targets.add(colorInversion); - final ToggleAllowListingFeatureTarget oneHandedMode = - new ToggleAllowListingFeatureTarget(context, - shortcutType, - isShortcutContained(context, shortcutType, - ONE_HANDED_COMPONENT_NAME.flattenToString()), - ONE_HANDED_COMPONENT_NAME.flattenToString(), - context.getString(R.string.one_handed_mode_feature_name), - context.getDrawable(R.drawable.ic_accessibility_one_handed), - Settings.Secure.ONE_HANDED_MODE_ACTIVATED); + if (SUPPORT_ONE_HANDED_MODE) { + final ToggleAllowListingFeatureTarget oneHandedMode = + new ToggleAllowListingFeatureTarget(context, + shortcutType, + isShortcutContained(context, shortcutType, + ONE_HANDED_COMPONENT_NAME.flattenToString()), + ONE_HANDED_COMPONENT_NAME.flattenToString(), + context.getString(R.string.one_handed_mode_feature_name), + context.getDrawable(R.drawable.ic_accessibility_one_handed), + Settings.Secure.ONE_HANDED_MODE_ACTIVATED); + targets.add(oneHandedMode); + } final ToggleAllowListingFeatureTarget reduceBrightColors = new ToggleAllowListingFeatureTarget(context, @@ -249,11 +256,6 @@ public final class AccessibilityTargetHelper { context.getString(R.string.reduce_bright_colors_feature_name), context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors), Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED); - - targets.add(magnification); - targets.add(daltonizer); - targets.add(colorInversion); - targets.add(oneHandedMode); targets.add(reduceBrightColors); return targets; diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java index 98d81c9598b8..cccd80e82420 100644 --- a/core/java/com/android/internal/os/RoSystemProperties.java +++ b/core/java/com/android/internal/os/RoSystemProperties.java @@ -31,6 +31,8 @@ public class RoSystemProperties { SystemProperties.getInt("ro.factorytest", 0); public static final String CONTROL_PRIVAPP_PERMISSIONS = SystemProperties.get("ro.control_privapp_permissions"); + public static final boolean SUPPORT_ONE_HANDED_MODE = + SystemProperties.getBoolean("ro.support_one_handed_mode", /* def= */ false); // ------ ro.hdmi.* -------- // /** diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 3e4b1cc87ef8..e96c64206c45 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1414,6 +1414,7 @@ <activity android:name="com.android.internal.app.ChooserWrapperActivity"/> <activity android:name="com.android.internal.app.ResolverWrapperActivity"/> <activity android:name="com.android.internal.app.IntentForwarderActivityTest$IntentForwarderWrapperActivity"/> + <activity android:name="com.android.internal.accessibility.AccessibilityShortcutChooserActivityTest$TestAccessibilityShortcutChooserActivity"/> <receiver android:name="android.app.activity.AbortReceiver" android:exported="true"> diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java new file mode 100644 index 000000000000..973b904c9344 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2022 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.internal.accessibility; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.doubleClick; +import static androidx.test.espresso.action.ViewActions.scrollTo; +import static androidx.test.espresso.action.ViewActions.swipeUp; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withClassName; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.endsWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; + +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ActivityScenario; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; + +/** + * Tests for {@link AccessibilityShortcutChooserActivity}. + */ +@RunWith(AndroidJUnit4.class) +public class AccessibilityShortcutChooserActivityTest { + private static final String ONE_HANDED_MODE = "One-Handed mode"; + private static final String TEST_LABEL = "TEST_LABEL"; + private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class"); + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock + private AccessibilityServiceInfo mAccessibilityServiceInfo; + @Mock + private ResolveInfo mResolveInfo; + @Mock + private ServiceInfo mServiceInfo; + @Mock + private ApplicationInfo mApplicationInfo; + @Mock + private IAccessibilityManager mAccessibilityManagerService; + + @Test + public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() + throws Exception { + configureTestService(); + final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario = + ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); + scenario.moveToState(Lifecycle.State.CREATED); + scenario.moveToState(Lifecycle.State.STARTED); + scenario.moveToState(Lifecycle.State.RESUMED); + + onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot( + isDialog()).check(matches(isDisplayed())); + onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click()); + onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick()); + onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(), + click()); + + onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check( + doesNotExist()); + scenario.moveToState(Lifecycle.State.DESTROYED); + } + + @Test + public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() { + TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true); + final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario = + ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); + scenario.moveToState(Lifecycle.State.CREATED); + scenario.moveToState(Lifecycle.State.STARTED); + scenario.moveToState(Lifecycle.State.RESUMED); + + onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot( + isDialog()).check(matches(isDisplayed())); + onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click()); + onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(matches(isDisplayed())); + scenario.moveToState(Lifecycle.State.DESTROYED); + } + + @Test + public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() { + TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false); + final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario = + ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); + scenario.moveToState(Lifecycle.State.CREATED); + scenario.moveToState(Lifecycle.State.STARTED); + scenario.moveToState(Lifecycle.State.RESUMED); + + onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot( + isDialog()).check(matches(isDisplayed())); + onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click()); + onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist()); + scenario.moveToState(Lifecycle.State.DESTROYED); + } + + private void configureTestService() throws Exception { + when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo); + mResolveInfo.serviceInfo = mServiceInfo; + mServiceInfo.applicationInfo = mApplicationInfo; + when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL); + when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME); + when(mAccessibilityManagerService.getInstalledAccessibilityServiceList( + anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo)); + + TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService); + } + + /** + * Used for testing. + */ + public static class TestAccessibilityShortcutChooserActivity extends + AccessibilityShortcutChooserActivity { + private static IAccessibilityManager sAccessibilityManagerService; + + public static void setupForTesting(IAccessibilityManager accessibilityManagerService) { + sAccessibilityManagerService = accessibilityManagerService; + } + + @Override + public Object getSystemService(String name) { + if (Context.ACCESSIBILITY_SERVICE.equals(name) + && sAccessibilityManagerService != null) { + return new AccessibilityManager(this, new Handler(getMainLooper()), + sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true); + } + + return super.getSystemService(name); + } + } +} diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index 6baf3056be32..dc04220a0a11 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -21,6 +21,11 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SC import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; +import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -203,6 +208,17 @@ public class AccessibilityShortcutControllerTest { when(mAlertDialog.getWindow()).thenReturn(window); when(mTextToSpeech.getVoice()).thenReturn(mVoice); + + // Clears the sFrameworkShortcutFeaturesMap field which was not properly initialized + // during testing. + try { + Field field = AccessibilityShortcutController.class.getDeclaredField( + "sFrameworkShortcutFeaturesMap"); + field.setAccessible(true); + field.set(window, null); + } catch (Exception e) { + throw new RuntimeException("Unable to set sFrameworkShortcutFeaturesMap", e); + } } @AfterClass @@ -428,11 +444,10 @@ public class AccessibilityShortcutControllerTest { } @Test - public void getFrameworkFeatureMap_shouldBeNonNullAndUnmodifiable() { - Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> + public void getFrameworkFeatureMap_shouldBeUnmodifiable() { + final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> frameworkFeatureMap = AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); - assertTrue("Framework features not supported", frameworkFeatureMap.size() > 0); try { frameworkFeatureMap.clear(); @@ -442,7 +457,38 @@ public class AccessibilityShortcutControllerTest { } } + public void getFrameworkFeatureMap_containsExpectedDefaultKeys() { + final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> + frameworkFeatureMap = + AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); + + assertTrue(frameworkFeatureMap.containsKey(COLOR_INVERSION_COMPONENT_NAME)); + assertTrue(frameworkFeatureMap.containsKey(DALTONIZER_COMPONENT_NAME)); + assertTrue(frameworkFeatureMap.containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME)); + } + + @Test + public void getFrameworkFeatureMap_oneHandedModeEnabled_containsExpectedKey() { + TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true); + + final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> + frameworkFeatureMap = + AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); + + assertTrue(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME)); + } + @Test + public void getFrameworkFeatureMap_oneHandedModeDisabled_containsExpectedKey() { + TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false); + + final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> + frameworkFeatureMap = + AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); + + assertFalse(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME)); + } + public void testOnAccessibilityShortcut_forServiceWithNoSummary_doesNotCrash() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); diff --git a/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java new file mode 100644 index 000000000000..ff014add793a --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 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.internal.accessibility; + +import com.android.internal.os.RoSystemProperties; + +import java.lang.reflect.Field; + +/** + * Test utility methods. + */ +public class TestUtils { + + /** + * Sets the {@code enabled} of the given OneHandedMode flags to simulate device behavior. + */ + public static void setOneHandedModeEnabled(Object obj, boolean enabled) { + try { + final Field field = RoSystemProperties.class.getDeclaredField( + "SUPPORT_ONE_HANDED_MODE"); + field.setAccessible(true); + field.setBoolean(obj, enabled); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} |