Report two-finger touchpad swipes
Bug: 251196347
Test: try out two-finger scroll gestures with an Apple Magic Trackpad 2
Test: atest inputflinger_tests
Change-Id: I9e693350873b90bd50605ab348821224ae1121a8
diff --git a/include/android/input.h b/include/android/input.h
index a0b46de..e1aac65 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -786,6 +786,19 @@
* The same as {@link AMOTION_EVENT_AXIS_GESTURE_X_OFFSET}, but for the Y axis.
*/
AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET = 49,
+ /**
+ * Axis constant: X scroll distance axis of a motion event.
+ *
+ * - For a touch pad, reports the distance that should be scrolled in the X axis as a result of
+ * the user's two-finger scroll gesture, in display pixels.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE = 50,
+ /**
+ * Axis constant: Y scroll distance axis of a motion event.
+ *
+ * The same as {@link AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE}, but for the Y axis.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE = 51,
/**
* Note: This is not an "Axis constant". It does not represent any axis, nor should it be used
@@ -793,7 +806,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_GESTURE_Y_OFFSET,
+ AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE,
// 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 dd7cbb5..8ffd220 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -394,7 +394,9 @@
DEFINE_AXIS(GENERIC_15), \
DEFINE_AXIS(GENERIC_16), \
DEFINE_AXIS(GESTURE_X_OFFSET), \
- DEFINE_AXIS(GESTURE_Y_OFFSET)
+ DEFINE_AXIS(GESTURE_Y_OFFSET), \
+ DEFINE_AXIS(GESTURE_SCROLL_X_DISTANCE), \
+ DEFINE_AXIS(GESTURE_SCROLL_Y_DISTANCE)
// 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 628ce6f..ea0a429 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -263,11 +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.
+// TODO(b/251196347): add GESTURE_{X,Y}_OFFSET and GESTURE_SCROLL_{X,Y}_DISTANCE.
// 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_GESTURE_Y_OFFSET));
+ static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE));
static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) {
common::VideoFrame out;
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index ffc0523..575acb0 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -65,6 +65,10 @@
return {handleMove(when, readTime, gesture)};
case kGestureTypeButtonsChange:
return handleButtonsChange(when, readTime, gesture);
+ case kGestureTypeScroll:
+ return handleScroll(when, readTime, gesture);
+ case kGestureTypeFling:
+ return {handleFling(when, readTime, gesture)};
case kGestureTypeSwipe:
return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
gesture.details.swipe.dy);
@@ -175,6 +179,59 @@
return out;
}
+std::list<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ std::list<NotifyArgs> out;
+ PointerCoords& coords = mFakeFingerCoords[0];
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+ mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ mDownTime = when;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ }
+ float deltaX = gesture.details.scroll.dx;
+ float deltaY = gesture.details.scroll.dy;
+ rotateDelta(mOrientation, &deltaX, &deltaY);
+
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) - deltaX);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) - deltaY);
+ // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET.
+ coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, gesture.details.scroll.dx);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, gesture.details.scroll.dy);
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+ mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+ mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+ return out;
+}
+
+NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
+ // We don't actually want to use the gestures library's fling velocity values (to ensure
+ // consistency between touchscreen and touchpad flings), so we're just using the "start fling"
+ // gestures as a marker for the end of a two-finger scroll gesture.
+ if (gesture.details.fling.fling_state != GESTURES_FLING_START ||
+ mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+ return {};
+ }
+
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
+ NotifyArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition);
+ mCurrentClassification = MotionClassification::NONE;
+ return args;
+}
+
[[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when,
nsecs_t readTime,
uint32_t fingerCount,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index ae5581d..6bea2d9 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -47,9 +47,12 @@
const Gesture& gesture);
private:
- NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+ [[nodiscard]] NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
[[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
const Gesture& gesture);
+ [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+ [[nodiscard]] NotifyArgs handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
[[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
uint32_t fingerCount, float dx,
float dy);
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 1c7ec76..683e78e 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -239,6 +239,117 @@
WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
}
+TEST_F(GestureConverterTest, Scroll) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X, POINTER_Y - 10),
+ WithGestureScrollDistance(0, 10, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X, POINTER_Y - 15),
+ WithGestureScrollDistance(0, 5, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithCoords(POINTER_X, POINTER_Y - 15),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Scroll_Rotated) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X - 10, POINTER_Y),
+ WithGestureScrollDistance(0, 10, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X - 15, POINTER_Y),
+ WithGestureScrollDistance(0, 5, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithCoords(POINTER_X - 15, POINTER_Y),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Scroll_ClearsClassificationAndOffsetsAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionClassification(MotionClassification::NONE),
+ WithGestureScrollDistance(0, 0, EPSILON)));
+}
+
TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAndOffsetsAfterGesture) {
InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 64c2c75..53e4066 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -100,6 +100,19 @@
return xDiff <= epsilon && yDiff <= epsilon;
}
+MATCHER_P3(WithGestureScrollDistance, x, y, epsilon,
+ "InputEvent with specified touchpad gesture scroll distance") {
+ const auto argXDistance =
+ arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE);
+ const auto argYDistance =
+ arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE);
+ const double xDiff = fabs(argXDistance - x);
+ const double yDiff = fabs(argYDistance - y);
+ *result_listener << "expected gesture offset (" << x << ", " << y << ") within " << epsilon
+ << ", but got (" << argXDistance << ", " << argYDistance << ")";
+ return xDiff <= epsilon && yDiff <= epsilon;
+}
+
MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") {
const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
*result_listener << "expected pressure " << pressure << ", but got " << argPressure;
@@ -141,4 +154,9 @@
return arg.eventTime == eventTime;
}
+MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
+ *result_listener << "expected down time " << downTime << ", but got " << arg.downTime;
+ return arg.downTime == downTime;
+}
+
} // namespace android