Mouse: Add support to swap mouse primary button

Bug: 359349392
Bug: 352598211
Test: atest CursorInputMapperUnitTest

Flag: com.android.hardware.input.mouse_swap_primary_button

Change-Id: I37d70bc0ce37fc0832e6733a0a1c8936cab36ab1
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 69ae587..756a29b 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -96,7 +96,8 @@
         // The key remapping has changed.
         KEY_REMAPPING = 1u << 14,
 
-        // The mouse settings changed, this includes mouse reverse vertical scrolling.
+        // The mouse settings changed, this includes mouse reverse vertical scrolling and swap
+        // primary button.
         MOUSE_SETTINGS = 1u << 15,
 
         // All devices must be reopened.
@@ -259,6 +260,11 @@
     // wheel downwards scrolls the content upwards.
     bool mouseReverseVerticalScrollingEnabled;
 
+    // True if the connected mouse should have its primary button (default: left click) swapped,
+    // so that the right click will be the primary action button and the left click will be the
+    // secondary action.
+    bool mouseSwapPrimaryButtonEnabled;
+
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
             defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT),
@@ -290,7 +296,8 @@
             touchpadRightClickZoneEnabled(false),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false),
-            mouseReverseVerticalScrollingEnabled(false) {}
+            mouseReverseVerticalScrollingEnabled(false),
+            mouseSwapPrimaryButtonEnabled(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 302caa7..630bd9b 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -548,6 +548,7 @@
 
 void CursorInputMapper::configureOnChangeMouseSettings(const InputReaderConfiguration& config) {
     mMouseReverseVerticalScrolling = config.mouseReverseVerticalScrollingEnabled;
+    mCursorButtonAccumulator.setSwapLeftRightButtons(config.mouseSwapPrimaryButtonEnabled);
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
index 9e722d4..456562c 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
@@ -47,6 +47,10 @@
     mBtnTask = 0;
 }
 
+void CursorButtonAccumulator::setSwapLeftRightButtons(bool shouldSwap) {
+    mSwapLeftRightButtons = shouldSwap;
+}
+
 void CursorButtonAccumulator::process(const RawEvent& rawEvent) {
     if (rawEvent.type == EV_KEY) {
         switch (rawEvent.code) {
@@ -81,10 +85,12 @@
 uint32_t CursorButtonAccumulator::getButtonState() const {
     uint32_t result = 0;
     if (mBtnLeft) {
-        result |= AMOTION_EVENT_BUTTON_PRIMARY;
+        result |= mSwapLeftRightButtons ? AMOTION_EVENT_BUTTON_SECONDARY
+                                        : AMOTION_EVENT_BUTTON_PRIMARY;
     }
     if (mBtnRight) {
-        result |= AMOTION_EVENT_BUTTON_SECONDARY;
+        result |= mSwapLeftRightButtons ? AMOTION_EVENT_BUTTON_PRIMARY
+                                        : AMOTION_EVENT_BUTTON_SECONDARY;
     }
     if (mBtnMiddle) {
         result |= AMOTION_EVENT_BUTTON_TERTIARY;
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
index 256b2bb..6990030 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
@@ -41,6 +41,8 @@
     inline bool isExtraPressed() const { return mBtnExtra; }
     inline bool isTaskPressed() const { return mBtnTask; }
 
+    void setSwapLeftRightButtons(bool shouldSwap);
+
 private:
     bool mBtnLeft;
     bool mBtnRight;
@@ -51,6 +53,8 @@
     bool mBtnExtra;
     bool mBtnTask;
 
+    bool mSwapLeftRightButtons = false;
+
     void clearButtons();
 };
 
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index a43b64c..1762a45 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -205,9 +205,14 @@
     args.clear();
     args += process(EV_KEY, BTN_LEFT, 1);
     args += process(EV_SYN, SYN_REPORT, 0);
+
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))));
 
     // Move some more.
     args.clear();
@@ -221,9 +226,76 @@
     args += process(EV_KEY, BTN_LEFT, 0);
     args += process(EV_SYN, SYN_REPORT, 0);
     ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+}
+
+/**
+ * Test that enabling mouse swap primary button will have the left click result in a
+ * `SECONDARY_BUTTON` event and a right click will result in a `PRIMARY_BUTTON` event.
+ */
+TEST_F(CursorInputMapperUnitTest, SwappedPrimaryButtonPress) {
+    mReaderConfiguration.mouseSwapPrimaryButtonEnabled = true;
+    createMapper();
+    std::list<NotifyArgs> args;
+
+    // Now click the left mouse button , expect a `SECONDARY_BUTTON` button state.
+    args.clear();
+    args += process(EV_KEY, BTN_LEFT, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))));
+
+    // Release the left button.
+    args.clear();
+    args += process(EV_KEY, BTN_LEFT, 0);
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY))),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+    // Now click the right mouse button , expect a `PRIMARY_BUTTON` button state.
+    args.clear();
+    args += process(EV_KEY, BTN_RIGHT, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))));
+
+    // Release the right button.
+    args.clear();
+    args += process(EV_KEY, BTN_RIGHT, 0);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
 }
 
 /**