Mouse: Add support to reverse vertical mouse scroll

Bug: 359349392
Bug: 352598211
Test: atest CursorInputMapperUnitTest

Flag: com.android.hardware.input.mouse_reverse_vertical_scrolling

Change-Id: Ib6b20f571b76cb0643bd2b31bcc8daa926306927
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 2f6c6d7..69ae587 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -96,6 +96,9 @@
         // The key remapping has changed.
         KEY_REMAPPING = 1u << 14,
 
+        // The mouse settings changed, this includes mouse reverse vertical scrolling.
+        MOUSE_SETTINGS = 1u << 15,
+
         // All devices must be reopened.
         MUST_REOPEN = 1u << 31,
     };
@@ -252,6 +255,10 @@
     // Keycodes to be remapped.
     std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping;
 
+    // True if the external mouse should have its vertical scrolling reversed, so that rotating the
+    // wheel downwards scrolls the content upwards.
+    bool mouseReverseVerticalScrollingEnabled;
+
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
             defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT),
@@ -282,7 +289,8 @@
             shouldNotifyTouchpadHardwareState(false),
             touchpadRightClickZoneEnabled(false),
             stylusButtonMotionEventsEnabled(true),
-            stylusPointerIconEnabled(false) {}
+            stylusPointerIconEnabled(false),
+            mouseReverseVerticalScrollingEnabled(false) {}
 
     std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueDisplayId)
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 20cdb59..302caa7 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -164,6 +164,10 @@
         changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) {
         configureOnChangePointerSpeed(readerConfig);
     }
+
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::MOUSE_SETTINGS)) {
+        configureOnChangeMouseSettings(readerConfig);
+    }
     return out;
 }
 
@@ -275,7 +279,12 @@
     PointerCoords pointerCoords;
     pointerCoords.clear();
 
-    float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
+    // A negative value represents inverted scrolling direction.
+    // Applies only if the source is a mouse.
+    const bool isMouse =
+            (mSource == AINPUT_SOURCE_MOUSE) || (mSource == AINPUT_SOURCE_MOUSE_RELATIVE);
+    const int scrollingDirection = (mMouseReverseVerticalScrolling && isMouse) ? -1 : 1;
+    float vscroll = scrollingDirection * mCursorScrollAccumulator.getRelativeVWheel();
     float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
     bool scrolled = vscroll != 0 || hscroll != 0;
 
@@ -537,4 +546,8 @@
     bumpGeneration();
 }
 
+void CursorInputMapper::configureOnChangeMouseSettings(const InputReaderConfiguration& config) {
+    mMouseReverseVerticalScrolling = config.mouseReverseVerticalScrollingEnabled;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 3fc370c..403e96d 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -121,6 +121,7 @@
     nsecs_t mLastEventTime;
 
     const bool mEnableNewMousePointerBallistics;
+    bool mMouseReverseVerticalScrolling = false;
 
     explicit CursorInputMapper(InputDeviceContext& deviceContext,
                                const InputReaderConfiguration& readerConfig);
@@ -129,6 +130,7 @@
     void configureOnPointerCapture(const InputReaderConfiguration& config);
     void configureOnChangePointerSpeed(const InputReaderConfiguration& config);
     void configureOnChangeDisplayInfo(const InputReaderConfiguration& config);
+    void configureOnChangeMouseSettings(const InputReaderConfiguration& config);
 
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
 
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index b27d02d..a43b64c 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -882,6 +882,51 @@
                                           WithScroll(0.5f, 0.5f)))));
 }
 
+TEST_F(CursorInputMapperUnitTest, ProcessReversedVerticalScroll) {
+    mReaderConfiguration.mouseReverseVerticalScrollingEnabled = true;
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    // Reversed vertical scrolling only affects the y-axis, expect it to be -1.0f to indicate the
+    // inverted scroll direction.
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(1.0f, -1.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessHighResReversedVerticalScroll) {
+    mReaderConfiguration.mouseReverseVerticalScrollingEnabled = true;
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, -0.5f)))));
+}
+
 /**
  * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
  * pointer acceleration or speed processing should not be applied.