Merge changes from topic "move-input-rotation-to-native"

* changes:
  InputDispatcherPolicy: Transform incoming/outgoing events
  TouchInputMapper: Block touches outside logical display
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 30e5d5b..a1542c8 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -20,10 +20,8 @@
 #include <attestation/HmacKeyManager.h>
 #include <cutils/compiler.h>
 #include <inttypes.h>
-#include <limits.h>
 #include <string.h>
 
-#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <gui/constants.h>
 #include <input/Input.h>
@@ -43,15 +41,6 @@
 
 namespace {
 
-// When per-window-input-rotation is enabled, InputFlinger works in the un-rotated display
-// coordinates and SurfaceFlinger includes the display rotation in the input window transforms.
-bool isPerWindowInputRotationEnabled() {
-    static const bool PER_WINDOW_INPUT_ROTATION =
-            base::GetBoolProperty("persist.debug.per_window_input_rotation", false);
-
-    return PER_WINDOW_INPUT_ROTATION;
-}
-
 float transformAngle(const ui::Transform& transform, float angleRadians) {
     // Construct and transform a vector oriented at the specified clockwise angle from vertical.
     // Coordinate system: down is increasing Y, right is increasing X.
@@ -511,8 +500,6 @@
                                              size_t historicalIndex) const {
     const PointerCoords* coords = getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
 
-    if (!isPerWindowInputRotationEnabled()) return coords->getAxisValue(axis);
-
     if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
         // For compatibility, convert raw coordinates into logical display space.
         const vec2 xy = shouldDisregardTranslation(mSource)
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index 7e7dfd5..caf3a61 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -231,36 +231,14 @@
     static constexpr float RAW_X_OFFSET = 12;
     static constexpr float RAW_Y_OFFSET = -41.1;
 
-    static const std::optional<bool> INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE;
-
     int32_t mId;
     ui::Transform mTransform;
     ui::Transform mRawTransform;
 
-    void SetUp() override;
-    void TearDown() override;
-
     void initializeEventWithHistory(MotionEvent* event);
     void assertEqualsEventWithHistory(const MotionEvent* event);
 };
 
-const std::optional<bool> MotionEventTest::INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE =
-        !base::GetProperty("persist.debug.per_window_input_rotation", "").empty()
-        ? std::optional(base::GetBoolProperty("persist.debug.per_window_input_rotation", false))
-        : std::nullopt;
-
-void MotionEventTest::SetUp() {
-    // Ensure per_window_input_rotation is enabled.
-    base::SetProperty("persist.debug.per_window_input_rotation", "true");
-}
-
-void MotionEventTest::TearDown() {
-    const auto val = INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE.has_value()
-            ? (*INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE ? "true" : "false")
-            : "";
-    base::SetProperty("persist.debug.per_window_input_rotation", val);
-}
-
 void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
     mId = InputEvent::nextId();
     mTransform.set({X_SCALE, 0, X_OFFSET, 0, Y_SCALE, Y_OFFSET, 0, 0, 1});
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 5f48c1d..f094fee 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -524,6 +524,16 @@
     return true;
 }
 
+bool isFromSource(uint32_t source, uint32_t test) {
+    return (source & test) == test;
+}
+
+vec2 transformWithoutTranslation(const ui::Transform& transform, float x, float y) {
+    const vec2 transformedXy = transform.transform(x, y);
+    const vec2 transformedOrigin = transform.transform(0, 0);
+    return transformedXy - transformedOrigin;
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -3962,15 +3972,19 @@
         mLock.lock();
 
         if (shouldSendMotionToInputFilterLocked(args)) {
+            ui::Transform displayTransform;
+            if (const auto it = mDisplayInfos.find(args->displayId); it != mDisplayInfos.end()) {
+                displayTransform = it->second.transform;
+            }
+
             mLock.unlock();
 
             MotionEvent event;
-            ui::Transform identityTransform;
             event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
                              args->action, args->actionButton, args->flags, args->edgeFlags,
                              args->metaState, args->buttonState, args->classification,
-                             identityTransform, args->xPrecision, args->yPrecision,
-                             args->xCursorPosition, args->yCursorPosition, identityTransform,
+                             displayTransform, args->xPrecision, args->yPrecision,
+                             args->xCursorPosition, args->yCursorPosition, displayTransform,
                              args->downTime, args->eventTime, args->pointerCount,
                              args->pointerProperties, args->pointerCoords);
 
@@ -4220,6 +4234,7 @@
                                                   pointerProperties, samplePointerCoords,
                                                   motionEvent.getXOffset(),
                                                   motionEvent.getYOffset());
