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

Commit 8ef01d25 authored by jasonwshsu's avatar jasonwshsu
Browse files

Accessibility shortcut secondary action - save and restore shortcut key

- Implement onCheckboxClicked() to save shortcut key
- restore shortcut key when onViewCreated()
- Use preferredShortcutType to handle settings key

Bug: 142530063
Test: make -j52 RunSettingsRoboTests ROBOTEST_FILTER=AccessibilityUtilTest
Change-Id: Iabe636641968d346e52becea19b6e201ea5bc1fb
parent 8b47bd86
Loading
Loading
Loading
Loading
+124 −6
Original line number Diff line number Diff line
@@ -19,16 +19,20 @@ package com.android.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;

import com.android.settings.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.StringJoiner;

/** Provides utility methods to accessibility settings only. */
final class AccessibilityUtil {
@@ -56,6 +60,12 @@ final class AccessibilityUtil {
        int INTUITIVE = 2;
    }

    // TODO(b/147021230): Will move common functions and variables to
    //  android/internal/accessibility folder
    private static final char COMPONENT_NAME_SEPARATOR = ':';
    private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
            new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);

    /**
     * Annotation for different shortcut type UI type.
     *
@@ -69,14 +79,14 @@ final class AccessibilityUtil {
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            ShortcutType.DEFAULT,
            ShortcutType.SOFTWARE,
            ShortcutType.HARDWARE,
            ShortcutType.TRIPLETAP,
            PreferredShortcutType.DEFAULT,
            PreferredShortcutType.SOFTWARE,
            PreferredShortcutType.HARDWARE,
            PreferredShortcutType.TRIPLETAP,
    })

    /** Denotes the shortcut type. */
    public @interface ShortcutType {
    public @interface PreferredShortcutType {
        int DEFAULT = 0;
        int SOFTWARE = 1; // 1 << 0
        int HARDWARE = 2; // 1 << 1
@@ -129,7 +139,7 @@ final class AccessibilityUtil {
    }

    /**
     * Gets the corresponding fragment type of a given accessibility service
     * Gets the corresponding fragment type of a given accessibility service.
     *
     * @param accessibilityServiceInfo The accessibilityService's info
     * @return int from {@link AccessibilityServiceFragmentType}
@@ -148,4 +158,112 @@ final class AccessibilityUtil {
                ? AccessibilityServiceFragmentType.INVISIBLE
                : AccessibilityServiceFragmentType.INTUITIVE;
    }

    /**
     * Opts in component name into colon-separated {@code shortcutType} key's string in Settings.
     *
     * @param context The current context.
     * @param shortcutType The preferred shortcut type user selected.
     * @param componentName The component name that need to be opted in Settings.
     */
    static void optInValueToSettings(Context context, @PreferredShortcutType int shortcutType,
            @NonNull ComponentName componentName) {
        final String targetKey = convertKeyFromSettings(shortcutType);
        final String targetString = Settings.Secure.getString(context.getContentResolver(),
                targetKey);

        if (TextUtils.isEmpty(targetString)) {
            return;
        }

        if (hasValueInSettings(context, shortcutType, componentName)) {
            return;
        }

        final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));

        joiner.add(targetString);
        joiner.add(componentName.flattenToString());

        Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
    }

    /**
     * Opts out component name into colon-separated {@code shortcutType} key's string in Settings.
     *
     * @param context The current context.
     * @param shortcutType The preferred shortcut type user selected.
     * @param componentName The component name that need to be opted out from Settings.
     */
    static void optOutValueFromSettings(Context context, @PreferredShortcutType int shortcutType,
            @NonNull ComponentName componentName) {
        final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
        final String targetKey = convertKeyFromSettings(shortcutType);
        final String targetString = Settings.Secure.getString(context.getContentResolver(),
                targetKey);

        if (TextUtils.isEmpty(targetString)) {
            return;
        }

        sStringColonSplitter.setString(targetString);
        while (sStringColonSplitter.hasNext()) {
            final String name = sStringColonSplitter.next();
            if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) {
                continue;
            }
            joiner.add(name);
        }

        Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
    }

    /**
     * Returns if component name existed in Settings.
     *
     * @param context The current context.
     * @param shortcutType The preferred shortcut type user selected.
     * @param componentName The component name that need to be checked existed in Settings.
     * @return {@code true} if componentName existed in Settings.
     */
    static boolean hasValueInSettings(Context context, @PreferredShortcutType int shortcutType,
            @NonNull ComponentName componentName) {
        final String targetKey = convertKeyFromSettings(shortcutType);
        final String targetString = Settings.Secure.getString(context.getContentResolver(),
                targetKey);

        if (TextUtils.isEmpty(targetString)) {
            return false;
        }

        sStringColonSplitter.setString(targetString);

        while (sStringColonSplitter.hasNext()) {
            final String name = sStringColonSplitter.next();
            if ((componentName.flattenToString()).equals(name)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Converts {@link PreferredShortcutType} to key in Settings.
     *
     * @param shortcutType The shortcut type.
     * @return Mapping key in Settings.
     */
    static String convertKeyFromSettings(@PreferredShortcutType int shortcutType) {
        switch (shortcutType) {
            case PreferredShortcutType.SOFTWARE:
                return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
            case PreferredShortcutType.HARDWARE:
                return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
            case PreferredShortcutType.TRIPLETAP:
                return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
            default:
                throw new IllegalArgumentException(
                        "Unsupported preferredShortcutType " + shortcutType);
        }
    }
}
+63 −39
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ import androidx.preference.PreferenceScreen;

import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.ShortcutType;
import com.android.settings.accessibility.AccessibilityUtil.PreferredShortcutType;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.widget.SwitchBar;
import com.android.settings.widget.ToggleSwitch;
@@ -64,11 +64,11 @@ public class ToggleAccessibilityServicePreferenceFragment extends
        ToggleFeaturePreferenceFragment implements ShortcutPreference.OnClickListener {

    private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
    private static final String EXTRA_SHORTCUT_TYPE = "shortcutType";
    private static final String EXTRA_PREFERRED_SHORTCUT_TYPE = "preferred_shortcutType";
    // TODO(b/142530063): Check the new setting key to decide which summary should be shown.
    private static final String KEY_SHORTCUT_TYPE = Settings.System.MASTER_MONO;
    private static final String KEY_PREFERRED_SHORTCUT_TYPE = Settings.System.MASTER_MONO;
    private ShortcutPreference mShortcutPreference;
    private int mShortcutType = ShortcutType.DEFAULT;
    private int mPreferredShortcutType = PreferredShortcutType.DEFAULT;
    private CheckBox mSoftwareTypeCheckBox;
    private CheckBox mHardwareTypeCheckBox;

@@ -136,15 +136,16 @@ public class ToggleAccessibilityServicePreferenceFragment extends

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putInt(EXTRA_SHORTCUT_TYPE, mShortcutType);
        outState.putInt(EXTRA_PREFERRED_SHORTCUT_TYPE, mPreferredShortcutType);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onResume() {
        super.onResume();
        mSettingsContentObserver.register(getContentResolver());
        updateSwitchBarToggleSwitch();
        super.onResume();
        updateShortcutPreference();
    }

    @Override
@@ -225,8 +226,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
    }

    private void updateAlertDialogCheckState() {
        updateCheckStatus(mSoftwareTypeCheckBox, ShortcutType.SOFTWARE);
        updateCheckStatus(mHardwareTypeCheckBox, ShortcutType.HARDWARE);
        updateCheckStatus(mSoftwareTypeCheckBox, PreferredShortcutType.SOFTWARE);
        updateCheckStatus(mHardwareTypeCheckBox, PreferredShortcutType.HARDWARE);
    }

    private void updateAlertDialogEnableState() {
@@ -240,48 +241,48 @@ public class ToggleAccessibilityServicePreferenceFragment extends
        }
    }

    private void updateCheckStatus(CheckBox checkBox, @ShortcutType int type) {
        checkBox.setChecked((mShortcutType & type) == type);
    private void updateCheckStatus(CheckBox checkBox, @PreferredShortcutType int type) {
        checkBox.setChecked((mPreferredShortcutType & type) == type);
        checkBox.setOnClickListener(v -> {
            updateShortcutType(false);
            updatePreferredShortcutType(false);
            updateAlertDialogEnableState();
        });
    }

    private void updateShortcutType(boolean saveToDB) {
        mShortcutType = ShortcutType.DEFAULT;
    private void updatePreferredShortcutType(boolean saveToDB) {
        mPreferredShortcutType = PreferredShortcutType.DEFAULT;
        if (mSoftwareTypeCheckBox.isChecked()) {
            mShortcutType |= ShortcutType.SOFTWARE;
            mPreferredShortcutType |= PreferredShortcutType.SOFTWARE;
        }
        if (mHardwareTypeCheckBox.isChecked()) {
            mShortcutType |= ShortcutType.HARDWARE;
            mPreferredShortcutType |= PreferredShortcutType.HARDWARE;
        }
        if (saveToDB) {
            setShortcutType(mShortcutType);
            setPreferredShortcutType(mPreferredShortcutType);
        }
    }

    private void setSecureIntValue(String key, @ShortcutType int value) {
    private void setSecureIntValue(String key, @PreferredShortcutType int value) {
        Settings.Secure.putIntForUser(getPrefContext().getContentResolver(),
                key, value, getPrefContext().getContentResolver().getUserId());
    }

    private void setShortcutType(@ShortcutType int type) {
        setSecureIntValue(KEY_SHORTCUT_TYPE, type);
    private void setPreferredShortcutType(@PreferredShortcutType int type) {
        setSecureIntValue(KEY_PREFERRED_SHORTCUT_TYPE, type);
    }

    private String getShortcutTypeSummary(Context context) {
        final int shortcutType = getShortcutType(context);
        final int shortcutType = getPreferredShortcutType(context);
        final CharSequence softwareTitle =
                context.getText(AccessibilityUtil.isGestureNavigateEnabled(context)
                ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture
                : R.string.accessibility_shortcut_edit_dialog_title_software);

        List<CharSequence> list = new ArrayList<>();
        if ((shortcutType & ShortcutType.SOFTWARE) == ShortcutType.SOFTWARE) {
        if ((shortcutType & PreferredShortcutType.SOFTWARE) == PreferredShortcutType.SOFTWARE) {
            list.add(softwareTitle);
        }
        if ((shortcutType & ShortcutType.HARDWARE) == ShortcutType.HARDWARE) {
        if ((shortcutType & PreferredShortcutType.HARDWARE) == PreferredShortcutType.HARDWARE) {
            final CharSequence hardwareTitle = context.getText(
                    R.string.accessibility_shortcut_edit_dialog_title_hardware);
            list.add(hardwareTitle);
@@ -295,20 +296,22 @@ public class ToggleAccessibilityServicePreferenceFragment extends
        return AccessibilityUtil.capitalize(joinStrings);
    }

    @ShortcutType
    private int getShortcutType(Context context) {
        return getSecureIntValue(context, KEY_SHORTCUT_TYPE, ShortcutType.SOFTWARE);
    @PreferredShortcutType
    private int getPreferredShortcutType(Context context) {
        return getSecureIntValue(context, KEY_PREFERRED_SHORTCUT_TYPE,
                PreferredShortcutType.SOFTWARE);
    }

    @ShortcutType
    private int getSecureIntValue(Context context, String key, @ShortcutType int defaultValue) {
    @PreferredShortcutType
    private int getSecureIntValue(Context context, String key,
            @PreferredShortcutType int defaultValue) {
        return Settings.Secure.getIntForUser(
                context.getContentResolver(),
                key, defaultValue, context.getContentResolver().getUserId());
    }

    private void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
        updateShortcutType(true);
        updatePreferredShortcutType(true);
        mShortcutPreference.setSummary(
                getShortcutTypeSummary(getPrefContext()));
    }
@@ -339,12 +342,13 @@ public class ToggleAccessibilityServicePreferenceFragment extends
    }

    private void initShortcutPreference(Bundle savedInstanceState) {
        // Restore the Shortcut type
        // Restore the PreferredShortcut type
        if (savedInstanceState != null) {
            mShortcutType = savedInstanceState.getInt(EXTRA_SHORTCUT_TYPE, ShortcutType.DEFAULT);
            mPreferredShortcutType = savedInstanceState.getInt(EXTRA_PREFERRED_SHORTCUT_TYPE,
                    PreferredShortcutType.DEFAULT);
        }
        if (mShortcutType == ShortcutType.DEFAULT) {
            mShortcutType = getShortcutType(getPrefContext());
        if (mPreferredShortcutType == PreferredShortcutType.DEFAULT) {
            mPreferredShortcutType = getPreferredShortcutType(getPrefContext());
        }

        // Initial ShortcutPreference widget
@@ -353,17 +357,31 @@ public class ToggleAccessibilityServicePreferenceFragment extends
                preferenceScreen.getContext(), null);
        mShortcutPreference.setPersistent(false);
        mShortcutPreference.setKey(getShortcutPreferenceKey());
        mShortcutPreference.setOrder(-1);
        mShortcutPreference.setTitle(R.string.accessibility_shortcut_title);
        mShortcutPreference.setOnClickListener(this);
        mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
        mShortcutPreference.setOnClickListener(this);
        // Put the shortcutPreference before settingsPreference.
        mShortcutPreference.setOrder(-1);
        preferenceScreen.addPreference(mShortcutPreference);
        // TODO(b/142530063): Check the new key to decide whether checkbox should be checked.
        preferenceScreen.addPreference(mShortcutPreference);
    }

    private void updateShortcutPreference() {
        final PreferenceScreen preferenceScreen = getPreferenceScreen();
        ShortcutPreference shortcutPreference = preferenceScreen.findPreference(
                getShortcutPreferenceKey());

        if (shortcutPreference != null) {
            // TODO(b/142531156): Replace PreferredShortcutType.SOFTWARE value with dialog shortcut
            //  preferred key.
            shortcutPreference.setChecked(
                    AccessibilityUtil.hasValueInSettings(getContext(),
                            PreferredShortcutType.SOFTWARE,
                            mComponentName));
        }
    }

    public String getShortcutPreferenceKey() {
    protected String getShortcutPreferenceKey() {
        return KEY_SHORTCUT_PREFERENCE;
    }

@@ -464,15 +482,21 @@ public class ToggleAccessibilityServicePreferenceFragment extends
    @Override
    public void onCheckboxClicked(ShortcutPreference preference) {
        if (preference.getChecked()) {
            // TODO(b/142530063): Enable shortcut when checkbox is checked.
            // TODO(b/142531156): Replace PreferredShortcutType.SOFTWARE value with dialog shortcut
            //  preferred key.
            AccessibilityUtil.optInValueToSettings(getContext(), PreferredShortcutType.SOFTWARE,
                    mComponentName);
        } else {
            // TODO(b/142530063): Disable shortcut when checkbox is unchecked.
            // TODO(b/142531156): Replace PreferredShortcutType.SOFTWARE value with dialog shortcut
            //  preferred key.
            AccessibilityUtil.optOutValueFromSettings(getContext(), PreferredShortcutType.SOFTWARE,
                    mComponentName);
        }
    }

    @Override
    public void onSettingsClicked(ShortcutPreference preference) {
        mShortcutType = getShortcutType(getPrefContext());
        mPreferredShortcutType = getPreferredShortcutType(getPrefContext());
        showDialog(DialogType.EDIT_SHORTCUT);
    }

+71 −35

File changed.

Preview size limit exceeded, changes collapsed.

+73 −34

File changed.

Preview size limit exceeded, changes collapsed.

+142 −36

File changed.

Preview size limit exceeded, changes collapsed.

Loading