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

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

Accessibility shortcut improvement (10/n)

Update AccessibilityManagerService:
 - Enable multiple shortcut targets support for accessibility button.
 - Remove navibar magnification enabled setting key.

Bug: 136293963
Test: atest AccessibilityShortcutTest
Test: atest AccessibilityUserStateTest
Test: atest AccessibilityShortcutControllerTest
Change-Id: I69facd958307a22f3a0193dd769e595cef324f9f
parent 59a40df7
Loading
Loading
Loading
Loading
+30 −2
Original line number Diff line number Diff line
@@ -1195,6 +1195,19 @@ public final class AccessibilityManager {
    @TestApi
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void performAccessibilityShortcut() {
        performAccessibilityShortcut(null);
    }

    /**
     * Perform the accessibility shortcut for the given target which is assigned to the shortcut.
     *
     * @param targetName The flattened {@link ComponentName} string or the class name of a system
     *        class implementing a supported accessibility feature, or {@code null} if there's no
     *        specified target.
     * @hide
     */
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void performAccessibilityShortcut(@Nullable String targetName) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
@@ -1203,7 +1216,7 @@ public final class AccessibilityManager {
            }
        }
        try {
            service.performAccessibilityShortcut();
            service.performAccessibilityShortcut(targetName);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
        }
