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

Commit a2fe68ab authored by Chun-Ku Lin's avatar Chun-Ku Lin
Browse files

Turn off the accessibility services if the accessibility services only

provide accessibility shortcuts and no other shortcuts are associated
with these accessibility services.

Bug: 298869916

Test: manual turn off volume keys shortcut also turns of shortcut only
accessibility services
Test: atest AccessibilityShortcutControllerTest -- \
--template:map preparers=template/preparers/feature-flags \
--flag-value accessibility/android.view.accessibility.update_always_on_a11y_service=true

Change-Id: Ia386dca3b75b27293c78ef5962ec795dadf22c1d
parent dbb916f1
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -22,3 +22,10 @@ flag {
    description: "Enable force force-dark for smart inversion and dark theme everywhere"
    bug: "282821643"
}

flag {
    namespace: "accessibility"
    name: "update_always_on_a11y_service"
    description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
    bug: "298869916"
}
+21 −3
Original line number Diff line number Diff line
@@ -53,10 +53,13 @@ import android.util.Slog;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.Flags;
import android.widget.Toast;

import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.internal.util.function.pooled.PooledLambda;

import java.lang.annotation.Retention;
@@ -66,6 +69,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * Class to help manage the accessibility shortcut key
@@ -364,9 +368,23 @@ public class AccessibilityShortcutController {
                        })
                .setPositiveButton(R.string.accessibility_shortcut_off,
                        (DialogInterface d, int which) -> {
                            if (Flags.updateAlwaysOnA11yService()) {
                                Set<String> targetServices =
                                        ShortcutUtils.getShortcutTargetsFromSettings(
                                                mContext,
                                                ShortcutConstants.UserShortcutType.HARDWARE,
                                                userId);

                                Settings.Secure.putStringForUser(mContext.getContentResolver(),
                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
                                        userId);
                                ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
                                        mContext, targetServices, userId);
                            } else {
                                Settings.Secure.putStringForUser(mContext.getContentResolver(),
                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
                                        userId);
                            }

                            // If canceled, treat as if the dialog has never been shown
                            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+11 −0
Original line number Diff line number Diff line
@@ -59,6 +59,17 @@ public final class ShortcutConstants {
        int TRIPLETAP = 4; // 1 << 2
    }

    /**
     * A list of possible {@link UserShortcutType}. Should stay in sync with the
     * non-default IntDef types.
     */
    public static final int[] USER_SHORTCUT_TYPES = {
            UserShortcutType.SOFTWARE,
            UserShortcutType.HARDWARE,
            UserShortcutType.TRIPLETAP
    };


    /**
     * Annotation for the different accessibility fragment type.
     *
+20 −6
Original line number Diff line number Diff line
@@ -27,17 +27,25 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
import android.view.accessibility.AccessibilityManager.ShortcutType;
import android.view.accessibility.Flags;

import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Set;

/**
 * Extension for {@link AccessibilityServiceTarget} with
 * {@link AccessibilityFragmentType#INVISIBLE_TOGGLE} type.
 */
class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {

    InvisibleToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
    public InvisibleToggleAccessibilityServiceTarget(
            Context context, @ShortcutType int shortcutType,
            @NonNull AccessibilityServiceInfo serviceInfo) {
        super(context,
                shortcutType,
@@ -49,12 +57,18 @@ class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarg
    public void onCheckedChanged(boolean isChecked) {
        final ComponentName componentName = ComponentName.unflattenFromString(getId());

        if (Flags.updateAlwaysOnA11yService()) {
            super.onCheckedChanged(isChecked);
            ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
                    getContext(), Set.of(componentName.flattenToString()), UserHandle.myUserId());
        } else {
            if (!isComponentIdExistingInOtherShortcut()) {
                setAccessibilityServiceState(getContext(), componentName, isChecked);
            }

            super.onCheckedChanged(isChecked);
        }
    }

    private boolean isComponentIdExistingInOtherShortcut() {
        switch (getShortcutType()) {
+97 −0
Original line number Diff line number Diff line
@@ -15,20 +15,29 @@
 */

package com.android.internal.accessibility.util;

import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;

import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.ShortcutType;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;

/**
@@ -180,4 +189,92 @@ public final class ShortcutUtils {
                        "Unsupported shortcut type:" + type);
        }
    }

    /**
     * Updates an accessibility state if the accessibility service is a Always-On a11y service,
     * a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON
     * <p>
     * Turn on the accessibility service when there is any shortcut associated to it.
     * <p>
     * Turn off the accessibility service when there is no shortcut associated to it.
     *
     * @param componentNames the a11y shortcut target's component names
     */
    public static void updateInvisibleToggleAccessibilityServiceEnableState(
            Context context, Set<String> componentNames, int userId) {
        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
                Context.ACCESSIBILITY_SERVICE);
        if (am == null) return;

        final List<AccessibilityServiceInfo> installedServices =
                am.getInstalledAccessibilityServiceList();

        final Set<String> invisibleToggleServices = new ArraySet<>();
        for (AccessibilityServiceInfo serviceInfo : installedServices) {
            if (AccessibilityUtils.getAccessibilityServiceFragmentType(serviceInfo)
                    == INVISIBLE_TOGGLE) {
                invisibleToggleServices.add(serviceInfo.getComponentName().flattenToString());
            }
        }

        final Set<String> servicesWithShortcuts = new ArraySet<>();
        for (int shortcutType: USER_SHORTCUT_TYPES) {
            // The call to update always-on service might modify the shortcut setting right before
            // calling #updateAccessibilityServiceStateIfNeeded in the same call.
            // To avoid getting the shortcut target from out-dated value, use values from Settings
            // instead.
            servicesWithShortcuts.addAll(
                    getShortcutTargetsFromSettings(context, shortcutType, userId));
        }

        for (String componentName : componentNames) {
            // Only needs to update the Always-On A11yService's state when the shortcut changes.
            if (invisibleToggleServices.contains(componentName)) {

                boolean enableA11yService = servicesWithShortcuts.contains(componentName);
                AccessibilityUtils.setAccessibilityServiceState(
                        context,
                        ComponentName.unflattenFromString(componentName), enableA11yService);
            }
        }
    }

    /**
     * Returns the target component names of a given user shortcut type from Settings.
     *
     * <p>
     * Note: grab shortcut targets from Settings is only needed
     * if you depends on a value being set in the same call.
     * For example, you disable a single shortcut,
     * and you're checking if there is any shortcut remaining.
     *
     * <p>
     * If you just want to know the current state, you can use
     * {@link AccessibilityManager#getAccessibilityShortcutTargets(int)}
     */
    public static Set<String> getShortcutTargetsFromSettings(
            Context context, @UserShortcutType int shortcutType, int userId) {
        final String targetKey = convertToKey(shortcutType);
        if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)) {
            boolean magnificationEnabled = Settings.Secure.getIntForUser(
                    context.getContentResolver(), targetKey, /* def= */ 0, userId) == 1;
            return magnificationEnabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME)
                    : Collections.emptySet();

        } else {
            final String targetString = Settings.Secure.getStringForUser(
                    context.getContentResolver(), targetKey, userId);

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

            Set<String> targets = new ArraySet<>();
            sStringColonSplitter.setString(targetString);
            while (sStringColonSplitter.hasNext()) {
                targets.add(sStringColonSplitter.next());
            }
            return Collections.unmodifiableSet(targets);
        }
    }
}
Loading