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

Commit d15058df authored by Rhed Jao's avatar Rhed Jao
Browse files

Rollback the changes of a11y button

Single tap the a11y button should launch the shortcut target
or chooser activity if there are multiple targets assigned to
the a11y button and user does not specify one.

Rollback the channges of ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
and support a new setting key ACCESSIBILITY_BUTTON_TARGETS for
a11y button to maintain a list of targets which is assigned by
user in settings app.

Also, fix some potential issues, such as, disable a11y shortcut
(this is already not supported) and have more checks to verify
a11y button setting value when an a11y service package updated.

Bug: 152264133
Test: atest AccessibilityUserStateTest
Test: atest AccessibilityShortcutTest
Test: atest AccessibilityButtonSdk29Test
Change-Id: Ief7f44cb85eb75b45dc6ffb6e9a967b792959858
parent 1d0ddffe
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1082,6 +1082,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
            pw.append(", eventTypes="
                    + AccessibilityEvent.eventTypeToString(mEventTypes));
            pw.append(", notificationTimeout=" + mNotificationTimeout);
            pw.append(", requestA11yBtn=" + mRequestAccessibilityButton);
            pw.append("]");
        }
    }
+72 −23
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;

import android.Manifest;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -883,6 +884,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            throw new SecurityException("Caller does not hold permission "
                    + android.Manifest.permission.STATUS_BAR_SERVICE);
        }
        if (targetName == null) {
            synchronized (mLock) {
                final AccessibilityUserState userState = getCurrentUserStateLocked();
                targetName = userState.getTargetAssignedToAccessibilityButton();
            }
        }
        mMainHandler.sendMessage(obtainMessage(
                AccessibilityManagerService::performAccessibilityShortcutInternal, this,
                displayId, ACCESSIBILITY_BUTTON, targetName));
@@ -1835,7 +1842,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
        somethingChanged |= readAutoclickEnabledSettingLocked(userState);
        somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState);
        somethingChanged |= readAccessibilityButtonSettingsLocked(userState);
        somethingChanged |= readAccessibilityButtonTargetsLocked(userState);
        somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
        somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
        return somethingChanged;
    }
@@ -1955,9 +1963,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        return true;
    }

    private boolean readAccessibilityButtonSettingsLocked(AccessibilityUserState userState) {
    private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
        final Set<String> targetsFromSetting = new ArraySet<>();
        readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
        readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                userState.mUserId, targetsFromSetting, str -> str);

        final Set<String> currentTargets =
@@ -1971,6 +1979,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        return true;
    }

    private boolean readAccessibilityButtonTargetComponentLocked(AccessibilityUserState userState) {
        final String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
        if (TextUtils.isEmpty(componentId)) {
            if (userState.getTargetAssignedToAccessibilityButton() == null) {
                return false;
            }
            userState.setTargetAssignedToAccessibilityButton(null);
            return true;
        }
        if (componentId.equals(userState.getTargetAssignedToAccessibilityButton())) {
            return false;
        }
        userState.setTargetAssignedToAccessibilityButton(componentId);
        return true;
    }

    private boolean readUserRecommendedUiTimeoutSettingsLocked(AccessibilityUserState userState) {
        final int nonInteractiveUiTimeout = Settings.Secure.getIntForUser(
                mContext.getContentResolver(),
@@ -1991,7 +2016,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    }

    /**
     * Check if the targets that will be enabled by the accessibility shortcut key is installed.
     * Check if the target that will be enabled by the accessibility shortcut key is installed.
     * If it isn't, remove it from the list and associated setting so a side loaded service can't
     * spoof the package name of the default service.
     */
@@ -2152,7 +2177,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub

    /**
     * 1) Update accessibility button availability to accessibility services.
     * 2) Check if the targets that will be enabled by the accessibility button is installed.
     * 2) Check if the target that will be enabled by the accessibility button is installed.
     *    If it isn't, remove it from the list and associated setting so a side loaded service can't
     *    spoof the package name of the default service.
     */
@@ -2179,8 +2204,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        }

        // Update setting key with new value.
        persistColonDelimitedSetToSettingLocked(
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
        persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                userState.mUserId, currentTargets, str -> str);
        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
    }
@@ -2189,7 +2213,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
     * 1) Check if the service assigned to accessibility button target sdk version > Q.
     *    If it isn't, remove it from the list and associated setting.
     *    (It happens when an accessibility service package is downgraded.)
     * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is
     * 2) For a service targeting sdk version > Q and requesting a11y button, it should be in the
     *    enabled list if's assigned to a11y button.
     *    (It happens when an accessibility service package is same graded, and updated requesting
     *     a11y button flag)
     * 3) Check if an enabled service targeting sdk version > Q and requesting a11y button is
     *    assigned to a shortcut. If it isn't, assigns it to the accessibility button.
     *    (It happens when an enabled accessibility service package is upgraded.)
     *