@@ -1270,7 +1283,22 @@ public final class AccessibilityManager {
     * @param displayId The logical display id.
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
    public void notifyAccessibilityButtonClicked(int displayId) {
        notifyAccessibilityButtonClicked(displayId, null);
    }

    /**
     * Perform the accessibility button for the given target which is assigned to the button.
     *
     * @param displayId displayId The logical display id.
     * @param targetName The flattened {@link ComponentName} string or the class name of a system
     *        class implementing a supported accessibility feature, or {@code null} if there's no
     *        specified target.
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
    public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
@@ -1279,7 +1307,7 @@ public final class AccessibilityManager {
            }
        }
        try {
            service.notifyAccessibilityButtonClicked(displayId);
            service.notifyAccessibilityButtonClicked(displayId, targetName);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
        }
+2 −2
Original line number Diff line number Diff line
@@ -66,12 +66,12 @@ interface IAccessibilityManager {
    // Used by UiAutomation
    IBinder getWindowToken(int windowId, int userId);

    void notifyAccessibilityButtonClicked(int displayId);
    void notifyAccessibilityButtonClicked(int displayId, String targetName);

    void notifyAccessibilityButtonVisibilityChanged(boolean available);

    // Requires Manifest.permission.MANAGE_ACCESSIBILITY
    void performAccessibilityShortcut();
    void performAccessibilityShortcut(String targetName);

    // Requires Manifest.permission.MANAGE_ACCESSIBILITY
    List<String> getAccessibilityShortcutTargets(int shortcutType);
+6 −6
Original line number Diff line number Diff line
@@ -348,7 +348,7 @@ public class AccessibilityShortcutControllerTest {
        verify(mAlertDialog).show();
        verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
                anyInt());
        verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
        verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
        verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
    }

@@ -365,7 +365,7 @@ public class AccessibilityShortcutControllerTest {
        assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
                mLayoutParams.privateFlags
                        & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
        verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
        verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
    }

    @Test
@@ -433,7 +433,7 @@ public class AccessibilityShortcutControllerTest {

        verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
        verify(mToast).show();
        verify(mAccessibilityManagerService).performAccessibilityShortcut();
        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
    }

    @Test
@@ -459,7 +459,7 @@ public class AccessibilityShortcutControllerTest {
        when(mServiceInfo.loadSummary(any())).thenReturn(null);
        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
        getController().performAccessibilityShortcut();
        verify(mAccessibilityManagerService).performAccessibilityShortcut();
        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
    }

    @Test
@@ -471,7 +471,7 @@ public class AccessibilityShortcutControllerTest {
        getController().performAccessibilityShortcut();

        verifyZeroInteractions(mToast);
        verify(mAccessibilityManagerService).performAccessibilityShortcut();
        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
    }

    @Test
@@ -485,7 +485,7 @@ public class AccessibilityShortcutControllerTest {
        getController().performAccessibilityShortcut();

        verifyZeroInteractions(mToast);
        verify(mAccessibilityManagerService).performAccessibilityShortcut();
        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
    }

    @Test
+115 −94
Original line number Diff line number Diff line
@@ -411,6 +411,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    if (reboundAService) {
                        onUserStateChangedLocked(userState);
                    }
                    migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
                }
            }

@@ -811,9 +812,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub

            userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
            userState.setDisplayMagnificationEnabledLocked(false);
            userState.setNavBarMagnificationEnabledLocked(false);
            userState.disableShortcutMagnificationLocked();

            userState.setAutoclickEnabledLocked(false);
            userState.mEnabledServices.clear();
            userState.mEnabledServices.add(service);
@@ -853,17 +852,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
     * navigation area has been clicked.
     *
     * @param displayId The logical display id.
     * @param targetName The flattened {@link ComponentName} string or the class name of a system
     *        class implementing a supported accessibility feature, or {@code null} if there's no
     *        specified target.
     */
    @Override
    public void notifyAccessibilityButtonClicked(int displayId) {
    public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Caller does not hold permission "
                    + android.Manifest.permission.STATUS_BAR_SERVICE);
        }
        synchronized (mLock) {
            notifyAccessibilityButtonClickedLocked(displayId);
        }
        mMainHandler.sendMessage(obtainMessage(
                AccessibilityManagerService::performAccessibilityShortcutInternal, this,
                displayId, ACCESSIBILITY_BUTTON, targetName));
    }

    /**
@@ -1023,6 +1025,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            // the state since the context in which the current user
            // state was used has changed since it was inactive.
            onUserStateChangedLocked(userState);
            migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);

            if (announceNewUser) {
                // Schedule announcement of the current user if needed.
@@ -1132,66 +1135,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        }
    }

    // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal(
    //  ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged.
    private void notifyAccessibilityButtonClickedLocked(int displayId) {
        final AccessibilityUserState state = getCurrentUserStateLocked();

        int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
            final AccessibilityServiceConnection service = state.mBoundServices.get(i);
            if (service.mRequestAccessibilityButton) {
                potentialTargets++;
            }
        }

        if (potentialTargets == 0) {
            return;
        }
        if (potentialTargets == 1) {
            if (state.isNavBarMagnificationEnabledLocked()) {
                mMainHandler.sendMessage(obtainMessage(
                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
                        displayId));
                return;
            } else {
                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                    final AccessibilityServiceConnection service = state.mBoundServices.get(i);
                    if (service.mRequestAccessibilityButton) {
                        service.notifyAccessibilityButtonClickedLocked(displayId);
                        return;
                    }
                }
            }
        } else {
            if (state.getServiceAssignedToAccessibilityButtonLocked() == null
                    && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                mMainHandler.sendMessage(obtainMessage(
                        AccessibilityManagerService::showAccessibilityTargetsSelection, this,
                        displayId, ACCESSIBILITY_BUTTON));
            } else if (state.isNavBarMagnificationEnabledLocked()
                    && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                mMainHandler.sendMessage(obtainMessage(
                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
                        displayId));
                return;
            } else {
                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                    final AccessibilityServiceConnection service = state.mBoundServices.get(i);
                    if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
                            state.getServiceAssignedToAccessibilityButtonLocked()))) {
                        service.notifyAccessibilityButtonClickedLocked(displayId);
                        return;
                    }
                }
            }
            // The user may have turned off the assigned service or feature
            mMainHandler.sendMessage(obtainMessage(
                    AccessibilityManagerService::showAccessibilityTargetsSelection, this,
                    displayId, ACCESSIBILITY_BUTTON));
        }
    }

    private void sendAccessibilityButtonToInputFilter(int displayId) {
        synchronized (mLock) {
            if (mHasInputFilter && mInputFilter != null) {
@@ -1635,8 +1578,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            if (userState.isDisplayMagnificationEnabledLocked()) {
                flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
            }
            if (userState.isNavBarMagnificationEnabledLocked()
                    || userState.isShortcutKeyMagnificationEnabledLocked()) {
            if (userState.isShortcutMagnificationEnabledLocked()) {
                flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
            }
            if (userHasMagnificationServicesLocked(userState)) {
@@ -1886,14 +1828,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
                0, userState.mUserId) == 1;
        final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser(
                mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
                0, userState.mUserId) == 1;
        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
                || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
            userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
            userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
            return true;
        }
        return false;
@@ -2088,8 +2024,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        // displays in one display. It's not a real display and there's no input events for it.
        final ArrayList<Display> displays = getValidDisplayList();
        if (userState.isDisplayMagnificationEnabledLocked()
                || userState.isNavBarMagnificationEnabledLocked()
                || userState.isShortcutKeyMagnificationEnabledLocked()) {
                || userState.isShortcutMagnificationEnabledLocked()) {
            for (int i = 0; i < displays.size(); i++) {
                final Display display = displays.get(i);
                getMagnificationController().register(display.getDisplayId());
@@ -2208,6 +2143,85 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
    }

    /**
     * 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
     *    assigned to a shortcut. If it isn't, assigns it to the accessibility button.
     *    (It happens when an enabled accessibility service package is upgraded.)
     *
     * @param packageName The package name to check, or {@code null} to check all services.
     */
    private void migrateAccessibilityButtonSettingsIfNecessaryLocked(
            AccessibilityUserState userState, @Nullable String packageName) {
        final Set<String> buttonTargets =
                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
        int lastSize = buttonTargets.size();
        buttonTargets.removeIf(name -> {
            if (packageName != null && name != null && !name.contains(packageName)) {
                return false;
            }
            final ComponentName componentName = ComponentName.unflattenFromString(name);
            if (componentName == null) {
                return false;
            }
            final AccessibilityServiceInfo serviceInfo =
                    userState.getInstalledServiceInfoLocked(componentName);
            if (serviceInfo == null) {
                return false;
            }
            if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
                    .targetSdkVersion > Build.VERSION_CODES.Q) {
                return false;
            }
            // A11y services targeting sdk version <= Q should not be in the list.
            return true;
        });
        boolean changed = (lastSize != buttonTargets.size());
        lastSize = buttonTargets.size();

        final Set<String> shortcutKeyTargets =
                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
        userState.mEnabledServices.forEach(componentName -> {
            if (packageName != null && componentName != null
                    && !packageName.equals(componentName.getPackageName())) {
                return;
            }
            final AccessibilityServiceInfo serviceInfo =
                    userState.getInstalledServiceInfoLocked(componentName);
            if (serviceInfo == null) {
                return;
            }
            final boolean requestA11yButton = (serviceInfo.flags
                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
            if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo
                    .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) {
                return;
            }
            final String serviceName = serviceInfo.getComponentName().flattenToString();
            if (TextUtils.isEmpty(serviceName)) {
                return;
            }
            if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) {
                return;
            }
            // For enabled a11y services targeting sdk version > Q and requesting a11y button should
            // be assigned to a shortcut.
            buttonTargets.add(serviceName);
        });
        changed |= (lastSize != buttonTargets.size());
        if (!changed) {
            return;
        }

        // Update setting key with new value.
        persistColonDelimitedSetToSettingLocked(
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
                userState.mUserId, buttonTargets, str -> str);
        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
    }

    private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
        int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
        int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
