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

Commit cd57df19 authored by Chun-Ku Lin's avatar Chun-Ku Lin Committed by Android (Google) Code Review
Browse files

Merge "Turn off the accessibility services if the accessibility services only...

Merge "Turn off the accessibility services if the accessibility services only provide accessibility shortcuts and no other shortcuts are associated with these accessibility services." into main
parents bb7d7b84 a2fe68ab
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