+            transformMotionEntryForInjectionLocked(*injectedEntry);
             injectedEntries.push(std::move(injectedEntry));
             for (size_t i = motionEvent.getHistorySize(); i > 0; i--) {
                 sampleEventTimes += 1;
@@ -4241,6 +4256,7 @@
                                                       uint32_t(pointerCount), pointerProperties,
                                                       samplePointerCoords, motionEvent.getXOffset(),
                                                       motionEvent.getYOffset());
+                transformMotionEntryForInjectionLocked(*nextInjectedEntry);
                 injectedEntries.push(std::move(nextInjectedEntry));
             }
             break;
@@ -4404,6 +4420,38 @@
     }
 }
 
+void InputDispatcher::transformMotionEntryForInjectionLocked(MotionEntry& entry) const {
+    const bool isRelativeMouseEvent = isFromSource(entry.source, AINPUT_SOURCE_MOUSE_RELATIVE);
+    if (!isRelativeMouseEvent && !isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+        return;
+    }
+
+    // Input injection works in the logical display coordinate space, but the input pipeline works
+    // display space, so we need to transform the injected events accordingly.
+    const auto it = mDisplayInfos.find(entry.displayId);
+    if (it == mDisplayInfos.end()) return;
+    const auto& transformToDisplay = it->second.transform.inverse();
+
+    for (uint32_t i = 0; i < entry.pointerCount; i++) {
+        PointerCoords& pc = entry.pointerCoords[i];
+        const auto xy = isRelativeMouseEvent
+                ? transformWithoutTranslation(transformToDisplay, pc.getX(), pc.getY())
+                : transformToDisplay.transform(pc.getXYValue());
+        pc.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+        pc.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+
+        // Axes with relative values never represent points on a screen, so they should never have
+        // translation applied. If a device does not report relative values, these values are always
+        // 0, and will remain unaffected by the following operation.
+        const auto rel =
+                transformWithoutTranslation(transformToDisplay,
+                                            pc.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
+                                            pc.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
+        pc.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, rel.x);
+        pc.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, rel.y);
+    }
+}
+
 void InputDispatcher::incrementPendingForegroundDispatches(EventEntry& entry) {
     InjectionState* injectionState = entry.injectionState;
     if (injectionState) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 51ec551..2282d91 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -280,6 +280,7 @@
     bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
     void setInjectionResult(EventEntry& entry,
                             android::os::InputEventInjectionResult injectionResult);
+    void transformMotionEntryForInjectionLocked(MotionEntry&) const REQUIRES(mLock);
 
     std::condition_variable mInjectionSyncFinished;
     void incrementPendingForegroundDispatches(EventEntry& entry);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 419b0d0..22a0b57 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -749,16 +749,17 @@
             mPhysicalLeft = naturalPhysicalLeft;
             mPhysicalTop = naturalPhysicalTop;
 
-            const int32_t oldSurfaceWidth = mRawSurfaceWidth;
-            const int32_t oldSurfaceHeight = mRawSurfaceHeight;
-            mRawSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
-            mRawSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
-            mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
-            mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
-            mSurfaceRight = mSurfaceLeft + naturalLogicalWidth;
-            mSurfaceBottom = mSurfaceTop + naturalLogicalHeight;
-
             if (isPerWindowInputRotationEnabled()) {
+                // When per-window input rotation is enabled, InputReader works in the display
+                // space, so the surface bounds are the bounds of the display device.
+                const int32_t oldSurfaceWidth = mRawSurfaceWidth;
+                const int32_t oldSurfaceHeight = mRawSurfaceHeight;
+                mRawSurfaceWidth = naturalDeviceWidth;
+                mRawSurfaceHeight = naturalDeviceHeight;
+                mSurfaceLeft = 0;
+                mSurfaceTop = 0;
+                mSurfaceRight = mRawSurfaceWidth;
+                mSurfaceBottom = mRawSurfaceHeight;
                 // 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
@@ -774,6 +775,14 @@
                         mRawSurfaceWidth == oldSurfaceWidth &&
                         mRawSurfaceHeight == oldSurfaceHeight && viewportOrientationChanged;
             } else {
+                mRawSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
+                mRawSurfaceHeight =
+                        naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
+                mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
+                mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
+                mSurfaceRight = mSurfaceLeft + naturalLogicalWidth;
+                mSurfaceBottom = mSurfaceTop + naturalLogicalHeight;
+
                 mSurfaceOrientation = mParameters.orientationAware ? mViewport.orientation
                                                                    : DISPLAY_ORIENTATION_0;
             }
@@ -3772,6 +3781,12 @@
     const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale;
     const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale;
 
+    if (isPerWindowInputRotationEnabled()) {
+        return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
+                xScaled >= mPhysicalLeft && xScaled <= (mPhysicalLeft + mPhysicalWidth) &&
+                y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
+                yScaled >= mPhysicalTop && yScaled <= (mPhysicalTop + mPhysicalHeight);
+    }
     return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
             xScaled >= mSurfaceLeft && xScaled <= mSurfaceRight &&
             y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index a56468f..3340672 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -435,6 +435,7 @@
 
     // The surface origin specifies how the surface coordinates should be translated
     // to align with the logical display coordinate space.
+    // TODO(b/188939842): Remove surface coordinates when Per-Window Input Rotation is enabled.
     int32_t mSurfaceLeft;
     int32_t mSurfaceTop;
     int32_t mSurfaceRight;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e9d45b2..7fb2ccf 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -92,13 +92,29 @@
     FakeInputDispatcherPolicy() {}
 
     void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
-        assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_KEY, args.eventTime, args.action,
-                                        args.displayId);
+        assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) {
+            ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_KEY);
+            EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+            const auto& keyEvent = static_cast<const KeyEvent&>(event);
+            EXPECT_EQ(keyEvent.getEventTime(), args.eventTime);
+            EXPECT_EQ(keyEvent.getAction(), args.action);
+        });
     }
 
