Add orientation configuration for touchscreen devices

There are many instances where devices have display panels installed in
a different orientation than the device's default orientation. In these
cases, there is an SF flag to change the device's default orientation,
using ro.surface_flinger.primary_display_orientation. However, when that
is used, the touchscreens don't work in the expected orientation.

This CL adds the ability to configure the orientation of a touchscreen
device using an Input Device Configuration (IDC) file.

Bug: 196357204
Test: atest inputflinger_tests
Test: manual: ensure touch works as expected with
ro.sf.primary_display_orientation and touch.orientation set to 90

Change-Id: I1267f2b3fdb6faaef2923789bcaf5a6141971431
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index af02844..f6fa7a1 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -470,6 +470,23 @@
     getDeviceContext().getConfiguration().tryGetProperty(String8("touch.orientationAware"),
                                                          mParameters.orientationAware);
 
+    mParameters.orientation = Parameters::Orientation::ORIENTATION_0;
+    String8 orientationString;
+    if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.orientation"),
+                                                             orientationString)) {
+        if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) {
+            ALOGW("The configuration 'touch.orientation' is only supported for touchscreens.");
+        } else if (orientationString == "ORIENTATION_90") {
+            mParameters.orientation = Parameters::Orientation::ORIENTATION_90;
+        } else if (orientationString == "ORIENTATION_180") {
+            mParameters.orientation = Parameters::Orientation::ORIENTATION_180;
+        } else if (orientationString == "ORIENTATION_270") {
+            mParameters.orientation = Parameters::Orientation::ORIENTATION_270;
+        } else if (orientationString != "ORIENTATION_0") {
+            ALOGW("Invalid value for touch.orientation: '%s'", orientationString.string());
+        }
+    }
+
     mParameters.hasAssociatedDisplay = false;
     mParameters.associatedDisplayIsExternal = false;
     if (mParameters.orientationAware ||
@@ -508,6 +525,7 @@
                          toString(mParameters.associatedDisplayIsExternal),
                          mParameters.uniqueDisplayId.c_str());
     dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
+    dump += INDENT4 "Orientation: " + NamedEnum::string(mParameters.orientation) + "\n";
 }
 
 void TouchInputMapper::configureRawPointerAxes() {
@@ -669,7 +687,13 @@
             int32_t naturalPhysicalWidth, naturalPhysicalHeight;
             int32_t naturalPhysicalLeft, naturalPhysicalTop;
             int32_t naturalDeviceWidth, naturalDeviceHeight;
-            switch (mViewport.orientation) {
+
+            // Apply the inverse of the input device orientation so that the surface is configured
+            // in the same orientation as the device. The input device orientation will be
+            // re-applied to mSurfaceOrientation.
+            const int32_t naturalSurfaceOrientation =
+                    (mViewport.orientation - static_cast<int32_t>(mParameters.orientation) + 4) % 4;
+            switch (naturalSurfaceOrientation) {
                 case DISPLAY_ORIENTATION_90:
                     naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
                     naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
@@ -752,6 +776,10 @@
                 mSurfaceOrientation = mParameters.orientationAware ? mViewport.orientation
                                                                    : DISPLAY_ORIENTATION_0;
             }
+
+            // Apply the input device orientation for the device.
+            mSurfaceOrientation =
+                    (mSurfaceOrientation + static_cast<int32_t>(mParameters.orientation)) % 4;
         } else {
             mPhysicalWidth = rawWidth;
             mPhysicalHeight = rawHeight;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 920f842..e104220 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -204,6 +204,15 @@
         bool hasAssociatedDisplay;
         bool associatedDisplayIsExternal;
         bool orientationAware;
+
+        enum class Orientation : int32_t {
+            ORIENTATION_0 = DISPLAY_ORIENTATION_0,
+            ORIENTATION_90 = DISPLAY_ORIENTATION_90,
+            ORIENTATION_180 = DISPLAY_ORIENTATION_180,
+            ORIENTATION_270 = DISPLAY_ORIENTATION_270,
+        };
+        Orientation orientation;
+
         bool hasButtonUnderPad;
         std::string uniqueDisplayId;
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 43f14bd..91da0d5 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -4659,6 +4659,8 @@
     void prepareLocationCalibration();
     int32_t toRawX(float displayX);
     int32_t toRawY(float displayY);
+    int32_t toRotatedRawX(float displayX);
+    int32_t toRotatedRawY(float displayY);
     float toCookedX(float rawX, float rawY);
     float toCookedY(float rawX, float rawY);
     float toDisplayX(int32_t rawX);
@@ -4741,6 +4743,14 @@
     return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT + RAW_Y_MIN);
 }
 
+int32_t TouchInputMapperTest::toRotatedRawX(float displayX) {
+    return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_HEIGHT + RAW_X_MIN);
+}
+
+int32_t TouchInputMapperTest::toRotatedRawY(float displayY) {
+    return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_WIDTH + RAW_Y_MIN);
+}
+
 float TouchInputMapperTest::toCookedX(float rawX, float rawY) {
     AFFINE_TRANSFORM.applyTo(rawX, rawY);
     return rawX;
@@ -5487,6 +5497,172 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
 }
 
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation0_RotatesMotions) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.orientationAware", "1");
+    addConfigurationProperty("touch.orientation", "ORIENTATION_0");
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    NotifyMotionArgs args;
+
+    // Orientation 0.
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation90_RotatesMotions) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.orientationAware", "1");
+    addConfigurationProperty("touch.orientation", "ORIENTATION_90");
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    NotifyMotionArgs args;
+
+    // Orientation 90.
+    processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50));
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation180_RotatesMotions) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.orientationAware", "1");
+    addConfigurationProperty("touch.orientation", "ORIENTATION_180");
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    NotifyMotionArgs args;
+
+    // Orientation 180.
+    processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN);
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation270_RotatesMotions) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.orientationAware", "1");
+    addConfigurationProperty("touch.orientation", "ORIENTATION_270");
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    NotifyMotionArgs args;
+
+    // Orientation 270.
+    processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN);
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotionWithDisplay) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
+    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");
+    addConfigurationProperty("touch.orientation", "ORIENTATION_90");
+    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    NotifyMotionArgs args;
+
+    // Orientation 90, Rotation 0.
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50));
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+
+    // Orientation 90, Rotation 90.
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_90);
+    processDown(mapper, toRotatedRawX(50), toRotatedRawY(75));
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+
+    // Orientation 90, Rotation 180.
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_180);
+    processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN);
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+
+    // Orientation 90, Rotation 270.
+    clearViewports();
+    prepareDisplay(DISPLAY_ORIENTATION_270);
+    processDown(mapper, RAW_X_MAX - toRotatedRawX(50) + RAW_X_MIN,
+                RAW_Y_MAX - toRotatedRawY(75) + RAW_Y_MIN);
+    processSync(mapper);
+
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+}
+
 TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);