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

Commit a0eeda0d authored by Jason Hsu's avatar Jason Hsu Committed by Android (Google) Code Review
Browse files

Merge "Extract inner helper from AccessibilitySettings to public" into tm-dev

parents 963abde2 f02c069f
Loading
Loading
Loading
Loading
+2 −313
Original line number Diff line number Diff line
@@ -16,21 +16,13 @@

package com.android.settings.accessibility;

import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
@@ -40,22 +32,17 @@ import android.util.ArrayMap;
import android.view.accessibility.AccessibilityManager;

import androidx.annotation.VisibleForTesting;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;

import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.content.PackageMonitor;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;

@@ -63,7 +50,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/** Activity with the accessibility settings. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
@@ -71,9 +57,6 @@ public class AccessibilitySettings extends DashboardFragment {

    private static final String TAG = "AccessibilitySettings";

    // Index of the first preference in a preference category.
    private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1;

    // Preference categories
    private static final String CATEGORY_SCREEN_READER = "screen_reader_category";
    private static final String CATEGORY_CAPTIONS = "captions_category";
@@ -249,8 +232,7 @@ public class AccessibilitySettings extends DashboardFragment {
     * @param serviceEnabled Whether the accessibility service is enabled.
     * @return The service summary
     */
    @VisibleForTesting
    static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info,
    public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info,
            boolean serviceEnabled) {
        if (serviceEnabled && info.crashed) {
            return context.getText(R.string.accessibility_summary_state_stopped);
@@ -289,8 +271,7 @@ public class AccessibilitySettings extends DashboardFragment {
     * @param serviceEnabled Whether the accessibility service is enabled.
     * @return The service description
     */
    @VisibleForTesting
    static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info,
    public static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info,
            boolean serviceEnabled) {
        if (serviceEnabled && info.crashed) {
            return context.getText(R.string.accessibility_description_state_stopped);
@@ -506,296 +487,4 @@ public class AccessibilitySettings extends DashboardFragment {
                                    context);
                }
            };

    /**
     * This class helps setup RestrictedPreference.
     */
    @VisibleForTesting
    static class RestrictedPreferenceHelper {
        private final Context mContext;
        private final DevicePolicyManager mDpm;
        private final PackageManager mPm;
        private final AppOpsManager mAppOps;

        RestrictedPreferenceHelper(Context context) {
            mContext = context;
            mDpm = context.getSystemService(DevicePolicyManager.class);
            mPm = context.getPackageManager();
            mAppOps = context.getSystemService(AppOpsManager.class);
        }

        /**
         * Creates the list of {@link RestrictedPreference} with the installedServices arguments.
         *
         * @param installedServices The list of {@link AccessibilityServiceInfo}s of the
         *                          installed accessibility services
         * @return The list of {@link RestrictedPreference}
         */
        @VisibleForTesting
        List<RestrictedPreference> createAccessibilityServicePreferenceList(
                List<AccessibilityServiceInfo> installedServices) {

            final Set<ComponentName> enabledServices =
                    AccessibilityUtils.getEnabledServicesFromSettings(mContext);
            final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
                    UserHandle.myUserId());
            final int installedServicesSize = installedServices.size();

            final List<RestrictedPreference> preferenceList = new ArrayList<>(
                    installedServicesSize);

            for (int i = 0; i < installedServicesSize; ++i) {
                final AccessibilityServiceInfo info = installedServices.get(i);
                final ResolveInfo resolveInfo = info.getResolveInfo();
                final String packageName = resolveInfo.serviceInfo.packageName;
                final ComponentName componentName = new ComponentName(packageName,
                        resolveInfo.serviceInfo.name);

                final String key = componentName.flattenToString();
                final CharSequence title = resolveInfo.loadLabel(mPm);
                final boolean serviceEnabled = enabledServices.contains(componentName);
                final CharSequence summary = getServiceSummary(mContext, info, serviceEnabled);
                final String fragment = getAccessibilityServiceFragmentTypeName(info);

                Drawable icon = resolveInfo.loadIcon(mPm);
                if (resolveInfo.getIconResource() == 0) {
                    icon = ContextCompat.getDrawable(mContext,
                            R.drawable.ic_accessibility_generic);
                }

                final RestrictedPreference preference = createRestrictedPreference(key, title,
                        summary, icon, fragment, packageName,
                        resolveInfo.serviceInfo.applicationInfo.uid);

                setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled);

                final String prefKey = preference.getKey();
                final int imageRes = info.getAnimatedImageRes();
                final CharSequence intro = info.loadIntro(mPm);
                final CharSequence description = getServiceDescription(mContext, info,
                        serviceEnabled);
                final String htmlDescription = info.loadHtmlDescription(mPm);
                final String settingsClassName = info.getSettingsActivityName();
                final String tileServiceClassName = info.getTileServiceName();

                putBasicExtras(preference, prefKey, title, intro, description, imageRes,
                        htmlDescription, componentName);
                putServiceExtras(preference, resolveInfo, serviceEnabled);
                putSettingsExtras(preference, packageName, settingsClassName);
                putTileServiceExtras(preference, packageName, tileServiceClassName);

                preferenceList.add(preference);
            }
            return preferenceList;
        }

        /**
         * Create the list of {@link RestrictedPreference} with the installedShortcuts arguments.
         *
         * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the
         *                           installed accessibility shortcuts
         * @return The list of {@link RestrictedPreference}
         */
        @VisibleForTesting
        List<RestrictedPreference> createAccessibilityActivityPreferenceList(
                List<AccessibilityShortcutInfo> installedShortcuts) {
            final Set<ComponentName> enabledServices =
                    AccessibilityUtils.getEnabledServicesFromSettings(mContext);
            final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
                    UserHandle.myUserId());

            final int installedShortcutsSize = installedShortcuts.size();
            final List<RestrictedPreference> preferenceList = new ArrayList<>(
                    installedShortcutsSize);

            for (int i = 0; i < installedShortcutsSize; ++i) {
                final AccessibilityShortcutInfo info = installedShortcuts.get(i);
                final ActivityInfo activityInfo = info.getActivityInfo();
                final ComponentName componentName = info.getComponentName();

                final String key = componentName.flattenToString();
                final CharSequence title = activityInfo.loadLabel(mPm);
                final String summary = info.loadSummary(mPm);
                final String fragment =
                        LaunchAccessibilityActivityPreferenceFragment.class.getName();

                Drawable icon = activityInfo.loadIcon(mPm);
                if (activityInfo.getIconResource() == 0) {
                    icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic);
                }

                final RestrictedPreference preference = createRestrictedPreference(key, title,
                        summary, icon, fragment, componentName.getPackageName(),
                        activityInfo.applicationInfo.uid);
                final boolean serviceEnabled = enabledServices.contains(componentName);

                setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled);

                final String prefKey = preference.getKey();
                final CharSequence intro = info.loadIntro(mPm);
                final String description = info.loadDescription(mPm);
                final int imageRes = info.getAnimatedImageRes();
                final String htmlDescription = info.loadHtmlDescription(mPm);
                final String settingsClassName = info.getSettingsActivityName();
                final String tileServiceClassName = info.getTileServiceName();

                putBasicExtras(preference, prefKey, title, intro, description, imageRes,
                        htmlDescription, componentName);
                putSettingsExtras(preference, componentName.getPackageName(), settingsClassName);
                putTileServiceExtras(preference, componentName.getPackageName(),
                        tileServiceClassName);

                preferenceList.add(preference);
            }
            return preferenceList;
        }

        private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) {
            // Shorten the name to avoid exceeding 100 characters in one line.
            final String volumeShortcutToggleAccessibilityServicePreferenceFragment =
                    VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName();

            switch (AccessibilityUtil.getAccessibilityServiceFragmentType(info)) {
                case AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE:
                    return volumeShortcutToggleAccessibilityServicePreferenceFragment;
                case AccessibilityServiceFragmentType.INVISIBLE_TOGGLE:
                    return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName();
                case AccessibilityServiceFragmentType.TOGGLE:
                    return ToggleAccessibilityServicePreferenceFragment.class.getName();
                default:
                    // impossible status
                    throw new AssertionError();
            }
        }

        private RestrictedPreference createRestrictedPreference(String key, CharSequence title,
                CharSequence summary, Drawable icon, String fragment, String packageName, int uid) {
            final RestrictedPreference preference = new RestrictedPreference(mContext, packageName,
                    uid);

            preference.setKey(key);
            preference.setTitle(title);
            preference.setSummary(summary);
            preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE));
            preference.setFragment(fragment);
            preference.setIconSize(ICON_SIZE_MEDIUM);
            preference.setPersistent(false); // Disable SharedPreferences.
            preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX);

            return preference;
        }

        private void setRestrictedPreferenceEnabled(RestrictedPreference preference,
                final List<String> permittedServices, boolean serviceEnabled) {
            // permittedServices null means all accessibility services are allowed.
            boolean serviceAllowed = permittedServices == null || permittedServices.contains(
                    preference.getPackageName());
            boolean appOpsAllowed;
            if (serviceAllowed) {
                try {
                    final int mode = mAppOps.noteOpNoThrow(
                            AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
                            preference.getUid(), preference.getPackageName());
                    appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED;
                    serviceAllowed = appOpsAllowed;
                } catch (Exception e) {
                    // Allow service in case if app ops is not available in testing.
                    appOpsAllowed = true;
                }
            } else {
                appOpsAllowed = false;
            }
            if (serviceAllowed || serviceEnabled) {
                preference.setEnabled(true);
            } else {
                // Disable accessibility service that are not permitted.
                final EnforcedAdmin admin =
                        RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
                                mContext, preference.getPackageName(), UserHandle.myUserId());

                if (admin != null) {
                    preference.setDisabledByAdmin(admin);
                } else if (!appOpsAllowed) {
                    preference.setDisabledByAppOps(true);
                } else {
                    preference.setEnabled(false);
                }
            }
        }

        /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */
        private void putBasicExtras(RestrictedPreference preference, String prefKey,
                CharSequence title, CharSequence intro, CharSequence summary, int imageRes,
                String htmlDescription, ComponentName componentName) {
            final Bundle extras = preference.getExtras();
            extras.putString(EXTRA_PREFERENCE_KEY, prefKey);
            extras.putCharSequence(EXTRA_TITLE, title);
            extras.putCharSequence(EXTRA_INTRO, intro);
            extras.putCharSequence(EXTRA_SUMMARY, summary);
            extras.putParcelable(EXTRA_COMPONENT_NAME, componentName);
            extras.putInt(EXTRA_ANIMATED_IMAGE_RES, imageRes);
            extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
        }

        /**
         * Puts the service extras into {@link RestrictedPreference}'s getExtras().
         *
         * <p><b>Note:</b> Called by {@link AccessibilityServiceInfo}.</p>
         *
         * @param preference The preference we are configuring.
         * @param resolveInfo The service resolve info.
         * @param serviceEnabled Whether the accessibility service is enabled.
         */
        private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo,
                Boolean serviceEnabled) {
            final Bundle extras = preference.getExtras();

            extras.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo);
            extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
        }

        /**
         * Puts the settings extras into {@link RestrictedPreference}'s getExtras().
         *
         * <p><b>Note:</b> Called when settings UI is needed.</p>
         *
         * @param preference The preference we are configuring.
         * @param packageName Package of accessibility feature.
         * @param settingsClassName The component name of an activity that allows the user to modify
         *                          the settings for this accessibility feature.
         */
        private void putSettingsExtras(RestrictedPreference preference, String packageName,
                String settingsClassName) {
            final Bundle extras = preference.getExtras();

            if (!TextUtils.isEmpty(settingsClassName)) {
                extras.putString(EXTRA_SETTINGS_TITLE,
                        mContext.getText(R.string.accessibility_menu_item_settings).toString());
                extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
                        new ComponentName(packageName, settingsClassName).flattenToString());
            }
        }

        /**
         * Puts the information about a particular application
         * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s
         * getExtras().
         *
         * <p><b>Note:</b> Called when a tooltip of
         * {@link android.service.quicksettings.TileService} is needed.</p>
         *
         * @param preference The preference we are configuring.
         * @param packageName Package of accessibility feature.
         * @param tileServiceClassName The component name of tileService is associated with this
         *                             accessibility feature.
         */
        private void putTileServiceExtras(RestrictedPreference preference, String packageName,
                String tileServiceClassName) {
            final Bundle extras = preference.getExtras();
            if (!TextUtils.isEmpty(tileServiceClassName)) {
                extras.putString(EXTRA_TILE_SERVICE_COMPONENT_NAME,
                        new ComponentName(packageName, tileServiceClassName).flattenToString());
            }
        }
    }
}
+337 −0

