TouchInputMapper: Perform physical frame hit test in rotated display space

The physical frame is specified in DisplayViewport in the rotated
display space. The frame is not symmetric along the X or Y axes because
the right and bottom edges are outside of the frame. For example, for a
physical frame with bounds [left, top, right, bottom], any point with an
x value of `right` or y value of `bottom` is outside the frame, whereas
points in the frame could contain an x value of `left` and a y value of
`top`.

To address this asymmetry, we must perform any hit tests in the intended
coordinate space, which in this case is that of the rotated display.

This logic is tested again in following CLs.

Bug: 236798672
Bug: 257118693
Test: atest inputflinger_tests
Change-Id: I403a686c437aa53cb808910b296a7251e0e96321
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 7c56631..66691f8 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -51,9 +51,8 @@
     return base::StringPrintf("%dx%d", size.width, size.height);
 }
 
-static bool isPointInRect(const Rect& rect, int32_t x, int32_t y) {
-    // Consider all four sides as "inclusive".
-    return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
+static bool isPointInRect(const Rect& rect, vec2 p) {
+    return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
 }
 
 template <typename T>
@@ -81,29 +80,12 @@
     return value >= 8 ? value - 16 : value;
 }
 
-static std::tuple<ui::Size /*displayBounds*/, Rect /*physicalFrame*/> getNaturalDisplayInfo(
-        const DisplayViewport& viewport) {
+static ui::Size getNaturalDisplaySize(const DisplayViewport& viewport) {
     ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight};
     if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) {
         std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height);
     }
-
-    ui::Transform rotate(ui::Transform::toRotationFlags(viewport.orientation),
-                         rotatedDisplaySize.width, rotatedDisplaySize.height);
-
-    Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight,
-                       viewport.physicalBottom};
-    physicalFrame = rotate.transform(physicalFrame);
-
-    LOG_ALWAYS_FATAL_IF(!physicalFrame.isValid());
-    if (physicalFrame.isEmpty()) {
-        ALOGE("Viewport is not set properly: %s", viewport.toString().c_str());
-        physicalFrame.right =
-                physicalFrame.left + (physicalFrame.width() == 0 ? 1 : physicalFrame.width());
-        physicalFrame.bottom =
-                physicalFrame.top + (physicalFrame.height() == 0 ? 1 : physicalFrame.height());
-    }
-    return {rotatedDisplaySize, physicalFrame};
+    return rotatedDisplaySize;
 }
 
 // --- RawPointerData ---
@@ -856,7 +838,7 @@
     }
 }
 
-ui::Transform TouchInputMapper::computeInputTransform() const {
+void TouchInputMapper::computeInputTransforms() {
     const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()};
 
     ui::Size rotatedRawSize = rawSize;
@@ -881,7 +863,13 @@
     const float yScale = static_cast<float>(mDisplayBounds.height) / rotatedRawSize.height;
     scaleToDisplay.set(xScale, 0, 0, yScale);
 
-    return (scaleToDisplay * (rotate * undoRawOffset));
+    mRawToDisplay = (scaleToDisplay * (rotate * undoRawOffset));
+
+    // Calculate the transform that takes raw coordinates to the rotated display space.
+    ui::Transform displayToRotatedDisplay;
+    displayToRotatedDisplay.set(ui::Transform::toRotationFlags(-mViewport.orientation),
+                                mViewport.deviceWidth, mViewport.deviceHeight);
+    mRawToRotatedDisplay = displayToRotatedDisplay * mRawToDisplay;
 }
 
 void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) {
@@ -955,7 +943,9 @@
         if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
             const auto oldDisplayBounds = mDisplayBounds;
 
-            std::tie(mDisplayBounds, mPhysicalFrameInDisplay) = getNaturalDisplayInfo(mViewport);
+            mDisplayBounds = getNaturalDisplaySize(mViewport);
+            mPhysicalFrameInRotatedDisplay = {mViewport.physicalLeft, mViewport.physicalTop,
+                                              mViewport.physicalRight, mViewport.physicalBottom};
 
             // InputReader works in the un-rotated display coordinate space, so we don't need to do
             // anything if the device is already orientation-aware. If the device is not
@@ -972,13 +962,14 @@
 
             // Apply the input device orientation for the device.
             mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation;
-            mRawToDisplay = computeInputTransform();
+            computeInputTransforms();
         } else {
             mDisplayBounds = rawSize;
-            mPhysicalFrameInDisplay = Rect{mDisplayBounds};
+            mPhysicalFrameInRotatedDisplay = Rect{mDisplayBounds};
             mInputDeviceOrientation = ui::ROTATION_0;
             mRawToDisplay.reset();
             mRawToDisplay.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue);
