Add a minimum physical threshold for freeform gesture on touchpad

Currently, TouchInputMapper uses the product of the size of the touchpad
and a ratio (currently 0.25) to determine a two finger gesture is a
freeform gesture or a swipe, that when the distance of two pointers is
greater than the calculated value, it is a freeform gesture, otherwise,
it is a swipe gesture. However, on a really small touch pad, such as
Sony Dualshock4's, due to the common size of human fingers, almost all
two finger gestures are decided to be freeform. Adding a minimun
physical value for the freeform gesture, 30 mm, can solve the problem.

Bug: 197146318
Test: manual test
Change-Id: Ia4ff521cb2f47b587fa55a5d753b90d023a648ef
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 901b4de..75a3c28 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -373,8 +373,12 @@
         mConfig.defaultPointerDisplayId = pointerDisplayId;
     }
 
+    void setPointerGestureEnabled(bool enabled) { mConfig.pointerGesturesEnabled = enabled; }
+
     float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; }
 
+    float getPointerGestureZoomSpeedRatio() { return mConfig.pointerGestureZoomSpeedRatio; }
+
     void setVelocityControlParams(const VelocityControlParameters& params) {
         mConfig.pointerVelocityControlParameters = params;
         mConfig.wheelVelocityControlParameters = params;
@@ -9464,6 +9468,258 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
+class MultiTouchPointerModeTest : public MultiTouchInputMapperTest {
+protected:
+    float mPointerMovementScale;
+    float mPointerXZoomScale;
+    void preparePointerMode(int xAxisResolution, int yAxisResolution) {
+        addConfigurationProperty("touch.deviceType", "pointer");
+        std::shared_ptr<FakePointerController> fakePointerController =
+                std::make_shared<FakePointerController>();
+        fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+        fakePointerController->setPosition(0, 0);
+        fakePointerController->setButtonState(0);
+        prepareDisplay(DISPLAY_ORIENTATION_0);
+
+        prepareAxes(POSITION);
+        prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution);
+        // In order to enable swipe and freeform gesture in pointer mode, pointer capture
+        // needs to be disabled, and the pointer gesture needs to be enabled.
+        mFakePolicy->setPointerCapture(false);
+        mFakePolicy->setPointerGestureEnabled(true);
+        mFakePolicy->setPointerController(fakePointerController);
+
+        float rawDiagonal = hypotf(RAW_X_MAX - RAW_X_MIN, RAW_Y_MAX - RAW_Y_MIN);
+        float displayDiagonal = hypotf(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mPointerMovementScale =
+                mFakePolicy->getPointerGestureMovementSpeedRatio() * displayDiagonal / rawDiagonal;
+        mPointerXZoomScale =
+                mFakePolicy->getPointerGestureZoomSpeedRatio() * displayDiagonal / rawDiagonal;
+    }
+
+    void prepareAbsoluteAxisResolution(int xAxisResolution, int yAxisResolution) {
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX,
+                                       /*flat*/ 0,
+                                       /*fuzz*/ 0, /*resolution*/ xAxisResolution);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX,
+                                       /*flat*/ 0,
+                                       /*fuzz*/ 0, /*resolution*/ yAxisResolution);
+    }
+};
+
+/**
+ * Two fingers down on a pointer mode touch pad. The width
+ * of the two finger is larger than 1/4 of the touch pack diagnal length. However, it
+ * is smaller than the fixed min physical length 30mm. Two fingers' distance must
+ * be greater than the both value to be freeform gesture, so that after two
+ * fingers start to move downwards, the gesture should be swipe.
+ */
+TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) {
+    // The min freeform gesture width is 25units/mm x 30mm = 750
+    // which is greater than fraction of the diagnal length of the touchpad (349).
+    // Thus, MaxSwipWidth is 750.
+    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    NotifyMotionArgs motionArgs;
+
+    // Two fingers down at once.
+    // The two fingers are 450 units apart, expects the current gesture to be PRESS
+    // Pointer's initial position is used the [0,0] coordinate.
+    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(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_NO_FATAL_FAILURE(
+            assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0));
+
+    // It should be recognized as a SWIPE gesture when two fingers start to move down,
+    // that there should be 1 pointer.
+    int32_t movingDistance = 200;
+    y1 += 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(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0,
+                                                movingDistance * mPointerMovementScale, 1, 0, 0, 0,
+                                                0, 0, 0, 0));
+}
+
+/**
+ * Two fingers down on a pointer mode touch pad. The width of the two finger is larger
+ * than the minimum freeform gesture width, 30mm. However, it is smaller than 1/4 of
+ * the touch pack diagnal length. Two fingers' distance must be greater than the both
+ * value to be freeform gesture, so that after two fingers start to move downwards,
+ * the gesture should be swipe.
+ */
+TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) {
+    // The min freeform gesture width is 5units/mm x 30mm = 150
+    // which is greater than fraction of the diagnal length of the touchpad (349).
+    // Thus, MaxSwipWidth is the fraction of the diagnal length, 349.
+    preparePointerMode(5 /*xResolution*/, 5 /*yResolution*/);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    NotifyMotionArgs motionArgs;
+
+    // Two fingers down at once.
+    // The two fingers are 250 units apart, expects the current gesture to be PRESS
+    // Pointer's initial position is used the [0,0] coordinate.
+    int32_t x1 = 100, y1 = 125, x2 = 350, 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(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_NO_FATAL_FAILURE(
+            assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0));
+
+    // It should be recognized as a SWIPE gesture when two fingers start to move down,
+    // and there should be 1 pointer.
+    int32_t movingDistance = 200;
+    y1 += 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(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    // New coordinate is the scaled relative coordinate from the initial coordinate.
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0,
+                                                movingDistance * mPointerMovementScale, 1, 0, 0, 0,
+                                                0, 0, 0, 0));
+}
+
+/**
+ * Touch the touch pad with two fingers with a distance wider than the minimum freeform
+ * gesture width and 1/4 of the diagnal length of the touchpad. Expect to receive
+ * freeform gestures after two fingers start to move downwards.
+ */
+TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) {
+    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+
+    // Two fingers down at once. Wider than the max swipe width.
+    // The gesture is expected to be PRESS, then transformed to FREEFORM
+    int32_t x1 = 100, y1 = 125, x2 = 900, 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(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    // One pointer for PRESS, and its coordinate is used as the origin for pointer coordinates.
+    ASSERT_NO_FATAL_FAILURE(
+            assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0));
+
+    int32_t movingDistance = 200;
+
+    // Move two fingers down, expect a cancel event because gesture is changing to freeform,
+    // then two down events for two pointers.
+    y1 += 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));
+    // The previous PRESS gesture is cancelled, because it is transformed to freeform
+    ASSERT_EQ(1U, motionArgs.pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(1U, motionArgs.pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_EQ(2U, motionArgs.pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN, motionArgs.action & AMOTION_EVENT_ACTION_MASK);
+    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    // Two pointers' scaled relative coordinates from their initial centroid.
+    // Initial y coordinates are 0 as y1 and y2 have the same value.
+    float cookedX1 = (x1 - x2) / 2 * mPointerXZoomScale;
+    float cookedX2 = (x2 - x1) / 2 * mPointerXZoomScale;
+    // When pointers move,  the new coordinates equal to the initial coordinates plus
+    // scaled moving distance.
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1,
+                                                movingDistance * mPointerMovementScale, 1, 0, 0, 0,
+                                                0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], cookedX2,
+                                                movingDistance * mPointerMovementScale, 1, 0, 0, 0,
+                                                0, 0, 0, 0));
+
+    // Move two fingers down again, expect one MOVE motion event.
+    y1 += 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(2U, motionArgs.pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1,
+                                                movingDistance * 2 * mPointerMovementScale, 1, 0, 0,
+                                                0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], cookedX2,
+                                                movingDistance * 2 * mPointerMovementScale, 1, 0, 0,
+                                                0, 0, 0, 0, 0));
+}
+
 // --- JoystickInputMapperTest ---
 
 class JoystickInputMapperTest : public InputMapperTest {