File added.

Preview size limit exceeded, changes collapsed.

+0 −33
Original line number Diff line number Diff line
@@ -87,8 +87,6 @@ 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 int ON = 1;
    private static final int OFF = 0;
    private static final String EMPTY_STRING = "";
    private static final String DEFAULT_SUMMARY = "default summary";
    private static final String DEFAULT_DESCRIPTION = "default description";
@@ -246,37 +244,6 @@ public class AccessibilitySettingsTest {
        assertThat(description).isEqualTo(DEFAULT_DESCRIPTION);
    }

    @Test
    public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() {
        final String key = COMPONENT_NAME.flattenToString();
        final AccessibilitySettings.RestrictedPreferenceHelper helper =
                new AccessibilitySettings.RestrictedPreferenceHelper(mContext);
        final List<AccessibilityServiceInfo> infoList = new ArrayList<>(
                singletonList(mServiceInfo));

        final List<RestrictedPreference> preferenceList =
                helper.createAccessibilityServicePreferenceList(infoList);
        RestrictedPreference preference = preferenceList.get(0);

        assertThat(preference.getKey()).isEqualTo(key);
    }

    @Test
    public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() {
        final String key = COMPONENT_NAME.flattenToString();
        final AccessibilitySettings.RestrictedPreferenceHelper helper =
                new AccessibilitySettings.RestrictedPreferenceHelper(mContext);
        setMockAccessibilityShortcutInfo(mShortcutInfo);
        final List<AccessibilityShortcutInfo> infoList = new ArrayList<>(
                singletonList(mShortcutInfo));

        final List<RestrictedPreference> preferenceList =
                helper.createAccessibilityActivityPreferenceList(infoList);
        RestrictedPreference preference = preferenceList.get(0);

        assertThat(preference.getKey()).isEqualTo(key);
    }

    @Test
    @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
    public void onCreate_haveRegisterToSpecificUrisAndActions() {
+133 −0

File added.

Preview size limit exceeded, changes collapsed.