Per-window input rotation: Let InputReader use un-rotated coordinates
InputReader works in the rotated coordinate system that is oriented with
the display's orientation obtained through DisplayViewport.
When per-window input rotation is enabled, we let InputReader work in
the un-rotated coordinate space as if the display's orientation is
always ROTATION_0. Since the input rotation is included in the
per-window transform, the output from InputReader should not be rotated.
This requires two changes in InputReader:
1. When setting an orientation to use for an InputDevice, we do the
opposite of the default behavior: If a device was being rotated before,
we don't rotate it anymore, and vice-versa. We invert the direction of
the rotation so that when the coordinates are rotated by the per-window
transform, they appear as expected.
2. Since PointerController should still use the rotated coordinates, we
need to convert all display coordinates going in/out of
PointerController.
Bug: 179274888
Test: test touch, mouse, and touchpad input in all four orientations
using a test app and the PointerLocationView.
Change-Id: I1eec8ba1d72355564a79cdf1c14c88828124e8a4
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index bb12be7..d6bd823 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -188,11 +188,29 @@
if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
mOrientation = DISPLAY_ORIENTATION_0;
- if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
- std::optional<DisplayViewport> internalViewport =
- config->getDisplayViewportByType(ViewportType::INTERNAL);
- if (internalViewport) {
- mOrientation = internalViewport->orientation;
+ const bool isOrientedDevice =
+ (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
+
+ if (isPerWindowInputRotationEnabled()) {
+ // When per-window input rotation is enabled, InputReader works in the un-rotated
+ // coordinate space, so we don't need to do anything if the device is already
+ // orientation-aware. If the device is not orientation-aware, then we need to apply the
+ // inverse rotation of the display so that when the display rotation is applied later
+ // as a part of the per-window transform, we get the expected screen coordinates.
+ if (!isOrientedDevice) {
+ std::optional<DisplayViewport> internalViewport =
+ config->getDisplayViewportByType(ViewportType::INTERNAL);
+ if (internalViewport) {
+ mOrientation = getInverseRotation(internalViewport->orientation);
+ }
+ }
+ } else {
+ if (isOrientedDevice) {
+ std::optional<DisplayViewport> internalViewport =
+ config->getDisplayViewportByType(ViewportType::INTERNAL);
+ if (internalViewport) {
+ mOrientation = internalViewport->orientation;
+ }
}
}
@@ -294,11 +312,8 @@
float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
bool moved = deltaX != 0 || deltaY != 0;
- // Rotate delta according to orientation if needed.
- if (mParameters.orientationAware && mParameters.hasAssociatedDisplay &&
- (deltaX != 0.0f || deltaY != 0.0f)) {
- rotateDelta(mOrientation, &deltaX, &deltaY);
- }
+ // Rotate delta according to orientation.
+ rotateDelta(mOrientation, &deltaX, &deltaY);
// Move the pointer.
PointerProperties pointerProperties;
@@ -326,7 +341,15 @@
mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
if (moved) {
- mPointerController->move(deltaX, deltaY);
+ float dx = deltaX;
+ float dy = deltaY;
+ if (isPerWindowInputRotationEnabled()) {
+ // Rotate the delta from InputReader's un-rotated coordinate space to
+ // PointerController's rotated coordinate space that is oriented with the
+ // viewport.
+ rotateDelta(getInverseRotation(mOrientation), &dx, &dy);
+ }
+ mPointerController->move(dx, dy);
}
if (buttonsChanged) {
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index 5344227..1843b03 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -17,6 +17,7 @@
#ifndef _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H
#define _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H
+#include <android-base/properties.h>
#include <input/DisplayViewport.h>
#include <stdint.h>
@@ -28,6 +29,26 @@
// --- Static Definitions ---
+// When per-window input rotation is enabled, display transformations such as rotation and
+// projection are part of the input window's transform. This means InputReader should work in the
+// un-rotated coordinate space.
+static bool isPerWindowInputRotationEnabled() {
+ static const bool PER_WINDOW_INPUT_ROTATION =
+ base::GetBoolProperty("persist.debug.per_window_input_rotation", false);
+ return PER_WINDOW_INPUT_ROTATION;
+}
+
+static int32_t getInverseRotation(int32_t orientation) {
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ return DISPLAY_ORIENTATION_270;
+ case DISPLAY_ORIENTATION_270:
+ return DISPLAY_ORIENTATION_90;
+ default:
+ return orientation;
+ }
+}
+
static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) {
float temp;
switch (orientation) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 1a7ddee..13712ee 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -28,6 +28,30 @@
namespace android {
+namespace {
+
+// Rotates the given point (x, y) by the supplied orientation. The width and height are the
+// dimensions of the surface prior to this rotation being applied.
+void rotatePoint(int32_t orientation, float& x, float& y, int32_t width, int32_t height) {
+ rotateDelta(orientation, &x, &y);
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ y += width;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x += width;
+ y += height;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ x += height;
+ break;
+ default:
+ break;
+ }
+}
+
+} // namespace
+
// --- Constants ---
// Maximum amount of latency to add to touch events while waiting for data from an
@@ -729,8 +753,20 @@
mSurfaceRight = mSurfaceLeft + naturalLogicalWidth;
mSurfaceBottom = mSurfaceTop + naturalLogicalHeight;
- mSurfaceOrientation =
- mParameters.orientationAware ? mViewport.orientation : DISPLAY_ORIENTATION_0;
+ if (isPerWindowInputRotationEnabled()) {
+ // When per-window input rotation is enabled, InputReader works in the un-rotated
+ // coordinate space, so we don't need to do anything if the device is already
+ // orientation-aware. If the device is not orientation-aware, then we need to apply
+ // the inverse rotation of the display so that when the display rotation is applied
+ // later as a part of the per-window transform, we get the expected screen
+ // coordinates.
+ mSurfaceOrientation = mParameters.orientationAware
+ ? DISPLAY_ORIENTATION_0
+ : getInverseRotation(mViewport.orientation);
+ } else {
+ mSurfaceOrientation = mParameters.orientationAware ? mViewport.orientation
+ : DISPLAY_ORIENTATION_0;
+ }
} else {
mPhysicalWidth = rawWidth;
mPhysicalHeight = rawHeight;
@@ -1637,10 +1673,9 @@
mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
mPointerController->setButtonState(mCurrentRawState.buttonState);
- mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
- mCurrentCookedState.cookedPointerData.idToIndex,
- mCurrentCookedState.cookedPointerData.touchingIdBits,
- mViewport.displayId);
+ setTouchSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
+ mCurrentCookedState.cookedPointerData.idToIndex,
+ mCurrentCookedState.cookedPointerData.touchingIdBits, mViewport.displayId);
}
bool TouchInputMapper::isTouchScreen() {
@@ -2378,10 +2413,9 @@
}
if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
- mPointerController->setSpots(mPointerGesture.currentGestureCoords,
- mPointerGesture.currentGestureIdToIndex,
- mPointerGesture.currentGestureIdBits,
- mPointerController->getDisplayId());
+ setTouchSpots(mPointerGesture.currentGestureCoords,
+ mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.currentGestureIdBits, mPointerController->getDisplayId());
}
} else {
mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
@@ -2525,8 +2559,7 @@
// the pointer is hovering again even if the user is not currently touching
// the touch pad. This ensures that a view will receive a fresh hover enter
// event after a tap.
- float x, y;
- mPointerController->getPosition(&x, &y);
+ auto [x, y] = getMouseCursorPosition();
PointerProperties pointerProperties;
pointerProperties.clear();
@@ -2783,13 +2816,12 @@
// Move the pointer using a relative motion.
// When using spots, the click will occur at the position of the anchor
// spot and all other spots will move there.
- mPointerController->move(deltaX, deltaY);
+ moveMouseCursor(deltaX, deltaY);
} else {
mPointerVelocityControl.reset();
}
- float x, y;
- mPointerController->getPosition(&x, &y);
+ auto [x, y] = getMouseCursorPosition();
mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG;
mPointerGesture.currentGestureIdBits.clear();
@@ -2815,8 +2847,7 @@
mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) &&
lastFingerCount == 1) {
if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
- float x, y;
- mPointerController->getPosition(&x, &y);
+ auto [x, y] = getMouseCursorPosition();
if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
#if DEBUG_GESTURES
@@ -2884,8 +2915,7 @@
mPointerGesture.currentGestureMode = PointerGesture::Mode::HOVER;
if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) {
if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
- float x, y;
- mPointerController->getPosition(&x, &y);
+ auto [x, y] = getMouseCursorPosition();
if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG;
@@ -2919,7 +2949,7 @@
// Move the pointer using a relative motion.
// When using spots, the hover or drag will occur at the position of the anchor spot.
- mPointerController->move(deltaX, deltaY);
+ moveMouseCursor(deltaX, deltaY);
} else {
mPointerVelocityControl.reset();
}
@@ -2941,8 +2971,7 @@
down = false;
}
- float x, y;
- mPointerController->getPosition(&x, &y);
+ auto [x, y] = getMouseCursorPosition();
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
@@ -3015,8 +3044,9 @@
mCurrentRawState.rawPointerData
.getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
&mPointerGesture.referenceTouchY);
- mPointerController->getPosition(&mPointerGesture.referenceGestureX,
- &mPointerGesture.referenceGestureY);
+ auto [x, y] = getMouseCursorPosition();
+ mPointerGesture.referenceGestureX = x;
+ mPointerGesture.referenceGestureY = y;
}
// Clear the reference deltas for fingers not yet included in the reference calculation.
@@ -3354,14 +3384,13 @@
if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
uint32_t id = mCurrentCookedState.stylusIdBits.firstMarkedBit();
uint32_t index = mCurrentCookedState.cookedPointerData.idToIndex[id];
- float x = mCurrentCookedState.cookedPointerData.pointerCoords[index].getX();
- float y = mCurrentCookedState.cookedPointerData.pointerCoords[index].getY();
- mPointerController->setPosition(x, y);
+ setMouseCursorPosition(mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(),
+ mCurrentCookedState.cookedPointerData.pointerCoords[index].getY());
hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id);
down = !hovering;
- mPointerController->getPosition(&x, &y);
+ auto [x, y] = getMouseCursorPosition();
mPointerSimple.currentCoords.copyFrom(
mCurrentCookedState.cookedPointerData.pointerCoords[index]);
mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3402,7 +3431,7 @@
rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
mPointerVelocityControl.move(when, &deltaX, &deltaY);
- mPointerController->move(deltaX, deltaY);
+ moveMouseCursor(deltaX, deltaY);
} else {
mPointerVelocityControl.reset();
}
@@ -3410,8 +3439,7 @@
down = isPointerDown(mCurrentRawState.buttonState);
hovering = !down;
- float x, y;
- mPointerController->getPosition(&x, &y);
+ auto [x, y] = getMouseCursorPosition();
mPointerSimple.currentCoords.copyFrom(
mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]);
mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3451,9 +3479,7 @@
}
int32_t displayId = mPointerController->getDisplayId();
- float xCursorPosition;
- float yCursorPosition;
- mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ auto [xCursorPosition, yCursorPosition] = getMouseCursorPosition();
if (mPointerSimple.down && !down) {
mPointerSimple.down = false;
@@ -3619,7 +3645,9 @@
float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
if (mDeviceMode == DeviceMode::POINTER) {
- mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ auto [x, y] = getMouseCursorPosition();
+ xCursorPosition = x;
+ yCursorPosition = y;
}
const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
const int32_t deviceId = getDeviceId();
@@ -3969,4 +3997,63 @@
return std::nullopt;
}
+void TouchInputMapper::moveMouseCursor(float dx, float dy) const {
+ if (isPerWindowInputRotationEnabled()) {
+ // Convert from InputReader's un-rotated coordinate space to PointerController's coordinate
+ // space that is oriented with the viewport.
+ rotateDelta(mViewport.orientation, &dx, &dy);
+ }
+
+ mPointerController->move(dx, dy);
+}
+
+std::pair<float, float> TouchInputMapper::getMouseCursorPosition() const {
+ float x = 0;
+ float y = 0;
+ mPointerController->getPosition(&x, &y);
+
+ if (!isPerWindowInputRotationEnabled()) return {x, y};
+ if (!mViewport.isValid()) return {x, y};
+
+ // Convert from PointerController's rotated coordinate space that is oriented with the viewport
+ // to InputReader's un-rotated coordinate space.
+ const int32_t orientation = getInverseRotation(mViewport.orientation);
+ rotatePoint(orientation, x, y, mViewport.deviceWidth, mViewport.deviceHeight);
+ return {x, y};
+}
+
+void TouchInputMapper::setMouseCursorPosition(float x, float y) const {
+ if (isPerWindowInputRotationEnabled() && mViewport.isValid()) {
+ // Convert from InputReader's un-rotated coordinate space to PointerController's rotated
+ // coordinate space that is oriented with the viewport.
+ rotatePoint(mViewport.orientation, x, y, mRawSurfaceWidth, mRawSurfaceHeight);
+ }
+
+ mPointerController->setPosition(x, y);
+}
+
+void TouchInputMapper::setTouchSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits, int32_t displayId) {
+ std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
+
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+ const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
+ float x = spotCoords[index].getX();
+ float y = spotCoords[index].getY();
+ float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+
+ if (isPerWindowInputRotationEnabled()) {
+ // Convert from InputReader's un-rotated coordinate space to PointerController's rotated
+ // coordinate space.
+ rotatePoint(mViewport.orientation, x, y, mRawSurfaceWidth, mRawSurfaceHeight);
+ }
+
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+ }
+
+ mPointerController->setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits, displayId);
+}
+
} // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 9b84ed5..5146299 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -776,6 +776,14 @@
const char* modeToString(DeviceMode deviceMode);
void rotateAndScale(float& x, float& y);
+
+ // Wrapper methods for interfacing with PointerController. These are used to convert points
+ // between the coordinate spaces used by InputReader and PointerController, if they differ.
+ void moveMouseCursor(float dx, float dy) const;
+ std::pair<float, float> getMouseCursorPosition() const;
+ void setMouseCursorPosition(float x, float y) const;
+ void setTouchSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits, int32_t displayId);
};
} // namespace android