Report motion offsets for touchpad swipes

Adds two new axes, AXIS_GESTURE_X_OFFSET and AXIS_GESTURE_Y_OFFSET,
which report the movement of swipe gestures on the touchpad as a
fraction of the touchpad's size.

Bug: 246758376
Test: check axis values come through in a test app
Change-Id: I313410053a8db13273bd05a33d3a6a1f75081dae
diff --git a/include/android/input.h b/include/android/input.h
index d906af6..5d19c5c 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -771,6 +771,21 @@
      * The interpretation of a generic axis is device-specific.
      */
     AMOTION_EVENT_AXIS_GENERIC_16 = 47,
+    /**
+     * Axis constant: X gesture offset axis of a motion event.
+     *
+     * - For a touch pad, reports the distance that a swipe gesture has moved in the X axis, as a
+     *   proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a
+     *   swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
+     *   -0.1.
+     */
+    AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48,
+    /**
+     * Axis constant: Y gesture offset axis of a motion event.
+     *
+     * The same as {@link AMOTION_EVENT_AXIS_GESTURE_X_OFFSET}, but for the Y axis.
+     */
+    AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET = 49,
 
     /**
      * Note: This is not an "Axis constant". It does not represent any axis, nor should it be used
@@ -778,7 +793,7 @@
      * to make some computations (like iterating through all possible axes) cleaner.
      * Please update the value accordingly if you add a new axis.
      */
-    AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GENERIC_16,
+    AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET,
 
     // NOTE: If you add a new axis here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 163a2fe..b78fae3 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -391,7 +391,9 @@
     DEFINE_AXIS(GENERIC_13), \
     DEFINE_AXIS(GENERIC_14), \
     DEFINE_AXIS(GENERIC_15), \
-    DEFINE_AXIS(GENERIC_16)
+    DEFINE_AXIS(GENERIC_16), \
+    DEFINE_AXIS(GESTURE_X_OFFSET), \
+    DEFINE_AXIS(GESTURE_Y_OFFSET)
 
 // NOTE: If you add new LEDs here, you must also add them to Input.h
 #define LEDS_SEQUENCE \
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 23b6f57..6db89d4 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -263,8 +263,11 @@
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14);
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15);
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16);
+// TODO(hcutts): add GESTURE_X_OFFSET and GESTURE_Y_OFFSET.
+// If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the
+// static_assert below and add the new axis here, or leave a comment summarizing your decision.
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) ==
-              static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_16));
+              static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET));
 
 static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) {
     common::VideoFrame out;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index da58efd..7f6785e 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -3175,7 +3175,7 @@
         mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
 
         // Add delta for all fingers and calculate a common movement delta.
-        float commonDeltaX = 0, commonDeltaY = 0;
+        int32_t commonDeltaRawX = 0, commonDeltaRawY = 0;
         BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
                               mCurrentCookedState.fingerIdBits.value);
         for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
@@ -3188,11 +3188,11 @@
             delta.dy += cpd.y - lpd.y;
 
             if (first) {
-                commonDeltaX = delta.dx;
-                commonDeltaY = delta.dy;
+                commonDeltaRawX = delta.dx;
+                commonDeltaRawY = delta.dy;
             } else {
-                commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx);
-                commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy);
+                commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx);
+                commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy);
             }
         }
 
@@ -3298,7 +3298,7 @@
         // Move the reference points based on the overall group motion of the fingers
         // except in PRESS mode while waiting for a transition to occur.
         if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS &&
-            (commonDeltaX || commonDeltaY)) {
+            (commonDeltaRawX || commonDeltaRawY)) {
             for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
                 uint32_t id = idBits.clearFirstMarkedBit();
                 PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
@@ -3306,11 +3306,11 @@
                 delta.dy = 0;
             }
 
-            mPointerGesture.referenceTouchX += commonDeltaX;
-            mPointerGesture.referenceTouchY += commonDeltaY;
+            mPointerGesture.referenceTouchX += commonDeltaRawX;
+            mPointerGesture.referenceTouchY += commonDeltaRawY;
 
-            commonDeltaX *= mPointerXMovementScale;
-            commonDeltaY *= mPointerYMovementScale;
+            float commonDeltaX = commonDeltaRawX * mPointerXMovementScale;
+            float commonDeltaY = commonDeltaRawY * mPointerYMovementScale;
 
             rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
             mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
@@ -3341,6 +3341,16 @@
             mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
                                                                  mPointerGesture.referenceGestureY);
             mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+            if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+                float xOffset = static_cast<float>(commonDeltaRawX) /
+                        (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
+                float yOffset = static_cast<float>(commonDeltaRawY) /
+                        (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue);
+                mPointerGesture.currentGestureCoords[0]
+                        .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
+                mPointerGesture.currentGestureCoords[0]
+                        .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
+            }
         } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
             // FREEFORM mode.
             ALOGD_IF(DEBUG_GESTURES,
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index dded6a1..1e26265 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -10085,6 +10085,52 @@
                                                 0, 0, 0, 0, 0));
 }
 
+TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) {
+    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    NotifyMotionArgs motionArgs;
+
+    // Place two fingers down.
+    int32_t x1 = 100, y1 = 125, x2 = 550, y2 = 125;
+
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, x1, y1);
+    processMTSync(mapper);
+    processId(mapper, SECOND_TRACKING_ID);
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(1U, motionArgs.pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(MotionClassification::NONE, motionArgs.classification);
+    ASSERT_EQ(0, motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET));
+    ASSERT_EQ(0, motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET));
+
+    // Move the two fingers down and to the left.
+    int32_t movingDistance = 200;
+    x1 -= movingDistance;
+    y1 += movingDistance;
+    x2 -= movingDistance;
+    y2 += movingDistance;
+
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, x1, y1);
+    processMTSync(mapper);
+    processId(mapper, SECOND_TRACKING_ID);
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(1U, motionArgs.pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification);
+    ASSERT_LT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET), 0);
+    ASSERT_GT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET), 0);
+}
+
 // --- JoystickInputMapperTest ---
 
 class JoystickInputMapperTest : public InputMapperTest {