-    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args) {
-        assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_MOTION, args.eventTime, args.action,
-                                        args.displayId);
+    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) {
+        assertFilterInputEventWasCalledInternal([&](const InputEvent& event) {
+            ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_MOTION);
+            EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+            const auto& motionEvent = static_cast<const MotionEvent&>(event);
+            EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
+            EXPECT_EQ(motionEvent.getAction(), args.action);
+            EXPECT_EQ(motionEvent.getX(0), point.x);
+            EXPECT_EQ(motionEvent.getY(0), point.y);
+            EXPECT_EQ(motionEvent.getRawX(0), point.x);
+            EXPECT_EQ(motionEvent.getRawY(0), point.y);
+        });
     }
 
     void assertFilterInputEventWasNotCalled() {
@@ -425,26 +441,11 @@
         mDropTargetWindowToken = token;
     }
 
-    void assertFilterInputEventWasCalled(int type, nsecs_t eventTime, int32_t action,
-                                         int32_t displayId) {
+    void assertFilterInputEventWasCalledInternal(
+            const std::function<void(const InputEvent&)>& verify) {
         std::scoped_lock lock(mLock);
         ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
-        ASSERT_EQ(mFilteredEvent->getType(), type);
-
-        if (type == AINPUT_EVENT_TYPE_KEY) {
-            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*mFilteredEvent);
-            EXPECT_EQ(keyEvent.getEventTime(), eventTime);
-            EXPECT_EQ(keyEvent.getAction(), action);
-            EXPECT_EQ(keyEvent.getDisplayId(), displayId);
-        } else if (type == AINPUT_EVENT_TYPE_MOTION) {
-            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*mFilteredEvent);
-            EXPECT_EQ(motionEvent.getEventTime(), eventTime);
-            EXPECT_EQ(motionEvent.getAction(), action);
-            EXPECT_EQ(motionEvent.getDisplayId(), displayId);
-        } else {
-            FAIL() << "Unknown type: " << type;
-        }
-
+        verify(*mFilteredEvent);
         mFilteredEvent = nullptr;
     }
 };
