Support high-resolution scroll

VirtualMouse currently supports -1f to 1f float scroll values,
but it worked for integer values only as input framework supported
only REL_HWHEEL and REL_WHEEL events. With the introduction of
high-res scroll event support (REL_HWHEEL_HI_RES and REL_WHEEL_HI_RES),
granular mouse scrolling can be done, and VirtualMouse scroll API
would work for all float values.

Flag: android.companion.virtualdevice.flags.high_resolution_scroll
Test: atest VirtualMouseTest
Bug: 335160780
Change-Id: I7b13ac1722b6fd31736fe1c0117d4de6e838261a
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index b2f15b4..a052a4e 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -78,7 +78,6 @@
     name: "libinputreader_defaults",
     srcs: [":libinputreader_sources"],
     shared_libs: [
-        "android.companion.virtualdevice.flags-aconfig-cc-host",
         "libbase",
         "libcap",
         "libcrypto",
@@ -116,6 +115,7 @@
         "libinputreader_defaults",
     ],
     shared_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc-host",
         "libinputflinger_base",
     ],
     export_header_lib_headers: [
@@ -141,6 +141,7 @@
     shared_libs: [
         // This should consist only of dependencies from inputflinger. Other dependencies should be
         // in cc_defaults so that they are included in the tests.
+        "android.companion.virtualdevice.flags-aconfig-cc-host",
         "libinputflinger_base",
         "libjsoncpp",
     ],
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index beab6e7..87b72af 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -339,8 +339,8 @@
         int32_t buttonState{};
 
         // Scroll state.
-        int32_t rawVScroll{};
-        int32_t rawHScroll{};
+        float rawVScroll{};
+        float rawHScroll{};
 
         inline void clear() { *this = RawState(); }
     };
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
index f85cab2..06315e2 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
@@ -16,18 +16,29 @@
 
 #include "CursorScrollAccumulator.h"
 