+            mRawToRotatedDisplay = mRawToDisplay;
         }
     }
 
@@ -1061,7 +1052,8 @@
 void TouchInputMapper::dumpDisplay(std::string& dump) {
     dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str());
     dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str());
-    dump += StringPrintf(INDENT3 "PhysicalFrame: %s\n", toString(mPhysicalFrameInDisplay).c_str());
+    dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n",
+                         toString(mPhysicalFrameInRotatedDisplay).c_str());
     dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
 }
 
@@ -3822,12 +3814,9 @@
 }
 
 bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const {
-    const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale;
-    const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale;
-
     return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
             y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
-            isPointInRect(mPhysicalFrameInDisplay, xScaled, yScaled);
+            isPointInRect(mPhysicalFrameInRotatedDisplay, mRawToRotatedDisplay.transform(x, y));
 }
 
 const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 45a4962..1a583c0 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -410,9 +410,9 @@
     // Always starts at (0, 0).
     ui::Size mDisplayBounds{ui::kInvalidSize};
 
-    // The physical frame is the rectangle in the natural display's coordinate space that maps to
+    // The physical frame is the rectangle in the rotated display's coordinate space that maps to
     // the logical display frame.
-    Rect mPhysicalFrameInDisplay{Rect::INVALID_RECT};
+    Rect mPhysicalFrameInRotatedDisplay{Rect::INVALID_RECT};
 
     // The orientation of the input device relative to that of the display panel. It specifies
     // the rotation of the input device coordinates required to produce the display panel
@@ -423,6 +423,11 @@
     // coordinate space. InputReader generates events in the un-rotated display's coordinate space.
     ui::Transform mRawToDisplay;
 
+    // The transform that maps the input device's raw coordinate space to the rotated display's
+    // coordinate space. This used to perform hit-testing of raw events with the physical frame in
+    // the rotated coordinate space. See mPhysicalFrameInRotatedDisplay.
+    ui::Transform mRawToRotatedDisplay;
+
     // Translation and scaling factors, orientation-independent.
     float mXScale;
     float mXPrecision;
@@ -817,7 +822,7 @@
 
     static void assignPointerIds(const RawState& last, RawState& current);
 
-    ui::Transform computeInputTransform() const;
+    void computeInputTransforms();
 
     void configureDeviceType();
 };
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 621bad7..e407638 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -5740,6 +5740,61 @@
     EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
 }
 
+TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.orientationAware", "1");
+    prepareDisplay(ui::ROTATION_0);
+    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    // Set a physical frame in the display viewport.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->physicalLeft = 20;
+    viewport->physicalTop = 600;
+    viewport->physicalRight = 30;
+    viewport->physicalBottom = 610;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    // Start the touch.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
+    processSync(mapper);
+
+    // Expect all input starting outside the physical frame to be ignored.
+    const std::array<Point, 6> outsidePoints = {
+            {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}};
+    for (const auto& p : outsidePoints) {
+        processMove(mapper, toRawX(p.x), toRawY(p.y));
+        processSync(mapper);
+        EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    }
+
+    // Move the touch into the physical frame.
+    processMove(mapper, toRawX(25), toRawY(605));
+    processSync(mapper);
+    NotifyMotionArgs args;
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    EXPECT_NEAR(25, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(605, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    // Once the touch down is reported, continue reporting input, even if it is outside the frame.
+    for (const auto& p : outsidePoints) {
+        processMove(mapper, toRawX(p.x), toRawY(p.y));
+        processSync(mapper);
+        EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+        EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+        EXPECT_NEAR(p.x, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+        EXPECT_NEAR(p.y, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+    }
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+}
+
 TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);