@@ -3481,7 +3482,8 @@
 
 class InputFilterTest : public InputDispatcherTest {
 protected:
-    void testNotifyMotion(int32_t displayId, bool expectToBeFiltered) {
+    void testNotifyMotion(int32_t displayId, bool expectToBeFiltered,
+                          const ui::Transform& transform = ui::Transform()) {
         NotifyMotionArgs motionArgs;
 
         motionArgs =
@@ -3492,7 +3494,8 @@
         mDispatcher->notifyMotion(&motionArgs);
         ASSERT_TRUE(mDispatcher->waitForIdle());
         if (expectToBeFiltered) {
-            mFakePolicy->assertFilterInputEventWasCalled(motionArgs);
+            const auto xy = transform.transform(motionArgs.pointerCoords->getXYValue());
+            mFakePolicy->assertFilterInputEventWasCalled(motionArgs, xy);
         } else {
             mFakePolicy->assertFilterInputEventWasNotCalled();
         }
@@ -3550,6 +3553,30 @@
     testNotifyKey(/*expectToBeFiltered*/ false);
 }
 
+// Ensure that MotionEvents sent to the InputFilter through InputListener are converted to the
+// logical display coordinate space.
+TEST_F(InputFilterTest, MotionEvent_UsesLogicalDisplayCoordinates_notifyMotion) {
+    ui::Transform firstDisplayTransform;
+    firstDisplayTransform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
+    ui::Transform secondDisplayTransform;
+    secondDisplayTransform.set({-6.6, -5.5, -4.4, -3.3, -2.2, -1.1, 0, 0, 1});
+
+    std::vector<gui::DisplayInfo> displayInfos(2);
+    displayInfos[0].displayId = ADISPLAY_ID_DEFAULT;
+    displayInfos[0].transform = firstDisplayTransform;
+    displayInfos[1].displayId = SECOND_DISPLAY_ID;
+    displayInfos[1].transform = secondDisplayTransform;
+
+    mDispatcher->onWindowInfosChanged({}, displayInfos);
+
+    // Enable InputFilter
+    mDispatcher->setInputFilterEnabled(true);
+
+    // Ensure the correct transforms are used for the displays.
+    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ true, firstDisplayTransform);
+    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ true, secondDisplayTransform);
+}
+
 class InputFilterInjectionPolicyTest : public InputDispatcherTest {
 protected:
     virtual void SetUp() override {
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 5c430f5..b28c1e2 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -250,14 +250,35 @@
         return mConfig.getDisplayViewportByPort(displayPort);
     }
 
+    void addDisplayViewport(DisplayViewport viewport) {
+        mViewports.push_back(std::move(viewport));
+        mConfig.setDisplayViewports(mViewports);
+    }
+
     void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation,
                             bool isActive, const std::string& uniqueId,
-                            std::optional<uint8_t> physicalPort, ViewportType viewportType) {
-        const DisplayViewport viewport =
-                createDisplayViewport(displayId, width, height, orientation, isActive, uniqueId,
-                                      physicalPort, viewportType);
-        mViewports.push_back(viewport);
-        mConfig.setDisplayViewports(mViewports);
+                            std::optional<uint8_t> physicalPort, ViewportType type) {
+        const bool isRotated =
+                (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270);
+        DisplayViewport v;
+        v.displayId = displayId;
+        v.orientation = orientation;
+        v.logicalLeft = 0;
+        v.logicalTop = 0;
+        v.logicalRight = isRotated ? height : width;
+        v.logicalBottom = isRotated ? width : height;
+        v.physicalLeft = 0;
+        v.physicalTop = 0;
+        v.physicalRight = isRotated ? height : width;
+        v.physicalBottom = isRotated ? width : height;
+        v.deviceWidth = isRotated ? height : width;
+        v.deviceHeight = isRotated ? width : height;
+        v.isActive = isActive;
+        v.uniqueId = uniqueId;
+        v.physicalPort = physicalPort;
+        v.type = type;
+
+        addDisplayViewport(v);
     }
 
     bool updateViewport(const DisplayViewport& viewport) {
@@ -330,32 +351,6 @@
 private:
     uint32_t mNextPointerCaptureSequenceNumber = 0;
 
-    DisplayViewport createDisplayViewport(int32_t displayId, int32_t width, int32_t height,
-                                          int32_t orientation, bool isActive,
-                                          const std::string& uniqueId,
-                                          std::optional<uint8_t> physicalPort, ViewportType type) {
-        bool isRotated = (orientation == DISPLAY_ORIENTATION_90
-                || orientation == DISPLAY_ORIENTATION_270);
-        DisplayViewport v;
-        v.displayId = displayId;
-        v.orientation = orientation;
-        v.logicalLeft = 0;
-        v.logicalTop = 0;
-        v.logicalRight = isRotated ? height : width;
-        v.logicalBottom = isRotated ? width : height;
-        v.physicalLeft = 0;
-        v.physicalTop = 0;
-        v.physicalRight = isRotated ? height : width;
-        v.physicalBottom = isRotated ? width : height;
-        v.deviceWidth = isRotated ? height : width;
-        v.deviceHeight = isRotated ? width : height;
-        v.isActive = isActive;
-        v.uniqueId = uniqueId;
-        v.physicalPort = physicalPort;
-        v.type = type;
-        return v;
-    }
-
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override {
         *outConfig = mConfig;
     }
@@ -6296,6 +6291,172 @@
             toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
 }
 