@@ -2214,11 +2242,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                return false;
            }
            if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
                    .targetSdkVersion > Build.VERSION_CODES.Q) {
                return false;
            }
                    .targetSdkVersion <= Build.VERSION_CODES.Q) {
                // A11y services targeting sdk version <= Q should not be in the list.
                Slog.v(LOG_TAG, "Legacy service " + componentName
                        + " should not in the button");
                return true;
            }
            final boolean requestA11yButton = (serviceInfo.flags
                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
            if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) {
                // An a11y service targeting sdk version > Q and request A11y button and is assigned
                // to a11y btn should be in the enabled list.
                Slog.v(LOG_TAG, "Service requesting a11y button and be assigned to the button"
                        + componentName + " should be enabled state");
                return true;
            }
            return false;
        });
        boolean changed = (lastSize != buttonTargets.size());
        lastSize = buttonTargets.size();
@@ -2241,15 +2280,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) {
                return;
            }
            final String serviceName = serviceInfo.getComponentName().flattenToString();
            final String serviceName = componentName.flattenToString();
            if (TextUtils.isEmpty(serviceName)) {
                return;
            }
            if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) {
            if (doesShortcutTargetsStringContain(buttonTargets, serviceName)
                    || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)) {
                return;
            }
            // For enabled a11y services targeting sdk version > Q and requesting a11y button should
            // be assigned to a shortcut.
            Slog.v(LOG_TAG, "A enabled service requesting a11y button " + componentName
                    + " should be assign to the button or shortcut.");
            buttonTargets.add(serviceName);
        });
        changed |= (lastSize != buttonTargets.size());
@@ -2258,8 +2300,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        }

        // Update setting key with new value.
        persistColonDelimitedSetToSettingLocked(
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
        persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                userState.mUserId, buttonTargets, str -> str);
        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
    }
@@ -2360,12 +2401,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            return;
        }
        // In case the caller specified a target name
        if (targetName != null) {
            if (!shortcutTargets.contains(targetName)) {
                Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
                return;
        if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets, targetName)) {
            Slog.v(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
            targetName = null;
        }
        } else {
        if (targetName == null) {
            // In case there are many targets assigned to the given shortcut.
            if (shortcutTargets.size() > 1) {
                showAccessibilityTargetsSelection(displayId, shortcutType);
@@ -2979,6 +3019,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        private final Uri mAccessibilityButtonComponentIdUri = Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);

        private final Uri mAccessibilityButtonTargetsUri = Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);

        private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS);

@@ -3011,6 +3054,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
                    mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
                    mAccessibilityButtonTargetsUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
                    mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
