diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index f3eab935e91..8de49365060 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -23,7 +23,6 @@ import android.accessibilityservice.AccessibilityShortcutInfo; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; -import android.content.pm.ServiceInfo; import android.hardware.input.InputManager; import android.os.Bundle; import android.os.Handler; @@ -31,7 +30,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.Pair; import android.view.InputDevice; import android.view.accessibility.AccessibilityManager; @@ -59,8 +57,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; /** Activity with the accessibility settings. */ @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) @@ -415,14 +411,14 @@ public class AccessibilitySettings extends DashboardFragment implements final List installedShortcutList = a11yManager.getInstalledAccessibilityShortcutListAsUser(getPrefContext(), UserHandle.myUserId()); - final List modifiableInstalledServiceList = - new ArrayList<>(a11yManager.getInstalledAccessibilityServiceList()); + final List installedServiceList = + a11yManager.getInstalledAccessibilityServiceList(); final List preferenceList = getInstalledAccessibilityPreferences( - getPrefContext(), installedShortcutList, modifiableInstalledServiceList); + getPrefContext(), installedShortcutList, installedServiceList); if (Flags.checkPrebundledIsPreinstalled()) { removeNonPreinstalledComponents(mPreBundledServiceComponentToCategoryMap, - installedShortcutList, modifiableInstalledServiceList); + installedShortcutList, installedServiceList); } final PreferenceCategory downloadedServicesCategory = @@ -474,31 +470,18 @@ public class AccessibilitySettings extends DashboardFragment implements *

