diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 52b7cffbc3400..d67439cc9de28 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3768,6 +3768,41 @@ public final class MotionEvent extends InputEvent implements Parcelable { return (getButtonState() & button) == button; } + /** + * Gets a rotation matrix that (when applied to a motionevent) will rotate that motion event + * such that the result coordinates end up in the same physical location on a display whose + * coordinates are rotated by `rotation`. + * + * For example, rotating 0,0 by 90 degrees will move a point from the physical top-left to + * the bottom-left of the 90-degree-rotated display. + * + * @hide + */ + public static Matrix createRotateMatrix( + @Surface.Rotation int rotation, int displayW, int displayH) { + if (rotation == Surface.ROTATION_0) { + return new Matrix(Matrix.IDENTITY_MATRIX); + } + // values is row-major + float[] values = null; + if (rotation == Surface.ROTATION_90) { + values = new float[]{0, 1, 0, + -1, 0, displayH, + 0, 0, 1}; + } else if (rotation == Surface.ROTATION_180) { + values = new float[]{-1, 0, displayW, + 0, -1, displayH, + 0, 0, 1}; + } else if (rotation == Surface.ROTATION_270) { + values = new float[]{0, -1, displayW, + 1, 0, 0, + 0, 0, 1}; + } + Matrix toOrient = new Matrix(); + toOrient.setValues(values); + return toOrient; + } + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public MotionEvent createFromParcel(Parcel in) { diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index a2de0aff5dfac..143017cba57dc 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -36,6 +36,7 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; +import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -59,6 +60,9 @@ public class PointerLocationView extends View implements InputDeviceListener, */ private static final String GESTURE_EXCLUSION_PROP = "debug.pointerlocation.showexclusion"; + private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION = + SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false); + public static class PointerState { // Trace of previous points. private float[] mTraceX = new float[32]; @@ -352,6 +356,21 @@ public class PointerLocationView extends View implements InputDeviceListener, .toString(), 1 + itemW * 6, base, mTextPaint); } + int saveId = canvas.save(); + if (ENABLE_PER_WINDOW_INPUT_ROTATION) { + // Rotate negative (since we're rotating the drawing canvas vs the output). + canvas.rotate(-90.0f * mContext.getDisplay().getRotation()); + switch (mContext.getDisplay().getRotation()) { + case Surface.ROTATION_90: + canvas.translate(-canvas.getHeight(), 0); + break; + case Surface.ROTATION_180: + canvas.translate(-canvas.getWidth(), -canvas.getHeight()); + break; + case Surface.ROTATION_270: + canvas.translate(0, -canvas.getWidth()); + } + } // Pointer trace. for (int p = 0; p < NP; p++) { final PointerState ps = mPointers.get(p); @@ -399,7 +418,10 @@ public class PointerLocationView extends View implements InputDeviceListener, if (mCurDown && ps.mCurDown) { // Draw crosshairs. canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint); - canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint); + // Extend crosshairs to cover screen regardless of rotation (ie. since the rotated + // canvas can "expose" content past 0 and up-to the largest screen dimension). + canvas.drawLine(ps.mCoords.x, -getHeight(), ps.mCoords.x, + Math.max(getHeight(), getWidth()), mTargetPaint); // Draw current point. int pressureLevel = (int)(ps.mCoords.pressure * 255); @@ -458,6 +480,7 @@ public class PointerLocationView extends View implements InputDeviceListener, } } } + canvas.restoreToCount(saveId); } private void logMotionEvent(String type, MotionEvent event) { diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java index 786ae89ac2ae3..b3450de800920 100644 --- a/core/tests/coretests/src/android/view/MotionEventTest.java +++ b/core/tests/coretests/src/android/view/MotionEventTest.java @@ -169,4 +169,24 @@ public class MotionEventTest { assertEquals(0x3 << 30, ID_SOURCE_MASK & event.getId()); } } + + @Test + public void testEventRotation() { + final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, + ACTION_DOWN, 30 /* x */, 50 /* y */, 0 /* metaState */); + MotionEvent rot90 = MotionEvent.obtain(event); + rot90.transform(MotionEvent.createRotateMatrix(/* 90 deg */1, 1000, 600)); + assertEquals(50, (int) rot90.getX()); + assertEquals(570, (int) rot90.getY()); + + MotionEvent rot180 = MotionEvent.obtain(event); + rot180.transform(MotionEvent.createRotateMatrix(/* 180 deg */2, 1000, 600)); + assertEquals(970, (int) rot180.getX()); + assertEquals(550, (int) rot180.getY()); + + MotionEvent rot270 = MotionEvent.obtain(event); + rot270.transform(MotionEvent.createRotateMatrix(/* 270 deg */3, 1000, 600)); + assertEquals(950, (int) rot270.getX()); + assertEquals(30, (int) rot270.getY()); + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java index ffde84128549b..259cca8c01e28 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java @@ -16,13 +16,12 @@ package com.android.systemui.shared.system; -import android.os.Bundle; +import android.graphics.Matrix; import android.os.Looper; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; import android.view.InputChannel; import android.view.InputEvent; -import android.view.InputEventSender; import android.view.MotionEvent; /** @@ -53,6 +52,12 @@ public class InputChannelCompat { return target.addBatch(src); } + /** @see MotionEvent#createRotateMatrix */ + public static Matrix createRotationMatrix( + /*@Surface.Rotation*/ int rotation, int displayW, int displayH) { + return MotionEvent.createRotateMatrix(rotation, displayW, displayH); + } + /** * @see BatchedInputEventReceiver */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index d7a3537a4a5ff..292cc7a7deaa9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -38,6 +38,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.Choreographer; +import android.view.Display; import android.view.ISystemGestureExclusionListener; import android.view.InputDevice; import android.view.InputEvent; @@ -96,6 +97,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa static final boolean DEBUG_MISSING_GESTURE = true; static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; + private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION = + SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false); + private ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { @Override @@ -505,9 +509,19 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa } private void onInputEvent(InputEvent ev) { - if (ev instanceof MotionEvent) { - onMotionEvent((MotionEvent) ev); + if (!(ev instanceof MotionEvent)) return; + MotionEvent event = (MotionEvent) ev; + if (ENABLE_PER_WINDOW_INPUT_ROTATION) { + final Display display = mContext.getDisplay(); + int rotation = display.getRotation(); + if (rotation != Surface.ROTATION_0) { + Point sz = new Point(); + display.getRealSize(sz); + event = MotionEvent.obtain(event); + event.transform(MotionEvent.createRotateMatrix(rotation, sz.x, sz.y)); + } } + onMotionEvent(event); } private void updateMLModelState() { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 4e974112a5c30..c5be20e398642 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -37,6 +37,7 @@ import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.ContentObserver; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayViewport; import android.hardware.input.IInputDevicesChangedListener; @@ -69,6 +70,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.VibrationEffect; import android.provider.DeviceConfig; @@ -168,6 +170,9 @@ public class InputManagerService extends IInputManager.Stub /** TODO(b/169067926): Remove this. */ private static final boolean UNTRUSTED_TOUCHES_TOAST = false; + public static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION = + SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false); + // Pointer to native input manager service object. private final long mPtr; @@ -518,8 +523,51 @@ public class InputManagerService extends IInputManager.Stub nativeReloadDeviceAliases(mPtr); } + /** Rotates CCW by `delta` 90-degree increments. */ + private static void rotateBounds(Rect inOutBounds, int parentW, int parentH, int delta) { + int rdelta = ((delta % 4) + 4) % 4; + int origLeft = inOutBounds.left; + switch (rdelta) { + case 0: + return; + case 1: + inOutBounds.left = inOutBounds.top; + inOutBounds.top = parentW - inOutBounds.right; + inOutBounds.right = inOutBounds.bottom; + inOutBounds.bottom = parentW - origLeft; + return; + case 2: + inOutBounds.left = parentW - inOutBounds.right; + inOutBounds.right = parentW - origLeft; + return; + case 3: + inOutBounds.left = parentH - inOutBounds.bottom; + inOutBounds.bottom = inOutBounds.right; + inOutBounds.right = parentH - inOutBounds.top; + inOutBounds.top = origLeft; + return; + } + } + private void setDisplayViewportsInternal(List viewports) { - nativeSetDisplayViewports(mPtr, viewports.toArray(new DisplayViewport[0])); + final DisplayViewport[] vArray = new DisplayViewport[viewports.size()]; + if (ENABLE_PER_WINDOW_INPUT_ROTATION) { + // Remove all viewport operations. They will be built-into the window transforms. + for (int i = viewports.size() - 1; i >= 0; --i) { + final DisplayViewport v = vArray[i] = viewports.get(i).makeCopy(); + // deviceWidth/Height are apparently in "rotated" space, so flip them if needed. + int dw = (v.orientation % 2) == 0 ? v.deviceWidth : v.deviceHeight; + int dh = (v.orientation % 2) == 0 ? v.deviceHeight : v.deviceWidth; + v.logicalFrame.set(0, 0, dw, dh); + v.physicalFrame.set(0, 0, dw, dh); + v.orientation = 0; + } + } else { + for (int i = viewports.size() - 1; i >= 0; --i) { + vArray[i] = viewports.get(i); + } + } + nativeSetDisplayViewports(mPtr, vArray); } /** diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 86968ed6d1753..13e43a37eed2c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1002,7 +1002,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final InputChannel inputChannel = mWmService.mInputManager.monitorInput( "PointerEventDispatcher" + mDisplayId, mDisplayId); - mPointerEventDispatcher = new PointerEventDispatcher(inputChannel); + mPointerEventDispatcher = new PointerEventDispatcher(inputChannel, this); // Tap Listeners are supported for: // 1. All physical displays (multi-display). diff --git a/services/core/java/com/android/server/wm/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java index 6b8144c690792..08de9b052ced9 100644 --- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java +++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java @@ -16,11 +16,14 @@ package com.android.server.wm; +import static com.android.server.input.InputManagerService.ENABLE_PER_WINDOW_INPUT_ROTATION; + import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.MotionEvent; +import android.view.Surface; import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.server.UiThread; @@ -31,8 +34,11 @@ public class PointerEventDispatcher extends InputEventReceiver { private final ArrayList mListeners = new ArrayList<>(); private PointerEventListener[] mListenersArray = new PointerEventListener[0]; - public PointerEventDispatcher(InputChannel inputChannel) { + private final DisplayContent mDisplayContent; + + public PointerEventDispatcher(InputChannel inputChannel, DisplayContent dc) { super(inputChannel, UiThread.getHandler().getLooper()); + mDisplayContent = dc; } @Override @@ -40,7 +46,16 @@ public class PointerEventDispatcher extends InputEventReceiver { try { if (event instanceof MotionEvent && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - final MotionEvent motionEvent = (MotionEvent) event; + MotionEvent motionEvent = (MotionEvent) event; + if (ENABLE_PER_WINDOW_INPUT_ROTATION) { + int rotation = mDisplayContent.getRotation(); + if (rotation != Surface.ROTATION_0) { + motionEvent = MotionEvent.obtain(motionEvent); + motionEvent.transform(MotionEvent.createRotateMatrix(rotation, + mDisplayContent.getDisplayMetrics().widthPixels, + mDisplayContent.getDisplayMetrics().heightPixels)); + } + } PointerEventListener[] listeners; synchronized (mListeners) { if (mListenersArray == null) {