diff options
7 files changed, 267 insertions, 1 deletions
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 9d42b67bf5a8..506a19cce159 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -117,6 +117,8 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69; public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70; 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 FLAG_CANCELLED = 1; @@ -203,6 +205,8 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE, + KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN, + KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT, }) @Retention(RetentionPolicy.SOURCE) public @interface KeyGestureType { @@ -773,6 +777,10 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE: return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE"; + case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN: + return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN"; + case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT: + return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT"; default: return Integer.toHexString(value); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 617cca9d3075..d6fc6e461edc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -27,6 +27,8 @@ import android.annotation.NonNull; import android.content.Context; import android.graphics.Region; import android.hardware.input.InputManager; +import android.hardware.input.KeyGestureEvent; +import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; @@ -44,11 +46,14 @@ import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.Nullable; + import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper; +import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.MouseEventHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; @@ -187,6 +192,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private final AccessibilityManagerService mAms; + private final InputManager mInputManager; + private final SparseArray<EventStreamTransformation> mEventHandler; private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0); @@ -228,6 +235,47 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ private MotionEvent mLastActiveDeviceMotionEvent = null; + private boolean mKeyGestureEventHandlerInstalled = false; + private InputManager.KeyGestureEventHandler mKeyGestureEventHandler = + new InputManager.KeyGestureEventHandler() { + @Override + public boolean handleKeyGestureEvent( + @NonNull KeyGestureEvent event, + @Nullable IBinder focusedToken) { + final boolean complete = + event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE + && !event.isCancelled(); + final int gestureType = event.getKeyGestureType(); + final int displayId = isDisplayIdValid(event.getDisplayId()) + ? event.getDisplayId() : Display.DEFAULT_DISPLAY; + + switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN: + if (complete) { + mAms.getMagnificationController().scaleMagnificationByStep( + displayId, MagnificationController.ZOOM_DIRECTION_IN); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT: + if (complete) { + mAms.getMagnificationController().scaleMagnificationByStep( + displayId, MagnificationController.ZOOM_DIRECTION_OUT); + } + return true; + } + return false; + } + + @Override + public boolean isKeyGestureSupported(int gestureType) { + return switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true; + default -> false; + }; + } + }; + private static MotionEvent cancelMotion(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT @@ -287,6 +335,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mContext = context; mAms = service; mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mInputManager = context.getSystemService(InputManager.class); mEventHandler = eventHandler; } @@ -723,6 +772,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo createMagnificationGestureHandler(displayId, displayContext); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); + + if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures() + && !mKeyGestureEventHandlerInstalled) { + mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler); + mKeyGestureEventHandlerInstalled = true; + } } if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { @@ -842,6 +897,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mMouseKeysInterceptor.onDestroy(); mMouseKeysInterceptor = null; } + + if (mKeyGestureEventHandlerInstalled) { + mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler); + mKeyGestureEventHandlerInstalled = false; + } } private MagnificationGestureHandler createMagnificationGestureHandler( diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index d40e7476f7ec..51c4305061f8 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; import android.accessibilityservice.MagnificationConfig; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -101,6 +102,7 @@ public class MagnificationController implements MagnificationConnectionManager.C private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; /** Whether the platform supports window magnification feature. */ private final boolean mSupportWindowMagnification; + private final MagnificationScaleStepProvider mScaleStepProvider; private final Executor mBackgroundExecutor; @@ -131,6 +133,14 @@ public class MagnificationController implements MagnificationConnectionManager.C .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray = new SparseArray<>(); + // Direction magnifier scale can be altered. + public static final int ZOOM_DIRECTION_IN = 0; + public static final int ZOOM_DIRECTION_OUT = 1; + + @IntDef({ZOOM_DIRECTION_IN, ZOOM_DIRECTION_OUT}) + public @interface ZoomDirection { + } + /** * A callback to inform the magnification transition result on the given display. */ @@ -144,6 +154,41 @@ public class MagnificationController implements MagnificationConnectionManager.C void onResult(int displayId, boolean success); } + + /** + * An interface to configure how much the magnification scale should be affected when moving in + * steps. + */ + public interface MagnificationScaleStepProvider { + /** + * Calculate the next value given which direction (in/out) to adjust the magnification + * scale. + * + * @param currentScale The current magnification scale value. + * @param direction Whether to zoom in or out. + * @return The next scale value. + */ + float nextScaleStep(float currentScale, @ZoomDirection int direction); + } + + public static class DefaultMagnificationScaleStepProvider implements + MagnificationScaleStepProvider { + // Factor of magnification scale. For example, when this value is 1.189, scale + // value will be changed x1.000, x1.189, x1.414, x1.681, x2.000, ... + // Note: this value is 2.0 ^ (1 / 4). + public static final float ZOOM_STEP_SCALE_FACTOR = 1.18920712f; + + @Override + public float nextScaleStep(float currentScale, @ZoomDirection int direction) { + final int stepDelta = direction == ZOOM_DIRECTION_IN ? 1 : -1; + final long scaleIndex = Math.round( + Math.log(currentScale) / Math.log(ZOOM_STEP_SCALE_FACTOR)); + final float nextScale = (float) Math.pow(ZOOM_STEP_SCALE_FACTOR, + scaleIndex + stepDelta); + return MagnificationScaleProvider.constrainScale(nextScale); + } + } + public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) { @@ -156,6 +201,7 @@ public class MagnificationController implements MagnificationConnectionManager.C .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this); mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( FEATURE_WINDOW_MAGNIFICATION); + mScaleStepProvider = new DefaultMagnificationScaleStepProvider(); mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context); mAlwaysOnMagnificationFeatureFlag.addOnChangedListener( @@ -891,6 +937,37 @@ public class MagnificationController implements MagnificationConnectionManager.C return isActivated; } + /** + * Scales the magnifier on the given display one step in/out based on the zoomIn param. + * + * @param displayId The logical display id. + * @param direction Whether the scale should be zoomed in or out. + * @return {@code true} if the magnification scale was affected. + */ + public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { + if (getFullScreenMagnificationController().isActivated(displayId)) { + final float magnificationScale = getFullScreenMagnificationController().getScale( + displayId); + final float nextMagnificationScale = mScaleStepProvider.nextScaleStep( + magnificationScale, direction); + getFullScreenMagnificationController().setScaleAndCenter(displayId, + nextMagnificationScale, + Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID); + return nextMagnificationScale != magnificationScale; + } + + if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) { + final float magnificationScale = getMagnificationConnectionManager().getScale( + displayId); + final float nextMagnificationScale = mScaleStepProvider.nextScaleStep( + magnificationScale, direction); + getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale); + return nextMagnificationScale != magnificationScale; + } + + return false; + } + private final class DisableMagnificationCallback implements MagnificationAnimationCallback { private final TransitionCallBack mTransitionCallBack; diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 8cb51ce35a89..b1aa694f3aa3 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -215,6 +215,12 @@ final class InputGestureManager { systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK)); + systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT)); + systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN)); } if (keyboardA11yShortcutControl()) { if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) { diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index c645c0852f1b..9b7bbe04132c 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -114,6 +114,7 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" /> + <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" /> <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 8164ef9314d9..f0d3456a39de 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -19,9 +19,13 @@ package com.android.server.accessibility.magnification; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; +import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE; +import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE; import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; import static com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -660,6 +664,90 @@ public class MagnificationControllerTest { } @Test + public void scaleMagnificationByStep_fullscreenMode_stepInAndOut() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 1.0f, false); + reset(mScreenMagnificationController); + + // Verify the zoom scale factor increases by + // {@code MagnificationController.DefaultMagnificationScaleStepProvider + // .ZOOM_STEP_SCALE_FACTOR} and the center coordinates are + // unchanged (Float.NaN as values denotes unchanged center). + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_IN); + verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), + eq(MagnificationController + .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR), + eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt()); + + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_OUT); + verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), + eq(SCALE_MIN_VALUE), eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt()); + } + + @Test + public void scaleMagnificationByStep_testMaxScaling() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false); + reset(mScreenMagnificationController); + + float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY); + while (currentScale < SCALE_MAX_VALUE) { + assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_IN)).isTrue(); + final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY); + assertThat(nextScale).isGreaterThan(currentScale); + currentScale = nextScale; + } + + assertThat(currentScale).isEqualTo(SCALE_MAX_VALUE); + assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_IN)).isFalse(); + } + + @Test + public void scaleMagnificationByStep_testMinScaling() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MAX_VALUE, false); + reset(mScreenMagnificationController); + + float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY); + while (currentScale > SCALE_MIN_VALUE) { + assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_OUT)).isTrue(); + final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY); + assertThat(nextScale).isLessThan(currentScale); + currentScale = nextScale; + } + + assertThat(currentScale).isEqualTo(SCALE_MIN_VALUE); + assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_OUT)).isFalse(); + } + + @Test + public void scaleMagnificationByStep_windowedMode_stepInAndOut() throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false); + reset(mMagnificationConnectionManager); + + // Verify the zoom scale factor increases by + // {@code MagnificationController.DefaultMagnificationScaleStepProvider + // .ZOOM_STEP_SCALE_FACTOR}. + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_IN); + verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY), + eq(MagnificationController + .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR)); + + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_OUT); + verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY), + eq(SCALE_MIN_VALUE)); + } + + @Test public void enableWindowMode_notifyMagnificationChanged() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 1574d1b7ce6f..265767088df9 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -757,7 +757,31 @@ class KeyGestureControllerTests { intArrayOf(KeyEvent.KEYCODE_MINUS), KeyEvent.META_ALT_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) + ), + TestData( + "META + ALT + '-' -> Magnifier Zoom Out", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_MINUS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT, + intArrayOf(KeyEvent.KEYCODE_MINUS), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + '=' -> Magnifier Zoom In", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_EQUALS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN, + intArrayOf(KeyEvent.KEYCODE_EQUALS), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), ) } @@ -770,6 +794,7 @@ class KeyGestureControllerTests { com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG, com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS, + com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES, com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS ) @@ -787,6 +812,7 @@ class KeyGestureControllerTests { com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG, com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS, + com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES, com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS ) |