Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b0b32697 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Stops hiding a11y services with the same package+label as an activity." into main

parents 37e1d258 c38fd822
Loading
Loading
Loading
Loading
+8 −35
Original line number Diff line number Diff line
@@ -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<AccessibilityShortcutInfo> installedShortcutList =
                a11yManager.getInstalledAccessibilityShortcutListAsUser(getPrefContext(),
                        UserHandle.myUserId());
        final List<AccessibilityServiceInfo> modifiableInstalledServiceList =
                new ArrayList<>(a11yManager.getInstalledAccessibilityServiceList());
        final List<AccessibilityServiceInfo> installedServiceList =
                a11yManager.getInstalledAccessibilityServiceList();
        final List<RestrictedPreference> preferenceList = getInstalledAccessibilityPreferences(
                getPrefContext(), installedShortcutList, modifiableInstalledServiceList);
                getPrefContext(), installedShortcutList, installedServiceList);

        if (Flags.checkPrebundledIsPreinstalled()) {
            removeNonPreinstalledComponents(mPreBundledServiceComponentToCategoryMap,
                    installedShortcutList, modifiableInstalledServiceList);
                    installedShortcutList, installedServiceList);
        }

        final PreferenceCategory downloadedServicesCategory =
@@ -475,30 +471,17 @@ public class AccessibilitySettings extends DashboardFragment implements
     * 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 installedServiceList  A list of installed {@link AccessibilityServiceInfo}s.
     */
    private List<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
            List<AccessibilityShortcutInfo> installedShortcutList,
            List<AccessibilityServiceInfo> modifiableInstalledServiceList) {
            List<AccessibilityServiceInfo> installedServiceList) {
        final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);

        final List<AccessibilityActivityPreference> activityList =
                preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList);
        final Set<Pair<String, CharSequence>> 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<RestrictedPreference> serviceList =
                preferenceHelper.createAccessibilityServicePreferenceList(
                        modifiableInstalledServiceList);
                preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList);

        final List<RestrictedPreference> preferenceList = new ArrayList<>();
        preferenceList.addAll(activityList);
@@ -523,16 +506,6 @@ public class AccessibilitySettings extends DashboardFragment implements
        }
    }

    private boolean containsPackageAndLabelInList(
            Set<Pair<String, CharSequence>> 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);
+58 −8
Original line number Diff line number Diff line
@@ -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;
+23 −1
Original line number Diff line number Diff line
@@ -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<ComponentName, ComponentName> mA11yFeatureToTileMap = new ArrayMap<>();
    private List<AccessibilityShortcutInfo> mInstalledAccessibilityShortcutList = List.of();

    /**
     * Implements a hidden method {@link AccessibilityManager.getA11yFeatureToTileMap}
     * Implements a hidden method {@link AccessibilityManager#getA11yFeatureToTileMap}
     */
    @Implementation
    public Map<ComponentName, ComponentName> getA11yFeatureToTileMap(@UserIdInt int userId) {
@@ -49,4 +53,22 @@ public class ShadowAccessibilityManager extends org.robolectric.shadows.ShadowAc
            @NonNull Map<ComponentName, ComponentName> a11yFeatureToTileMap) {
        mA11yFeatureToTileMap = a11yFeatureToTileMap;
    }

    /**
     * Implements the hidden method
     * {@link AccessibilityManager#getInstalledAccessibilityShortcutListAsUser}.
     */
    @Implementation
    public List<AccessibilityShortcutInfo> getInstalledAccessibilityShortcutListAsUser(
            @NonNull Context context, @UserIdInt int userId) {
        return mInstalledAccessibilityShortcutList;
    }

    /**
     * Sets the value to be returned by {@link #getInstalledAccessibilityShortcutListAsUser}.
     */
    public void setInstalledAccessibilityShortcutListAsUser(
            @NonNull List<AccessibilityShortcutInfo> installedAccessibilityShortcutList) {
        mInstalledAccessibilityShortcutList = installedAccessibilityShortcutList;
    }
}