{@code modifiableInstalledServiceList} may be modified to remove any entries with * matching package name and label as an entry in {@code installedShortcutList}. * - * @param installedShortcutList A list of installed {@link AccessibilityShortcutInfo}s. - * @param modifiableInstalledServiceList A modifiable list of installed - * {@link AccessibilityServiceInfo}s. + * @param installedShortcutList A list of installed {@link AccessibilityShortcutInfo}s. + * @param installedServiceList A list of installed {@link AccessibilityServiceInfo}s. */ private List getInstalledAccessibilityPreferences(Context context, List installedShortcutList, - List modifiableInstalledServiceList) { + List installedServiceList) { final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context); final List activityList = preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList); - final Set> packageLabelPairs = - activityList.stream() - .map(a11yActivityPref -> new Pair<>( - a11yActivityPref.getPackageName(), a11yActivityPref.getLabel()) - ).collect(Collectors.toSet()); - - // Remove duplicate A11yServices that are already shown as A11yActivities. - if (!packageLabelPairs.isEmpty()) { - modifiableInstalledServiceList.removeIf( - target -> containsPackageAndLabelInList(packageLabelPairs, target)); - } final List serviceList = - preferenceHelper.createAccessibilityServicePreferenceList( - modifiableInstalledServiceList); + preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList); final List preferenceList = new ArrayList<>(); preferenceList.addAll(activityList); @@ -523,16 +506,6 @@ public class AccessibilitySettings extends DashboardFragment implements } } - private boolean containsPackageAndLabelInList( - Set> packageLabelPairs, - AccessibilityServiceInfo targetServiceInfo) { - final ServiceInfo serviceInfo = targetServiceInfo.getResolveInfo().serviceInfo; - final String servicePackageName = serviceInfo.packageName; - final CharSequence serviceLabel = serviceInfo.loadLabel(getPackageManager()); - - return packageLabelPairs.contains(new Pair<>(servicePackageName, serviceLabel)); - } - private void initializePreBundledServicesMapFromArray(String categoryKey, int key) { String[] services = getResources().getStringArray(key); PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey); diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 86763fd983c..3982dc0c68c 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -26,10 +26,13 @@ import static org.robolectric.Shadows.shadowOf; import static java.util.Collections.singletonList; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityShortcutInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.database.ContentObserver; @@ -48,6 +51,7 @@ import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.testutils.XmlTestUtils; +import com.android.settings.testutils.shadow.ShadowAccessibilityManager; import com.android.settings.testutils.shadow.ShadowApplicationPackageManager; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; @@ -73,7 +77,6 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowAccessibilityManager; import org.robolectric.shadows.ShadowContentResolver; import org.xmlpull.v1.XmlPullParserException; @@ -85,6 +88,7 @@ import java.util.List; /** Test for {@link AccessibilitySettings}. */ @RunWith(RobolectricTestRunner.class) @Config(shadows = { + ShadowAccessibilityManager.class, ShadowBluetoothAdapter.class, ShadowUserManager.class, ShadowColorDisplayManager.class, @@ -93,8 +97,10 @@ import java.util.List; }) public class AccessibilitySettingsTest { private static final String PACKAGE_NAME = "com.android.test"; - private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service"; - private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME, CLASS_NAME); + private static final ComponentName SERVICE_COMPONENT_NAME = + new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".test_a11y_service"); + private static final ComponentName ACTIVITY_COMPONENT_NAME = + new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".test_a11y_activity"); private static final String EMPTY_STRING = ""; private static final String DEFAULT_SUMMARY = "default summary"; private static final String DEFAULT_DESCRIPTION = "default description"; @@ -108,7 +114,7 @@ public class AccessibilitySettingsTest { private final Context mContext = ApplicationProvider.getApplicationContext(); @Spy private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo( - new ComponentName(PACKAGE_NAME, CLASS_NAME)); + SERVICE_COMPONENT_NAME); private ShadowAccessibilityManager mShadowAccessibilityManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; @@ -117,7 +123,8 @@ public class AccessibilitySettingsTest { @Before public void setup() { - mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext)); + mShadowAccessibilityManager = Shadow.extract( + mContext.getSystemService(AccessibilityManager.class)); mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>()); mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; @@ -369,7 +376,7 @@ public class AccessibilitySettingsTest { mFragment.onContentChanged(); RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference( - COMPONENT_NAME.flattenToString()); + SERVICE_COMPONENT_NAME.flattenToString()); assertThat(preference).isNotNull(); @@ -389,7 +396,7 @@ public class AccessibilitySettingsTest { mFragment.onResume(); RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference( - COMPONENT_NAME.flattenToString()); + SERVICE_COMPONENT_NAME.flattenToString()); assertThat(preference).isNotNull(); @@ -430,6 +437,36 @@ public class AccessibilitySettingsTest { assertThat(pref).isNull(); } + @Test + public void testSameNamedServiceAndActivity_bothPreferencesExist() { + final PackageManager pm = mContext.getPackageManager(); + AccessibilityServiceInfo a11yServiceInfo = mServiceInfo; + AccessibilityShortcutInfo a11yShortcutInfo = getMockAccessibilityShortcutInfo(); + // Ensure the test service and activity have the same package name and label. + // Before this change, any service and activity with the same package name and + // label would cause the service to be hidden. + assertThat(a11yServiceInfo.getComponentName()) + .isNotEqualTo(a11yShortcutInfo.getComponentName()); + assertThat(a11yServiceInfo.getComponentName().getPackageName()) + .isEqualTo(a11yShortcutInfo.getComponentName().getPackageName()); + assertThat(a11yServiceInfo.getResolveInfo().serviceInfo.loadLabel(pm)) + .isEqualTo(a11yShortcutInfo.getActivityInfo().loadLabel(pm)); + // Prepare A11yManager with the test service and activity. + mShadowAccessibilityManager.setInstalledAccessibilityServiceList( + List.of(mServiceInfo)); + mShadowAccessibilityManager.setInstalledAccessibilityShortcutListAsUser( + List.of(getMockAccessibilityShortcutInfo())); + setupFragment(); + + // Both service and activity preferences should exist on the page. + RestrictedPreference servicePref = mFragment.getPreferenceScreen().findPreference( + a11yServiceInfo.getComponentName().flattenToString()); + RestrictedPreference activityPref = mFragment.getPreferenceScreen().findPreference( + a11yShortcutInfo.getComponentName().flattenToString()); + assertThat(servicePref).isNotNull(); + assertThat(activityPref).isNotNull(); + } + private String getPreferenceCategory(ComponentName componentName) { return mFragment.mServicePreferenceToPreferenceCategoryMap.get( mFragment.getPreferenceScreen().findPreference( @@ -444,11 +481,12 @@ public class AccessibilitySettingsTest { boolean isSystemApp) { final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); when(applicationInfo.isSystemApp()).thenReturn(isSystemApp); - final ServiceInfo serviceInfo = new ServiceInfo(); + final ServiceInfo serviceInfo = Mockito.spy(new ServiceInfo()); applicationInfo.packageName = componentName.getPackageName(); serviceInfo.packageName = componentName.getPackageName(); serviceInfo.name = componentName.getClassName(); serviceInfo.applicationInfo = applicationInfo; + when(serviceInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); final ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = serviceInfo; @@ -464,6 +502,18 @@ public class AccessibilitySettingsTest { return null; } + private AccessibilityShortcutInfo getMockAccessibilityShortcutInfo() { + AccessibilityShortcutInfo mockInfo = Mockito.mock(AccessibilityShortcutInfo.class); + final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class); + activityInfo.applicationInfo = new ApplicationInfo(); + when(mockInfo.getActivityInfo()).thenReturn(activityInfo); + when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); + when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY); + when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION); + when(mockInfo.getComponentName()).thenReturn(ACTIVITY_COMPONENT_NAME); + return mockInfo; + } + private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) { info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccessibilityManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccessibilityManager.java index de7792c2ec0..fcd1e42c547 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccessibilityManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccessibilityManager.java @@ -16,15 +16,18 @@ package com.android.settings.testutils.shadow; +import android.accessibilityservice.AccessibilityShortcutInfo; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ComponentName; +import android.content.Context; import android.util.ArrayMap; import android.view.accessibility.AccessibilityManager; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import java.util.List; import java.util.Map; /** @@ -33,9 +36,10 @@ import java.util.Map; @Implements(AccessibilityManager.class) public class ShadowAccessibilityManager extends org.robolectric.shadows.ShadowAccessibilityManager { private Map mA11yFeatureToTileMap = new ArrayMap<>(); + private List mInstalledAccessibilityShortcutList = List.of(); /** - * Implements a hidden method {@link AccessibilityManager.getA11yFeatureToTileMap} + * Implements a hidden method {@link AccessibilityManager#getA11yFeatureToTileMap} */ @Implementation public Map getA11yFeatureToTileMap(@UserIdInt int userId) { @@ -49,4 +53,22 @@ public class ShadowAccessibilityManager extends org.robolectric.shadows.ShadowAc @NonNull Map a11yFeatureToTileMap) { mA11yFeatureToTileMap = a11yFeatureToTileMap; } + + /** + * Implements the hidden method + * {@link AccessibilityManager#getInstalledAccessibilityShortcutListAsUser}. + */ + @Implementation + public List getInstalledAccessibilityShortcutListAsUser( + @NonNull Context context, @UserIdInt int userId) { + return mInstalledAccessibilityShortcutList; + } + + /** + * Sets the value to be returned by {@link #getInstalledAccessibilityShortcutListAsUser}. + */ + public void setInstalledAccessibilityShortcutListAsUser( + @NonNull List installedAccessibilityShortcutList) { + mInstalledAccessibilityShortcutList = installedAccessibilityShortcutList; + } }