@@ -2269,9 +2283,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
     * AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires
     * permission to write secure settings, since someone with that permission can enable
     * accessibility services themselves.
     *
     * @param targetName The flattened {@link ComponentName} string or the class name of a system
     *        class implementing a supported accessibility feature, or {@code null} if there's no
     *        specified target.
     */
    @Override
    public void performAccessibilityShortcut() {
    public void performAccessibilityShortcut(String targetName) {
        if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
                && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
                != PackageManager.PERMISSION_GRANTED)) {
@@ -2280,7 +2298,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        }
        mMainHandler.sendMessage(obtainMessage(
                AccessibilityManagerService::performAccessibilityShortcutInternal, this,
                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
    }

    /**
@@ -2288,20 +2306,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
     *
     * @param shortcutType The shortcut type.
     * @param displayId The display id of the accessibility button.
     * @param targetName The flattened {@link ComponentName} string or the class name of a system
     *        class implementing a supported accessibility feature, or {@code null} if there's no
     *        specified target.
     */
    private void performAccessibilityShortcutInternal(int displayId,
            @ShortcutType int shortcutType) {
            @ShortcutType int shortcutType, @Nullable String targetName) {
        final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
        if (shortcutTargets.isEmpty()) {
            Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
            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;
            }
        } else {
            // In case there are many targets assigned to the given shortcut.
            if (shortcutTargets.size() > 1) {
                showAccessibilityTargetsSelection(displayId, shortcutType);
                return;
            }
        final String targetName = shortcutTargets.get(0);
            targetName = shortcutTargets.get(0);
        }
        // In case user assigned magnification to the given shortcut.
        if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
            sendAccessibilityButtonToInputFilter(displayId);
@@ -2844,11 +2873,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);

        // TODO(a11y shortcut): Remove this setting key, and have a migrate function in
        //  Setting provider after new shortcut UI merged.
        private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);

        private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);

@@ -2888,8 +2912,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
                    false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri,
                    false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(mAutoclickEnabledUri,
                    false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -2924,8 +2946,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    if (readTouchExplorationEnabledSettingLocked(userState)) {
                        onUserStateChangedLocked(userState);
                    }
                } else if (mDisplayMagnificationEnabledUri.equals(uri)
                        || mNavBarMagnificationEnabledUri.equals(uri)) {
                } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
                    if (readMagnificationEnabledSettingsLocked(userState)) {
                        onUserStateChangedLocked(userState);
                    }
+1 −33
Original line number Diff line number Diff line
@@ -313,48 +313,16 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
        }
    }

    // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged.
    public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
        // If the service does not request the accessibility button, it isn't available
        if (!mRequestAccessibilityButton) {
            return false;
        }

        // If the accessibility button isn't currently shown, it cannot be available to services
        if (!mSystemSupport.isAccessibilityButtonShown()) {
            return false;
        }

        // If magnification is on and assigned to the accessibility button, services cannot be
        if (userState.isNavBarMagnificationEnabledLocked()
                && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
            return false;
        }

        int requestingServices = 0;
        for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
            final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
            if (service.mRequestAccessibilityButton) {
                requestingServices++;
            }
        }

        if (requestingServices == 1) {
            // If only a single service is requesting, it must be this service, and the
            // accessibility button is available to it
            return true;
        } else {
            // With more than one active service, we derive the target from the user's settings
            if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
                // If the user has not made an assignment, we treat the button as available to
                // all services until the user interacts with the button to make an assignment
        return true;
            } else {
                // If an assignment was made, it defines availability
                return mComponentName.equals(
                        userState.getServiceAssignedToAccessibilityButtonLocked());
            }
        }
    }

    @Override
Loading