+#include <android_companion_virtualdevice_flags.h>
 #include "EventHub.h"
 #include "InputDevice.h"
 
 namespace android {
 
-CursorScrollAccumulator::CursorScrollAccumulator() : mHaveRelWheel(false), mHaveRelHWheel(false) {
+namespace vd_flags = android::companion::virtualdevice::flags;
+
+CursorScrollAccumulator::CursorScrollAccumulator()
+      : mHaveRelWheel(false),
+        mHaveRelHWheel(false),
+        mHaveRelWheelHighRes(false),
+        mHaveRelHWheelHighRes(false) {
     clearRelativeAxes();
 }
 
 void CursorScrollAccumulator::configure(InputDeviceContext& deviceContext) {
     mHaveRelWheel = deviceContext.hasRelativeAxis(REL_WHEEL);
     mHaveRelHWheel = deviceContext.hasRelativeAxis(REL_HWHEEL);
+    if (vd_flags::high_resolution_scroll()) {
+        mHaveRelWheelHighRes = deviceContext.hasRelativeAxis(REL_WHEEL_HI_RES);
+        mHaveRelHWheelHighRes = deviceContext.hasRelativeAxis(REL_HWHEEL_HI_RES);
+    }
 }
 
 void CursorScrollAccumulator::reset(InputDeviceContext& deviceContext) {
@@ -42,11 +53,31 @@
 void CursorScrollAccumulator::process(const RawEvent& rawEvent) {
     if (rawEvent.type == EV_REL) {
         switch (rawEvent.code) {
+            case REL_WHEEL_HI_RES:
+                if (mHaveRelWheelHighRes) {
+                    mRelWheel = rawEvent.value /
+                            static_cast<float>(kEvdevMouseHighResScrollUnitsPerDetent);
+                }
+                break;
+            case REL_HWHEEL_HI_RES:
+                if (mHaveRelHWheelHighRes) {
+                    mRelHWheel = rawEvent.value /
+                            static_cast<float>(kEvdevMouseHighResScrollUnitsPerDetent);
+                }
+                break;
             case REL_WHEEL:
-                mRelWheel = rawEvent.value;
+                // We should ignore regular scroll events, if we have already have high-res scroll
+                // enabled.
+                if (!mHaveRelWheelHighRes) {
+                    mRelWheel = rawEvent.value;
+                }
                 break;
             case REL_HWHEEL:
-                mRelHWheel = rawEvent.value;
+                // We should ignore regular scroll events, if we have already have high-res scroll
+                // enabled.
+                if (!mHaveRelHWheelHighRes) {
+                    mRelHWheel = rawEvent.value;
+                }
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
index e563620..6990d20 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
@@ -24,7 +24,6 @@
 struct RawEvent;
 
 /* Keeps track of cursor scrolling motions. */
-
 class CursorScrollAccumulator {
 public:
     CursorScrollAccumulator();
@@ -39,17 +38,19 @@
 
     inline int32_t getRelativeX() const { return mRelX; }
     inline int32_t getRelativeY() const { return mRelY; }
-    inline int32_t getRelativeVWheel() const { return mRelWheel; }
-    inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+    inline float getRelativeVWheel() const { return mRelWheel; }
+    inline float getRelativeHWheel() const { return mRelHWheel; }
 
 private:
     bool mHaveRelWheel;
     bool mHaveRelHWheel;
+    bool mHaveRelWheelHighRes;
+    bool mHaveRelHWheelHighRes;
 
     int32_t mRelX;
     int32_t mRelY;
-    int32_t mRelWheel;
-    int32_t mRelHWheel;
+    float mRelWheel;
+    float mRelHWheel;
 
     void clearRelativeAxes();
 };
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 189f117..65e0429 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -109,6 +109,7 @@
         },
     },
     static_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc-test",
         "libflagtest",
         "libgmock",
     ],
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 83074ff..727237f 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -22,6 +22,7 @@
 #include <variant>
 
 #include <android-base/logging.h>
+#include <android_companion_virtualdevice_flags.h>
 #include <com_android_input_flags.h>
 #include <gtest/gtest.h>
 #include <input/DisplayViewport.h>
@@ -127,6 +128,7 @@
 } // namespace
 
 namespace input_flags = com::android::input::flags;
+namespace vd_flags = android::companion::virtualdevice::flags;
 
 /**
  * Unit tests for CursorInputMapper.
@@ -151,6 +153,10 @@
                 .WillRepeatedly(Return(false));
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                 .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
 
         mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
         mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
@@ -194,6 +200,7 @@
 protected:
     void SetUp() override {
         input_flags::enable_new_mouse_pointer_ballistics(false);
+        vd_flags::high_resolution_scroll(false);
         CursorInputMapperUnitTestBase::SetUp();
     }
 };
@@ -840,6 +847,72 @@
                               WithOrientation(0.0f), WithDistance(0.0f)))));
 }
 
+TEST_F(CursorInputMapperUnitTest, ProcessRegularScroll) {
+    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);
+
+    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, ProcessHighResScroll) {
+    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)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, HighResScrollIgnoresRegularScroll) {
+    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_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    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.
@@ -1030,6 +1103,72 @@
                               WithRelativeMotion(10, 20)))));
 }
 
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessRegularScroll) {
+    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);
+
+    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(CursorInputMapperUnitTestWithNewBallistics, ProcessHighResScroll) {
+    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)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, HighResScrollIgnoresRegularScroll) {
+    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_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    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)))));
+}
+
 namespace {
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
index 94cfc32..2b8071b 100644
--- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -122,6 +122,10 @@
                 .WillRepeatedly(Return(true));
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                 .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
     }
 };
 
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 65fb9c6..f643fb1 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -697,6 +697,15 @@
     return argDistance == distance;
 }
 
+MATCHER_P2(WithScroll, scrollX, scrollY, "InputEvent with specified scroll values") {
+    const auto argScrollX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_HSCROLL);
+    const auto argScrollY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_VSCROLL);
+    *result_listener << "expected scroll values " << scrollX << " scroll x " << scrollY
+                     << " scroll y, but got " << argScrollX << " scroll x " << argScrollY
+                     << " scroll y";
+    return argScrollX == scrollX && argScrollY == scrollY;
+}
+
 MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
     const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
     const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);