+// --- TouchDisplayProjectionTest ---
+
+class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
+public:
+    // The values inside DisplayViewport are expected to be pre-rotated. This updates the current
+    // DisplayViewport to pre-rotate the values. The viewport's physical display will be set to the
+    // rotated equivalent of the given un-rotated physical display bounds.
+    void configurePhysicalDisplay(int32_t orientation, Rect naturalPhysicalDisplay) {
+        uint32_t inverseRotationFlags;
+        auto width = DISPLAY_WIDTH;
+        auto height = DISPLAY_HEIGHT;
+        switch (orientation) {
+            case DISPLAY_ORIENTATION_90:
+                inverseRotationFlags = ui::Transform::ROT_270;
+                std::swap(width, height);
+                break;
+            case DISPLAY_ORIENTATION_180:
+                inverseRotationFlags = ui::Transform::ROT_180;
+                break;
+            case DISPLAY_ORIENTATION_270:
+                inverseRotationFlags = ui::Transform::ROT_90;
+                std::swap(width, height);
+                break;
+            case DISPLAY_ORIENTATION_0:
+                inverseRotationFlags = ui::Transform::ROT_0;
+                break;
+            default:
+                FAIL() << "Invalid orientation: " << orientation;
+        }
+
+        const ui::Transform rotation(inverseRotationFlags, width, height);
+        const Rect rotatedPhysicalDisplay = rotation.transform(naturalPhysicalDisplay);
+
+        std::optional<DisplayViewport> internalViewport =
+                *mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+        DisplayViewport& v = *internalViewport;
+        v.displayId = DISPLAY_ID;
+        v.orientation = orientation;
+
+        v.logicalLeft = 0;
+        v.logicalTop = 0;
+        v.logicalRight = 100;
+        v.logicalBottom = 100;
+
+        v.physicalLeft = rotatedPhysicalDisplay.left;
+        v.physicalTop = rotatedPhysicalDisplay.top;
+        v.physicalRight = rotatedPhysicalDisplay.right;
+        v.physicalBottom = rotatedPhysicalDisplay.bottom;
+
+        v.deviceWidth = width;
+        v.deviceHeight = height;
+
+        v.isActive = true;
+        v.uniqueId = UNIQUE_ID;
+        v.type = ViewportType::INTERNAL;
+        mFakePolicy->updateViewport(v);
+        configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    }
+
+    void assertReceivedMove(const Point& point) {
+        NotifyMotionArgs motionArgs;
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+        ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+        ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+        ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], point.x, point.y,
+                                                    1, 0, 0, 0, 0, 0, 0, 0));
+    }
+};
+
+TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+
+    // Configure the DisplayViewport such that the logical display maps to a subsection of
+    // the display panel called the physical display. Here, the physical display is bounded by the
+    // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800.
+    static const Rect kPhysicalDisplay{10, 20, 70, 160};
+    static const std::array<Point, 6> kPointsOutsidePhysicalDisplay{
+            {{-10, -10}, {0, 0}, {5, 100}, {50, 15}, {75, 100}, {50, 165}}};
+
+    for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
+                             DISPLAY_ORIENTATION_270}) {
+        configurePhysicalDisplay(orientation, kPhysicalDisplay);
+
+        // Touches outside the physical display should be ignored, and should not generate any
+        // events. Ensure touches at the following points that lie outside of the physical display
+        // area do not generate any events.
+        for (const auto& point : kPointsOutsidePhysicalDisplay) {
+            processDown(mapper, toRawX(point.x), toRawY(point.y));
+            processSync(mapper);
+            processUp(mapper);
+            processSync(mapper);
+            ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled())
+                    << "Unexpected event generated for touch outside physical display at point: "
+                    << point.x << ", " << point.y;
+        }
+    }
+}
+
+TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+
+    // Configure the DisplayViewport such that the logical display maps to a subsection of
+    // the display panel called the physical display. Here, the physical display is bounded by the
+    // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800.
+    static const Rect kPhysicalDisplay{10, 20, 70, 160};
+
+    for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
+                             DISPLAY_ORIENTATION_270}) {
+        configurePhysicalDisplay(orientation, kPhysicalDisplay);
+
+        // Touches that start outside the physical display should be ignored until it enters the
+        // physical display bounds, at which point it should generate a down event. Start a touch at
+        // the point (5, 100), which is outside the physical display bounds.
+        static const Point kOutsidePoint{5, 100};
+        processDown(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y));
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+        // Move the touch into the physical display area. This should generate a pointer down.
+        processMove(mapper, toRawX(11), toRawY(21));
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+        ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+        ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+        ASSERT_NO_FATAL_FAILURE(
+                assertPointerCoords(motionArgs.pointerCoords[0], 11, 21, 1, 0, 0, 0, 0, 0, 0, 0));
+
+        // Move the touch inside the physical display area. This should generate a pointer move.
+        processMove(mapper, toRawX(69), toRawY(159));
+        processSync(mapper);
+        assertReceivedMove({69, 159});
+
+        // Move outside the physical display area. Since the pointer is already down, this should
+        // now continue generating events.
+        processMove(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y));
+        processSync(mapper);
+        assertReceivedMove(kOutsidePoint);
+
+        // Release. This should generate a pointer up.
+        processUp(mapper);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+        ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+        ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], kOutsidePoint.x,
+                                                    kOutsidePoint.y, 1, 0, 0, 0, 0, 0, 0, 0));
+
+        // Ensure no more events were generated.
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    }
+}
+
 // --- MultiTouchInputMapperTest ---
 
 class MultiTouchInputMapperTest : public TouchInputMapperTest {
@@ -8629,173 +8790,6 @@
     ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
 }
 
