summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author David Padlipsky <dpad@google.com> 2024-10-23 00:45:35 +0000
committer David Padlipsky <dpad@google.com> 2024-11-09 08:33:01 +0000
commitf39fa244528b2f7265e21e51199236ef3e3fc705 (patch)
treea9d0850311c0cd4a48d43df633c2acb1b625faaa
parentdad192db71a0f9093d2b785f6626e7742c5bb860 (diff)
Implement magnification toggle keyboard shortcut
Adds new "KEY_GESTURE" shortcut type to the AccessibilityManagerService which tracks Accessibility Services which are able to be activated directly from a KeyGestureEvent key combo. Bug: 375277034 Test: atest AccessibilityManagerServiceTest Test: Manually on device Flag: com.android.hardware.input.enable_talkback_and_magnifier_key_gestures Change-Id: I4d65eb7834d1b48628fca46802781054901c0287
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java4
-rw-r--r--core/java/com/android/internal/accessibility/common/ShortcutConstants.java10
-rw-r--r--core/java/com/android/internal/accessibility/util/ShortcutUtils.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java104
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java2
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java3
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt12
10 files changed, 172 insertions, 6 deletions
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 506a19cce159..907f0f2c8fbb 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -119,6 +119,7 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71;
public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN = 72;
public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT = 73;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74;
public static final int FLAG_CANCELLED = 1;
@@ -207,6 +208,7 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
+ KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -781,6 +783,8 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN";
case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT";
+ case KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
+ return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION";
default:
return Integer.toHexString(value);
}
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 44dceb9b7edb..4a49bb6720ef 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -63,6 +63,8 @@ public final class ShortcutConstants {
* quickly tapping screen 2 times with two fingers as preferred shortcut.
* {@code QUICK_SETTINGS} for displaying specifying the accessibility services or features which
* choose Quick Settings as preferred shortcut.
+ * {@code KEY_GESTURE} for shortcuts which are directly from key gestures and should be
+ * activated always.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -73,6 +75,7 @@ public final class ShortcutConstants {
UserShortcutType.TWOFINGER_DOUBLETAP,
UserShortcutType.QUICK_SETTINGS,
UserShortcutType.GESTURE,
+ UserShortcutType.KEY_GESTURE,
UserShortcutType.ALL
})
public @interface UserShortcutType {
@@ -84,8 +87,10 @@ public final class ShortcutConstants {
int TWOFINGER_DOUBLETAP = 1 << 3;
int QUICK_SETTINGS = 1 << 4;
int GESTURE = 1 << 5;
+ int KEY_GESTURE = 1 << 6;
// LINT.ThenChange(:shortcut_type_array)
- int ALL = SOFTWARE | HARDWARE | TRIPLETAP | TWOFINGER_DOUBLETAP | QUICK_SETTINGS | GESTURE;
+ int ALL = SOFTWARE | HARDWARE | TRIPLETAP | TWOFINGER_DOUBLETAP | QUICK_SETTINGS | GESTURE
+ | KEY_GESTURE;
}
/**
@@ -99,7 +104,8 @@ public final class ShortcutConstants {
UserShortcutType.TRIPLETAP,
UserShortcutType.TWOFINGER_DOUBLETAP,
UserShortcutType.QUICK_SETTINGS,
- UserShortcutType.GESTURE
+ UserShortcutType.GESTURE,
+ UserShortcutType.KEY_GESTURE
// LINT.ThenChange(:shortcut_type_intdef)
};
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 2e0ff3db6c50..14ca0f8cae69 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -27,6 +27,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.SERVIC
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.KEY_GESTURE;
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;
@@ -187,6 +188,7 @@ public final class ShortcutUtils {
case TWOFINGER_DOUBLETAP ->
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
case QUICK_SETTINGS -> Settings.Secure.ACCESSIBILITY_QS_TARGETS;
+ case KEY_GESTURE -> Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS;
default -> throw new IllegalArgumentException(
"Unsupported user shortcut type: " + type);
};
@@ -209,6 +211,7 @@ public final class ShortcutUtils {
TRIPLETAP;
case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED ->
TWOFINGER_DOUBLETAP;
+ case Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS -> KEY_GESTURE;
default -> throw new IllegalArgumentException(
"Unsupported user shortcut key: " + key);
};
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
index 8bebc62e93f2..1a9af6b55eed 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
@@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAG
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.google.common.truth.Truth.assertThat;
@@ -123,6 +124,14 @@ public class ShortcutUtilsTest {
}
@Test
+ public void getShortcutTargets_keyGestureShortcutNoService_emptyResult() {
+ assertThat(
+ ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext, KEY_GESTURE, mDefaultUserId)
+ ).isEmpty();
+ }
+
+ @Test
public void getShortcutTargets_softwareShortcut1Service_return1Service() {
setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
setupShortcutTargets(TWO_COMPONENTS, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 86d3ee6d1257..22837acde1c5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -42,9 +42,11 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATIN
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.Display.INVALID_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -55,6 +57,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
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.KEY_GESTURE;
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;
@@ -111,6 +114,8 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
import android.media.AudioManagerInternal;
import android.net.Uri;
import android.os.Binder;
@@ -338,6 +343,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private AlertDialog mEnableTouchExplorationDialog;
+ private final InputManager mInputManager;
+
private AccessibilityInputFilter mInputFilter;
private boolean mHasInputFilter;
@@ -503,6 +510,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
+ new InputManager.KeyGestureEventHandler() {
+ @Override
+ public boolean handleKeyGestureEvent(
+ @NonNull KeyGestureEvent event,
+ @Nullable IBinder focusedToken) {
+ return AccessibilityManagerService.this.handleKeyGestureEvent(event);
+ }
+
+ @Override
+ public boolean isKeyGestureSupported(int gestureType) {
+ return switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION -> true;
+ default -> false;
+ };
+ }
+ };
+
@VisibleForTesting
AccessibilityManagerService(
Context context,
@@ -542,6 +567,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUmi = LocalServices.getService(UserManagerInternal.class);
// TODO(b/255426725): not used on tests
mVisibleBgUserIds = null;
+ mInputManager = context.getSystemService(InputManager.class);
init();
}
@@ -583,6 +609,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
mUmi = LocalServices.getService(UserManagerInternal.class);
+ mInputManager = context.getSystemService(InputManager.class);
if (UserManager.isVisibleBackgroundUsersEnabled()) {
mVisibleBgUserIds = new SparseBooleanArray();
@@ -599,6 +626,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
mContext.getContentResolver());
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+ }
disableAccessibilityMenuToMigrateIfNeeded();
}
@@ -640,6 +670,51 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mIsAccessibilityButtonShown;
}
+ @VisibleForTesting
+ boolean handleKeyGestureEvent(KeyGestureEvent event) {
+ final boolean complete =
+ event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ && !event.isCancelled();
+ final int gestureType = event.getKeyGestureType();
+ if (!complete) {
+ return false;
+ }
+
+ String targetName;
+ switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
+ targetName = MAGNIFICATION_CONTROLLER_NAME;
+ break;
+ default:
+ return false;
+ }
+
+ List<String> shortcutTargets = getAccessibilityShortcutTargets(
+ KEY_GESTURE);
+ if (!shortcutTargets.contains(targetName)) {
+ int userId;
+ synchronized (mLock) {
+ userId = mCurrentUserId;
+ }
+ // TODO(b/377752960): Add dialog to confirm enabling the service and to
+ // activate the first time.
+ enableShortcutForTargets(true, UserShortcutType.KEY_GESTURE,
+ List.of(targetName), userId);
+
+ // Do not perform action on first press since it was just registered. Eventually,
+ // this will be a separate dialog that appears that requires the user to confirm
+ // which will resolve this race condition. For now, just require two presses the
+ // first time it is activated.
+ return true;
+ }
+
+ final int displayId = event.getDisplayId() != INVALID_DISPLAY
+ ? event.getDisplayId() : getLastNonProxyTopFocusedDisplayId();
+ performAccessibilityShortcutInternal(displayId, KEY_GESTURE, targetName);
+
+ return true;
+ }
+
@Override
public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
int windowId) {
@@ -1224,14 +1299,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
int displayId = event.getDisplayId();
final int windowId = event.getWindowId();
if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
- && displayId == Display.INVALID_DISPLAY) {
+ && displayId == INVALID_DISPLAY) {
displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
resolvedUserId, windowId);
event.setDisplayId(displayId);
}
synchronized (mLock) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- && displayId != Display.INVALID_DISPLAY
+ && displayId != INVALID_DISPLAY
&& mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
shouldComputeWindows = true;
}
@@ -3257,6 +3332,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
updateAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
updateAccessibilityShortcutTargetsLocked(userState, GESTURE);
updateAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
+ updateAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE);
// Update the capabilities before the mode because we will check the current mode is
// invalid or not..
updateMagnificationCapabilitiesSettingsChangeLocked(userState);
@@ -3387,6 +3463,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, GESTURE);
+ somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE);
somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
@@ -3968,6 +4045,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
shortcutTypes.add(GESTURE);
}
+ shortcutTypes.add(KEY_GESTURE);
final ComponentName serviceName = service.getComponentName();
for (Integer shortcutType: shortcutTypes) {
@@ -4078,13 +4156,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
private void performAccessibilityShortcutInternal(int displayId,
@UserShortcutType int shortcutType, @Nullable String targetName) {
- final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
+ 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 && !doesShortcutTargetsStringContain(shortcutTargets, targetName)) {
+ if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets,
+ targetName)) {
Slog.v(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
targetName = null;
}
@@ -4306,6 +4386,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
+ if (shortcutType == UserShortcutType.KEY_GESTURE
+ && !enableTalkbackAndMagnifierKeyGestures()) {
+ Slog.w(LOG_TAG,
+ "KEY_GESTURE type shortcuts are disabled by feature flag");
+ return;
+ }
+
final String shortcutTypeSettingKey = ShortcutUtils.convertToKey(shortcutType);
if (shortcutType == UserShortcutType.TRIPLETAP
|| shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
@@ -5683,6 +5770,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Uri mAccessibilityGestureTargetsUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+ private final Uri mAccessibilityKeyGestureTargetsUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS);
+
private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS);
@@ -5747,6 +5837,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
contentResolver.registerContentObserver(
mAccessibilityGestureTargetsUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
+ mAccessibilityKeyGestureTargetsUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mUserInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
@@ -5828,6 +5920,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (readAccessibilityShortcutTargetsLocked(userState, GESTURE)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilityKeyGestureTargetsUri.equals(uri)) {
+ if (readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE)) {
+ onUserStateChangedLocked(userState);
+ }
} else if (mUserNonInteractiveUiTimeoutUri.equals(uri)
|| mUserInteractiveUiTimeoutUri.equals(uri)) {
readUserRecommendedUiTimeoutSettingsLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 67b40632dde8..8b3e63d0dc5e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -29,6 +29,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
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.KEY_GESTURE;
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;
@@ -209,6 +210,7 @@ class AccessibilityUserState {
mShortcutTargets.put(SOFTWARE, new ArraySet<>());
mShortcutTargets.put(GESTURE, new ArraySet<>());
mShortcutTargets.put(QUICK_SETTINGS, new ArraySet<>());
+ mShortcutTargets.put(KEY_GESTURE, new ArraySet<>());
}
boolean isHandlingAccessibilityEventsLocked() {
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 2e7f5c083ac3..bc5c2db3881f 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -221,6 +221,9 @@ final class InputGestureManager {
systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION));
}
if (keyboardA11yShortcutControl()) {
if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 2edde9b74d0a..945e07913351 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -33,6 +33,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
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.KEY_GESTURE;
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.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
@@ -80,6 +81,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.input.KeyGestureEvent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -2183,6 +2185,28 @@ public class AccessibilityManagerServiceTest {
verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class);
}
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_toggleMagnifier() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).containsExactly(MAGNIFICATION_CONTROLLER_NAME);
+
+ // The magnifier will only be toggled on the second event received since the first is
+ // used to toggle the feature on.
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ verify(mInputFilter).notifyMagnificationShortcutTriggered(anyInt());
+ }
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
@@ -2298,6 +2322,10 @@ public class AccessibilityManagerServiceTest {
AccessibilityManagerService service) {
super(context, service);
}
+
+ @Override
+ void notifyMagnificationShortcutTriggered(int displayId) {
+ }
}
private static class A11yTestableContext extends TestableContext {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 8c35925debff..cb52eef6adfe 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -32,6 +32,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
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.KEY_GESTURE;
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;
@@ -174,6 +175,7 @@ public class AccessibilityUserStateTest {
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), SOFTWARE);
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), GESTURE);
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), QUICK_SETTINGS);
+ mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), KEY_GESTURE);
mUserState.updateA11yTilesInQsPanelLocked(
Set.of(AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME));
mUserState.setTargetAssignedToAccessibilityButton(componentNameString);
@@ -201,6 +203,7 @@ public class AccessibilityUserStateTest {
assertTrue(mUserState.getShortcutTargetsLocked(SOFTWARE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(GESTURE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(QUICK_SETTINGS).isEmpty());
+ assertTrue(mUserState.getShortcutTargetsLocked(KEY_GESTURE).isEmpty());
assertTrue(mUserState.getA11yQsTilesInQsPanel().isEmpty());
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 61400edba165..40ea9dc9cd89 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -782,6 +782,18 @@ class KeyGestureControllerTests {
KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
+ TestData(
+ "META + ALT + M -> Toggle Magnification",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_M
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}