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

Commit a711ed83 authored by Matthew Fritze's avatar Matthew Fritze
Browse files

Add A11y Slices

Add AccessibilityPreferenceController, which wraps all a11y settings
since they are share common infrastructure for enabling, current value,
and availability.

We add an overlay for OEMs to declare their bundled a11y services.
This is the only list of services that will be possible to enabled via
Settings slices.

Accessibility Slices are built by getting a list of valid services,
and indexing the service names as a key in the Slices DB. When they are
built at runtime, they use the generic A11yPrefController to get the status
and enable/disable the service.

Bug: 67997836
Bug: 67997672
Test: robotests
Change-Id: I66f905bf1c55eecb937945c4675c12bcbc96d698
parent d743f206
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -128,4 +128,7 @@
         doesn't interact well with scroll view -->
    <bool name="config_lock_pattern_minimal_ui">true</bool>

    <!-- List of a11y components on the device allowed to be enabled by Settings Slices -->
    <string-array name="config_settings_slices_accessibility_components" translatable="false"/>

</resources>
+18 −9
Original line number Diff line number Diff line
@@ -342,6 +342,21 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
        return super.onPreferenceTreeClick(preference);
    }

    public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info,
            boolean serviceEnabled) {
        final String serviceState = serviceEnabled
                ? context.getString(R.string.accessibility_summary_state_enabled)
                : context.getString(R.string.accessibility_summary_state_disabled);
        final CharSequence serviceSummary = info.loadSummary(context.getPackageManager());
        final String stateSummaryCombo = context.getString(
                R.string.preference_summary_default_combination,
                serviceState, serviceSummary);

        return (TextUtils.isEmpty(serviceSummary))
                ? serviceState
                : stateSummaryCombo;
    }

    private void handleToggleTextContrastPreferenceClick() {
        Settings.Secure.putInt(getContentResolver(),
                Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
@@ -543,15 +558,9 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
                preference.setSummary(R.string.accessibility_summary_state_stopped);
                description = getString(R.string.accessibility_description_state_stopped);
            } else {
                final String serviceState = serviceEnabled ?
                        getString(R.string.accessibility_summary_state_enabled) :
                        getString(R.string.accessibility_summary_state_disabled);
                final CharSequence serviceSummary = info.loadSummary(getPackageManager());
                final String stateSummaryCombo = getString(
                        R.string.preference_summary_default_combination,
                        serviceState, serviceSummary);
                preference.setSummary((TextUtils.isEmpty(serviceSummary)) ? serviceState
                        : stateSummaryCombo);
                final CharSequence serviceSummary = getServiceSummary(getContext(), info,
                        serviceEnabled);
                preference.setSummary(serviceSummary);
            }

            // Disable all accessibility services that are not permitted.
+108 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;

import com.android.settings.accessibility.AccessibilitySettings;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.accessibility.AccessibilityUtils;

import java.util.List;
import java.util.Set;

/**
 * PreferenceController for accessibility services to be used by Slices.
 * Wraps the common logic which enables accessibility services and checks their availability.
 * <p>
 * Should not be used in a {@link com.android.settings.dashboard.DashboardFragment}.
 */
public class AccessibilitySlicePreferenceController extends TogglePreferenceController {

    private final ComponentName mComponentName;

    private final int ON = 1;
    private final int OFF = 0;

    public AccessibilitySlicePreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        mComponentName = ComponentName.unflattenFromString(getPreferenceKey());

        if (mComponentName == null) {
            throw new IllegalArgumentException(
                    "Illegal Component Name from: " + preferenceKey);
        }
    }

    @Override
    public CharSequence getSummary() {
        final AccessibilityServiceInfo serviceInfo = getAccessibilityServiceInfo();
        return serviceInfo == null
                ? "" : AccessibilitySettings.getServiceSummary(mContext, serviceInfo, isChecked());
    }

    @Override
    public boolean isChecked() {
        final ContentResolver contentResolver = mContext.getContentResolver();
        final boolean accessibilityEnabled = Settings.Secure.getInt(contentResolver,
                Settings.Secure.ACCESSIBILITY_ENABLED, OFF) == ON;

        if (!accessibilityEnabled) {
            return false;
        }

        final Set<ComponentName> componentNames =
                AccessibilityUtils.getEnabledServicesFromSettings(mContext);

        return componentNames.contains(mComponentName);
    }

    @Override
    public boolean setChecked(boolean isChecked) {
        if (getAccessibilityServiceInfo() == null) {
            return false;
        }
        AccessibilityUtils.setAccessibilityServiceState(mContext, mComponentName, isChecked);
        return isChecked == isChecked(); // Verify that it was probably changed.
    }

    @Override
    public int getAvailabilityStatus() {
        // Return unsupported when the service is disabled or not installed.
        return getAccessibilityServiceInfo() == null ? DISABLED_UNSUPPORTED : AVAILABLE;
    }

    private AccessibilityServiceInfo getAccessibilityServiceInfo() {
        final AccessibilityManager accessibilityManager = mContext.getSystemService(
                AccessibilityManager.class);
        final List<AccessibilityServiceInfo> serviceList =
                accessibilityManager.getInstalledAccessibilityServiceList();

        for (AccessibilityServiceInfo serviceInfo : serviceList) {
            if (mComponentName.equals(serviceInfo.getComponentName())) {
                return serviceInfo;
            }
        }

        return null;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -148,7 +148,7 @@ public class SettingsSliceProvider extends SliceProvider {
    void loadSlice(Uri uri) {
        long startBuildTime = System.currentTimeMillis();

        SliceData sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri);
        final SliceData sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri);
        mSliceDataCache.put(uri, sliceData);
        getContext().getContentResolver().notifyChange(uri, null /* content observer */);

+9 −9
Original line number Diff line number Diff line
@@ -113,13 +113,13 @@ public class SliceBuilderUtils {
     * - key
     * <p>
     * Examples of valid paths are:
     * - intent/wifi
     * - intent/bluetooth
     * - action/wifi
     * - action/accessibility/servicename
     * - /intent/wifi
     * - /intent/bluetooth
     * - /action/wifi
     * - /action/accessibility/servicename
     *
     * @param uri of the Slice. Follows pattern outlined in {@link SettingsSliceProvider}.
     * @return Pair whose first element {@code true} if the path is prepended with "action", and
     * @return Pair whose first element {@code true} if the path is prepended with "intent", and
     * second is a key.
     */
    public static Pair<Boolean, String> getPathData(Uri uri) {
@@ -133,10 +133,10 @@ public class SliceBuilderUtils {
            throw new IllegalArgumentException("Uri (" + uri + ") has incomplete path: " + path);
        }

        final boolean isInline = TextUtils.equals(SettingsSlicesContract.PATH_SETTING_ACTION,
        final boolean isIntent = TextUtils.equals(SettingsSlicesContract.PATH_SETTING_INTENT,
                split[1]);

        return new Pair<>(isInline, split[2]);
        return new Pair<>(isIntent, split[2]);
    }

    /**
@@ -215,8 +215,8 @@ public class SliceBuilderUtils {
    static Intent getContentIntent(Context context, SliceData sliceData) {
        final Uri contentUri = new Uri.Builder().appendPath(sliceData.getKey()).build();
        final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
                sliceData.getFragmentClassName(), sliceData.getKey(), sliceData.getScreenTitle(),
                0 /* TODO */);
                sliceData.getFragmentClassName(), sliceData.getKey(),
                sliceData.getScreenTitle().toString(), 0 /* TODO */);
        intent.setClassName(context.getPackageName(), SubSettings.class.getName());
        intent.setData(contentUri);
        return intent;
Loading