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

Commit a3a00c17 authored by Riley Jones's avatar Riley Jones
Browse files

Shortcut target migration and automated button mode switching

When switching from gesture navigation to navBar navigation,
All gesture targets are migrated to the software target list.
When switching from navBar to gesture, software targets are unchanged.

A11y button mode is changed in certain conditions
When in gesture navigation mode, button mode is always set to FAB.
When in navBar navigation mode, and there are >0 gesture targets and 0 software targets,
button mode is set to navBar.
There is no condition in which software targets are migrated to the gesture target list.

Test: atest AccessibilityManagerServiceTest, and verify behavior manually
Bug: 352165471
Flag: android.provider.a11y_standalone_gesture_enabled
Change-Id: I6adf818b532d5fb1951dd5588eccb446b3bd4783
parent d6575634
Loading
Loading
Loading
Loading
+64 −12
Original line number Diff line number Diff line
@@ -16,18 +16,31 @@

package com.android.internal.accessibility.util;

import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;

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.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;

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

import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -45,6 +58,7 @@ public final class ShortcutUtils {

    private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
            new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
    private static final String TAG = "AccessibilityShortcutUtils";

    /**
     * Opts in component id into colon-separated {@link UserShortcutType}
@@ -164,17 +178,17 @@ public final class ShortcutUtils {
     */
    public static String convertToKey(@UserShortcutType int type) {
        switch (type) {
            case UserShortcutType.SOFTWARE:
            case SOFTWARE:
                return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
            case UserShortcutType.GESTURE:
            case GESTURE:
                return Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
            case UserShortcutType.HARDWARE:
            case HARDWARE:
                return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
            case UserShortcutType.TRIPLETAP:
            case TRIPLETAP:
                return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
            case UserShortcutType.TWOFINGER_DOUBLETAP:
            case TWOFINGER_DOUBLETAP:
                return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
            case UserShortcutType.QUICK_SETTINGS:
            case QUICK_SETTINGS:
                return Settings.Secure.ACCESSIBILITY_QS_TARGETS;
            default:
                throw new IllegalArgumentException(
@@ -191,14 +205,14 @@ public final class ShortcutUtils {
    @UserShortcutType
    public static int convertToType(String key) {
        return switch (key) {
            case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> UserShortcutType.SOFTWARE;
            case Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS -> UserShortcutType.GESTURE;
            case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> UserShortcutType.QUICK_SETTINGS;
            case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> UserShortcutType.HARDWARE;
            case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> SOFTWARE;
            case Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS -> GESTURE;
            case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> QUICK_SETTINGS;
            case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> HARDWARE;
            case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED ->
                    UserShortcutType.TRIPLETAP;
                    TRIPLETAP;
            case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED ->
                    UserShortcutType.TWOFINGER_DOUBLETAP;
                    TWOFINGER_DOUBLETAP;
            default -> throw new IllegalArgumentException(
                    "Unsupported user shortcut key: " + key);
        };
@@ -296,4 +310,42 @@ public final class ShortcutUtils {
            return Collections.unmodifiableSet(targets);
        }
    }

    /**
     * Retrieves the button mode of the provided context.
     * Returns -1 if the button mode is undefined.
     * Valid button modes:
     * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR},
     * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU},
     * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}
     */
    public static int getButtonMode(Context context, @UserIdInt int userId) {
        return Settings.Secure.getIntForUser(context.getContentResolver(),
                ACCESSIBILITY_BUTTON_MODE, /* default value = */ -1, userId);
    }

    /**
     * Sets the button mode of the provided context.
     * Must be a valid button mode, or it will return false.
     * Returns true if the setting was changed, false otherwise.
     * Valid button modes:
     * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR},
     * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU},
     * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}
     */
    public static boolean setButtonMode(Context context, int mode, @UserIdInt int userId) {
        // Input validation
        if (getButtonMode(context, userId) == mode) {
            return false;
        }
        if ((mode
                & (ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR
                | ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU
                | ACCESSIBILITY_BUTTON_MODE_GESTURE)) != mode) {
            Slog.w(TAG, "Tried to set button mode to unexpected value " + mode);
            return false;
        }
        return Settings.Secure.putIntForUser(
                context.getContentResolver(), ACCESSIBILITY_BUTTON_MODE, mode, userId);
    }
}
+0 −38
Original line number Diff line number Diff line
@@ -16,10 +16,6 @@

package com.android.systemui.navigationbar;

import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;

import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
@@ -32,8 +28,6 @@ import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -59,7 +53,6 @@ import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -192,7 +185,6 @@ public class NavigationBarControllerImpl implements
        }
        final int oldMode = mNavMode;
        mNavMode = mode;
        updateAccessibilityButtonModeIfNeeded();

        mExecutor.execute(() -> {
            // create/destroy nav bar based on nav mode only in unfolded state
@@ -209,34 +201,6 @@ public class NavigationBarControllerImpl implements
        });
    }

    private void updateAccessibilityButtonModeIfNeeded() {
        final int mode = mSecureSettings.getIntForUser(
                Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);

        // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural
        // mode, so we don't need to update it.
        if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
            return;
        }

        // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to
        // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE.
        if (QuickStepContract.isGesturalMode(mNavMode)
                && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) {
            mSecureSettings.putIntForUser(
                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE,
                    UserHandle.USER_CURRENT);
            // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to
            // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR.
        } else if (!QuickStepContract.isGesturalMode(mNavMode)
                && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
            mSecureSettings.putIntForUser(
                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                    ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
        }
    }

    private boolean shouldCreateNavBarAndTaskBar(int displayId) {
        if (mHasNavBar.indexOfKey(displayId) > -1) {
            return mHasNavBar.get(displayId);
@@ -353,8 +317,6 @@ public class NavigationBarControllerImpl implements
    @Override
    public void createNavigationBars(final boolean includeDefaultDisplay,
            RegisterStatusBarResult result) {
        updateAccessibilityButtonModeIfNeeded();

        // Don't need to create nav bar on the default display if we initialize TaskBar.
        final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
                && !initializeTaskbarIfNecessary();
+103 −2
Original line number Diff line number Diff line
@@ -38,7 +38,11 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAG
import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;

import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
@@ -55,6 +59,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityShortcutActivated;
import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted;
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;
@@ -937,6 +942,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                            }
                        }
                        case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                             Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS,
                             Settings.Secure.ACCESSIBILITY_QS_TARGETS,
                             Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE ->
                                restoreShortcutTargets(newValue,
@@ -1995,6 +2001,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            // Accessibility Menu component disabled.
            disableAccessibilityMenuToMigrateIfNeeded();

            // As an initialization step, update the shortcuts for the current user.
            updateShortcutsForCurrentNavigationMode();

            if (announceNewUser) {
                // Schedule announcement of the current user if needed.
                mMainHandler.sendMessageDelayed(
@@ -2216,6 +2225,69 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        mProxyManager.clearCacheLocked();
    }

    @VisibleForTesting
    void updateShortcutsForCurrentNavigationMode() {
        synchronized (mLock) {
            AccessibilityUserState userState = getCurrentUserStateLocked();
            if (!isUserSetupCompleted(mContext)) {
                return;
            }
            final boolean isInGesturalNavigation = Settings.Secure.getIntForUser(
                    mContext.getContentResolver(), Settings.Secure.NAVIGATION_MODE,
                    -1, userState.mUserId) == NAV_BAR_MODE_GESTURAL;

            Set<String> gestureTargets = userState.getShortcutTargetsLocked(GESTURE);
            Set<String> softwareTargets = userState.getShortcutTargetsLocked(SOFTWARE);
            int buttonMode = ShortcutUtils.getButtonMode(mContext, userState.mUserId);

            if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
                if (isInGesturalNavigation) {
                    if (buttonMode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
                        // GESTURE button mode indicates migrating from old version
                        // User was using gesture, so move all targets into gesture
                        gestureTargets.addAll(softwareTargets);
                        softwareTargets.clear();
                    }
                    buttonMode = ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
                } else {
                    // Only change the current button mode if there are gesture targets
                    // (indicating the user came from gesture mode or is migrating)
                    if (!gestureTargets.isEmpty()) {
                        buttonMode = softwareTargets.isEmpty()
                                ? ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR
                                : ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;

                        softwareTargets.addAll(gestureTargets);
                        gestureTargets.clear();
                    }
                }
            } else {
                if (!gestureTargets.isEmpty()) {
                    // Adjust button mode before clearing out gesture targets
                    if (!softwareTargets.isEmpty()) {
                        buttonMode = ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
                    } else if (isInGesturalNavigation) {
                        buttonMode = ACCESSIBILITY_BUTTON_MODE_GESTURE;
                    } else {
                        buttonMode = ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
                    }
                    softwareTargets.addAll(gestureTargets);
                    gestureTargets.clear();
                } else if (buttonMode != ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
                    buttonMode = isInGesturalNavigation
                            ? ACCESSIBILITY_BUTTON_MODE_GESTURE
                            : ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
                }
            }

            updateShortcutTargetSets(userState, Set.of(
                    Pair.create(gestureTargets, GESTURE),
                    Pair.create(softwareTargets, SOFTWARE)
            ));
            ShortcutUtils.setButtonMode(mContext, buttonMode, userState.mUserId);
        }
    }

    private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
            @NonNull MagnificationConfig config) {
        final AccessibilityUserState state = getCurrentUserStateLocked();
@@ -3646,6 +3718,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
    }

    private void updateShortcutTargetSets(AccessibilityUserState userState,
            Set<Pair<Set<String>, Integer>> targetSets) {
        boolean somethingChanged = false;
        for (Pair<Set<String>, Integer> pair : targetSets) {
            Set<String> targets = pair.first;
            int type = pair.second;
            if (userState.updateShortcutTargetsLocked(targets, type)) {
                somethingChanged = true;
                persistColonDelimitedSetToSettingLocked(ShortcutUtils.convertToKey(type),
                        userState.mUserId, targets, str -> str);
            }
        }
        if (somethingChanged) {
            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.
@@ -5505,6 +5594,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        private final Uri mMouseKeysUri = Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED);

        private final Uri mNavigationModeUri = Settings.Secure.getUriFor(
                Settings.Secure.NAVIGATION_MODE);

        private final Uri mUserSetupCompleteUri = Settings.Secure.getUriFor(
                Settings.Secure.USER_SETUP_COMPLETE);

        public AccessibilityContentObserver(Handler handler) {
            super(handler);
        }
@@ -5555,6 +5650,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    mAlwaysOnMagnificationUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
                    mMouseKeysUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
                    mNavigationModeUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
                    mUserSetupCompleteUri, false, this, UserHandle.USER_ALL);
        }

        @Override
@@ -5639,6 +5738,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    if (readMouseKeysEnabledLocked(userState)) {
                        onUserStateChangedLocked(userState);
                    }
                } else if (mNavigationModeUri.equals(uri) || mUserSetupCompleteUri.equals(uri)) {
                    updateShortcutsForCurrentNavigationMode();
                }
            }
        }
+4 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.view.accessibility.IAccessibilityManagerClient;

import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.util.ShortcutUtils;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -579,6 +580,9 @@ class AccessibilityUserState {
                .append(String.valueOf(mAlwaysOnMagnificationEnabled));
        pw.append("}");
        pw.println();
        pw.append("     button mode: ");
        pw.append(String.valueOf(ShortcutUtils.getButtonMode(mContext, mUserId)));
        pw.println();
        dumpShortcutTargets(pw, HARDWARE, "shortcut key");
        dumpShortcutTargets(pw, SOFTWARE, "button");
        pw.append("     button target:{").append(mTargetAssignedToAccessibilityButton);
+215 −49

File changed.

Preview size limit exceeded, changes collapsed.