-/**
- * Test touch should not work if outside of surface.
- */
-class MultiTouchInputMapperTest_SurfaceRange : public MultiTouchInputMapperTest {
-protected:
-    void halfDisplayToCenterHorizontal(int32_t orientation) {
-        std::optional<DisplayViewport> internalViewport =
-                mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-
-        // Half display to (width/4, 0, width * 3/4, height) to make display has offset.
-        internalViewport->orientation = orientation;
-        if (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270) {
-            internalViewport->logicalLeft = 0;
-            internalViewport->logicalTop = 0;
-            internalViewport->logicalRight = DISPLAY_HEIGHT;
-            internalViewport->logicalBottom = DISPLAY_WIDTH / 2;
-
-            internalViewport->physicalLeft = 0;
-            internalViewport->physicalTop = DISPLAY_WIDTH / 4;
-            internalViewport->physicalRight = DISPLAY_HEIGHT;
-            internalViewport->physicalBottom = DISPLAY_WIDTH * 3 / 4;
-
-            internalViewport->deviceWidth = DISPLAY_HEIGHT;
-            internalViewport->deviceHeight = DISPLAY_WIDTH;
-        } else {
-            internalViewport->logicalLeft = 0;
-            internalViewport->logicalTop = 0;
-            internalViewport->logicalRight = DISPLAY_WIDTH / 2;
-            internalViewport->logicalBottom = DISPLAY_HEIGHT;
-
-            internalViewport->physicalLeft = DISPLAY_WIDTH / 4;
-            internalViewport->physicalTop = 0;
-            internalViewport->physicalRight = DISPLAY_WIDTH * 3 / 4;
-            internalViewport->physicalBottom = DISPLAY_HEIGHT;
-
-            internalViewport->deviceWidth = DISPLAY_WIDTH;
-            internalViewport->deviceHeight = DISPLAY_HEIGHT;
-        }
-
-        mFakePolicy->updateViewport(internalViewport.value());
-        configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-    }
-
-    void processPositionAndVerify(MultiTouchInputMapper& mapper, int32_t xOutside, int32_t yOutside,
-                                  int32_t xInside, int32_t yInside, int32_t xExpected,
-                                  int32_t yExpected) {
-        // touch on outside area should not work.
-        processPosition(mapper, toRawX(xOutside), toRawY(yOutside));
-        processSync(mapper);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-        // touch on inside area should receive the event.
-        NotifyMotionArgs args;
-        processPosition(mapper, toRawX(xInside), toRawY(yInside));
-        processSync(mapper);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-        ASSERT_NEAR(xExpected, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
-        ASSERT_NEAR(yExpected, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
-
-        // Reset.
-        mapper.reset(ARBITRARY_TIME);
-    }
-};
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    // Touch on center of normal display should work.
-    const int32_t x = DISPLAY_WIDTH / 4;
-    const int32_t y = DISPLAY_HEIGHT / 2;
-    processPosition(mapper, toRawX(x), toRawY(y));
-    processSync(mapper);
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], x, y, 1.0f, 0.0f, 0.0f, 0.0f,
-                                                0.0f, 0.0f, 0.0f, 0.0f));
-    // Reset.
-    mapper.reset(ARBITRARY_TIME);
-
-    // Let physical display be different to device, and make surface and physical could be 1:1 in
-    // all four orientations.
-    for (int orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
-                            DISPLAY_ORIENTATION_270}) {
-        halfDisplayToCenterHorizontal(orientation);
-
-        const int32_t xExpected = (x + 1) - (DISPLAY_WIDTH / 4);
-        const int32_t yExpected = y;
-        processPositionAndVerify(mapper, x - 1, y, x + 1, y, xExpected, yExpected);
-    }
-}
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange_90_NotOrientationAware) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    // Half display to (width/4, 0, width * 3/4, height) and rotate 90-degrees.
-    halfDisplayToCenterHorizontal(DISPLAY_ORIENTATION_90);
-
-    const int32_t x = DISPLAY_WIDTH / 4;
-    const int32_t y = DISPLAY_HEIGHT / 2;
-
-    // expect x/y = swap x/y then reverse x.
-    constexpr int32_t xExpected = DISPLAY_HEIGHT - y;
-    constexpr int32_t yExpected = (x + 1) - DISPLAY_WIDTH / 4;
-    processPositionAndVerify(mapper, x - 1, y, x + 1, y, xExpected, yExpected);
-}
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange_270_NotOrientationAware) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    // Half display to (width/4, 0, width * 3/4, height) and rotate 270-degrees.
-    halfDisplayToCenterHorizontal(DISPLAY_ORIENTATION_270);
-
-    const int32_t x = DISPLAY_WIDTH / 4;
-    const int32_t y = DISPLAY_HEIGHT / 2;
-
-    // expect x/y = swap x/y then reverse y.
-    const int32_t xExpected = y;
-    const int32_t yExpected = (DISPLAY_WIDTH * 3 / 4) - (x + 1);
-    processPositionAndVerify(mapper, x - 1, y, x + 1, y, xExpected, yExpected);
-}
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange_Corner_NotOrientationAware) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    const int32_t x = 0;
-    const int32_t y = 0;
-
-    const int32_t xExpected = x;
-    const int32_t yExpected = y;
-    processPositionAndVerify(mapper, x - 1, y, x, y, xExpected, yExpected);
-
-    clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_90);
-    // expect x/y = swap x/y then reverse x.
-    const int32_t xExpected90 = DISPLAY_HEIGHT - 1;
-    const int32_t yExpected90 = x;
-    processPositionAndVerify(mapper, x - 1, y, x, y, xExpected90, yExpected90);
-
-    clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_270);
-    // expect x/y = swap x/y then reverse y.
-    const int32_t xExpected270 = y;
-    const int32_t yExpected270 = DISPLAY_WIDTH - 1;
-    processPositionAndVerify(mapper, x - 1, y, x, y, xExpected270, yExpected270);
-}
-
 TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
     // we need a pointer controller for mouse mode of touchpad (start pointer at 0,0)
     std::shared_ptr<FakePointerController> fakePointerController =