@@ -3057,7 +3102,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                        onUserStateChangedLocked(userState);
                    }
                } else if (mAccessibilityButtonComponentIdUri.equals(uri)) {
                    if (readAccessibilityButtonSettingsLocked(userState)) {
                    if (readAccessibilityButtonTargetComponentLocked(userState)) {
                        onUserStateChangedLocked(userState);
                    }
                } else if (mAccessibilityButtonTargetsUri.equals(uri)) {
                    if (readAccessibilityButtonTargetsLocked(userState)) {
                        onUserStateChangedLocked(userState);
                    }
                } else if (mUserNonInteractiveUiTimeoutUri.equals(uri)
+58 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import com.android.internal.accessibility.AccessibilityShortcutController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -96,6 +97,8 @@ class AccessibilityUserState {

    private ComponentName mServiceChangingSoftKeyboardMode;

    private String mTargetAssignedToAccessibilityButton;

    private boolean mBindInstantServiceAllowed;
    private boolean mIsAutoclickEnabled;
    private boolean mIsDisplayMagnificationEnabled;
@@ -152,6 +155,7 @@ class AccessibilityUserState {
        mTouchExplorationGrantedServices.clear();
        mAccessibilityShortcutKeyTargets.clear();
        mAccessibilityButtonTargets.clear();
        mTargetAssignedToAccessibilityButton = null;
        mIsTouchExplorationEnabled = false;
        mServiceHandlesDoubleTap = false;
        mRequestMultiFingerGestures = false;
@@ -469,6 +473,8 @@ class AccessibilityUserState {
            }
        }
        pw.println("}");
        pw.append("     button target:{").append(mTargetAssignedToAccessibilityButton);
        pw.println("}");
        pw.append("     Bound services:{");
        final int serviceCount = mBoundServices.size();
        for (int j = 0; j < serviceCount; j++) {
@@ -716,4 +722,56 @@ class AccessibilityUserState {
    public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
        mUserNonInteractiveUiTimeout = timeout;
    }

    /**
     * Gets a shortcut target which is assigned to the accessibility button by the chooser
     * activity.
     *
     * @return The flattened component name or the system class name of the shortcut target.
     */
    public String getTargetAssignedToAccessibilityButton() {
        return mTargetAssignedToAccessibilityButton;
    }

    /**
     * Sets a shortcut target which is assigned to the accessibility button by the chooser
     * activity.
     *
     * @param target The flattened component name or the system class name of the shortcut target.
     */
    public void setTargetAssignedToAccessibilityButton(String target) {
        mTargetAssignedToAccessibilityButton = target;
    }

    /**
     * Whether or not the given target name is contained in the shortcut collection. Since the
     * component name string format could be short or long, this function un-flatten the component
     * name from the string in {@code shortcutTargets} and compared with the given target name.
     *
     * @param shortcutTargets The shortcut type.
     * @param targetName The target name.
     * @return {@code true} if the target is in the shortcut collection.
     */
    public static boolean doesShortcutTargetsStringContain(Collection<String> shortcutTargets,
            String targetName) {
        if (shortcutTargets == null || targetName == null) {
            return false;
        }
        // Some system features, such as magnification, don't have component name. Using string
        // compare first.
        if (shortcutTargets.contains(targetName)) {
            return true;
        }
        final ComponentName targetComponentName = ComponentName.unflattenFromString(targetName);
        if (targetComponentName == null) {
            return false;
        }
        for (String stringName : shortcutTargets) {
            if (!TextUtils.isEmpty(stringName)
                    && targetComponentName.equals(ComponentName.unflattenFromString(stringName))) {
                return true;
            }
        }
        return false;
    }
}
+36 −0
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSI
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;

import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -41,6 +43,7 @@ import android.content.Context;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.testing.DexmakerShareClassLoaderRule;
import android.util.ArraySet;

import com.android.internal.util.test.FakeSettingsProvider;

@@ -56,6 +59,12 @@ public class AccessibilityUserStateTest {

    private static final ComponentName COMPONENT_NAME =
            new ComponentName("com.android.server.accessibility", "AccessibilityUserStateTest");
    private static final ComponentName COMPONENT_NAME1 =
            new ComponentName("com.android.server.accessibility",
                    "com.android.server.accessibility.AccessibilityUserStateTest1");
    private static final ComponentName COMPONENT_NAME2 =
            new ComponentName("com.android.server.accessibility",
                    "com.android.server.accessibility.AccessibilityUserStateTest2");

    // Values of setting key SHOW_IME_WITH_HARD_KEYBOARD
    private static final int STATE_HIDE_IME = 0;
@@ -111,6 +120,7 @@ public class AccessibilityUserStateTest {
        mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
        mUserState.mAccessibilityShortcutKeyTargets.add(COMPONENT_NAME.flattenToString());
        mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
        mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
        mUserState.setTouchExplorationEnabledLocked(true);
        mUserState.setDisplayMagnificationEnabledLocked(true);
        mUserState.setAutoclickEnabledLocked(true);
@@ -129,6 +139,7 @@ public class AccessibilityUserStateTest {
        assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
        assertTrue(mUserState.mAccessibilityShortcutKeyTargets.isEmpty());
        assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
        assertNull(mUserState.getTargetAssignedToAccessibilityButton());
        assertFalse(mUserState.isTouchExplorationEnabledLocked());
        assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
        assertFalse(mUserState.isAutoclickEnabledLocked());
@@ -285,6 +296,31 @@ public class AccessibilityUserStateTest {
        verify(mMockConnection).notifySoftKeyboardShowModeChangedLocked(eq(SHOW_MODE_HIDDEN));
    }

    @Test
    public void doesShortcutTargetsStringContain_returnFalse() {
        assertFalse(doesShortcutTargetsStringContain(null, null));
        assertFalse(doesShortcutTargetsStringContain(null,
                COMPONENT_NAME.flattenToShortString()));
        assertFalse(doesShortcutTargetsStringContain(new ArraySet<>(), null));

        final ArraySet<String> shortcutTargets = new ArraySet<>();
        shortcutTargets.add(COMPONENT_NAME.flattenToString());
        assertFalse(doesShortcutTargetsStringContain(shortcutTargets,
                COMPONENT_NAME1.flattenToString()));
    }

    @Test
    public void isAssignedToShortcutLocked_withDifferentTypeComponentString_returnTrue() {
        final ArraySet<String> shortcutTargets = new ArraySet<>();
        shortcutTargets.add(COMPONENT_NAME1.flattenToShortString());
        shortcutTargets.add(COMPONENT_NAME2.flattenToString());

        assertTrue(doesShortcutTargetsStringContain(shortcutTargets,
                COMPONENT_NAME1.flattenToString()));
        assertTrue(doesShortcutTargetsStringContain(shortcutTargets,
                COMPONENT_NAME2.flattenToShortString()));
    }

    @Test
    public void isShortcutTargetInstalledLocked_returnTrue() {
        mUserState.mInstalledServices.add(mMockServiceInfo);