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