Report pinch gestures
Bug: 251196347
Test: check events received by a custom tester app, and touches shown by
pointer location overlay
Test: atest inputflinger_tests
Change-Id: I249ca6208091e3c4291c5be68c77339bf5f69a5b
diff --git a/include/android/input.h b/include/android/input.h
index e1aac65..d6f9d63 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -778,6 +778,9 @@
* 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.
+ *
+ * These values are relative to the state from the last event, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical events.
*/
AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48,
/**
@@ -791,6 +794,9 @@
*
* - 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.
+ *
+ * These values are relative to the state from the last event, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical events.
*/
AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE = 50,
/**
@@ -799,6 +805,18 @@
* The same as {@link AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE}, but for the Y axis.
*/
AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE = 51,
+ /**
+ * Axis constant: pinch scale factor of a motion event.
+ *
+ * - For a touch pad, reports the change in distance between the fingers when the user is making
+ * a pinch gesture, as a proportion of that distance when the gesture was last reported. For
+ * example, if the fingers were 50 units apart and are now 52 units apart, the scale factor
+ * would be 1.04.
+ *
+ * These values are relative to the state from the last event, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical events.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR = 52,
/**
* Note: This is not an "Axis constant". It does not represent any axis, nor should it be used
@@ -806,7 +824,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_SCROLL_Y_DISTANCE,
+ AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
// 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.
@@ -891,6 +909,13 @@
* why they have a separate constant from two-finger swipes.
*/
AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE = 4,
+ /**
+ * Classification constant: pinch.
+ *
+ * The current event stream represents the user pinching with two fingers on a touchpad. The
+ * gesture is centered around the current cursor position.
+ */
+ AMOTION_EVENT_CLASSIFICATION_PINCH = 5,
};
/**
diff --git a/include/input/Input.h b/include/input/Input.h
index 7e62ac0..cf5474c 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -308,6 +308,11 @@
* have a separate constant from two-finger swipes.
*/
MULTI_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE,
+ /**
+ * The current gesture represents the user pinching with two fingers on a touchpad. The gesture
+ * is centered around the current cursor position.
+ */
+ PINCH = AMOTION_EVENT_CLASSIFICATION_PINCH,
};
/**
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 000775b..c247fdb 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -74,6 +74,8 @@
return "TWO_FINGER_SWIPE";
case MotionClassification::MULTI_FINGER_SWIPE:
return "MULTI_FINGER_SWIPE";
+ case MotionClassification::PINCH:
+ return "PINCH";
}
}
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 8ffd220..7159e27 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -396,7 +396,8 @@
DEFINE_AXIS(GESTURE_X_OFFSET), \
DEFINE_AXIS(GESTURE_Y_OFFSET), \
DEFINE_AXIS(GESTURE_SCROLL_X_DISTANCE), \
- DEFINE_AXIS(GESTURE_SCROLL_Y_DISTANCE)
+ DEFINE_AXIS(GESTURE_SCROLL_Y_DISTANCE), \
+ DEFINE_AXIS(GESTURE_PINCH_SCALE_FACTOR)
// 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 ea0a429..0c93f5c 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -263,11 +263,12 @@
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(b/251196347): add GESTURE_{X,Y}_OFFSET and GESTURE_SCROLL_{X,Y}_DISTANCE.
+// TODO(b/251196347): add GESTURE_{X,Y}_OFFSET, GESTURE_SCROLL_{X,Y}_DISTANCE, and
+// GESTURE_PINCH_SCALE_FACTOR.
// 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_SCROLL_Y_DISTANCE));
+ static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR));
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 575acb0..e8e05b7 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -18,6 +18,7 @@
#include <android/input.h>
#include <linux/input-event-codes.h>
+#include <log/log_main.h>
#include "TouchCursorInputMapperCommon.h"
#include "input/Input.h"
@@ -78,6 +79,8 @@
case kGestureTypeSwipeLift:
case kGestureTypeFourFingerSwipeLift:
return handleMultiFingerSwipeLift(when, readTime);
+ case kGestureTypePinch:
+ return handlePinch(when, readTime, gesture);
default:
// TODO(b/251196347): handle more gesture types.
return {};
@@ -321,6 +324,77 @@
return out;
}
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ std::list<NotifyArgs> out;
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+ // Pinch gesture phases are reported a little differently from others, in that the same details
+ // struct is used for all phases of the gesture, just with different zoom_state values. When
+ // zoom_state is START or END, dz will always be 1, so we don't need to move the pointers in
+ // those cases.
+
+ if (mCurrentClassification != MotionClassification::PINCH) {
+ LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START,
+ "First pinch gesture does not have the START zoom state (%d instead).",
+ gesture.details.pinch.zoom_state);
+ mCurrentClassification = MotionClassification::PINCH;
+ mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition - mPinchFingerSeparation / 2);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition + mPinchFingerSeparation / 2);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ mFakeFingerCoords[1].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));
+ out.push_back(makeMotionArgs(when, readTime,
+ AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ return out;
+ }
+
+ if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) {
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
+ out.push_back(makeMotionArgs(when, readTime,
+ AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
+ mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+ mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+ mCurrentClassification = MotionClassification::NONE;
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
+ return out;
+ }
+
+ mPinchFingerSeparation *= gesture.details.pinch.dz;
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
+ gesture.details.pinch.dz);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition - mPinchFingerSeparation / 2);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition + mPinchFingerSeparation / 2);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+ mButtonState, /* pointerCount= */ 2, mFingerProps.data(),
+ mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+ return out;
+}
+
NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
int32_t actionButton, int32_t buttonState,
uint32_t pointerCount,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 6bea2d9..8e8e3d9 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -57,6 +57,8 @@
uint32_t fingerCount, float dx,
float dy);
[[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> handlePinch(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
int32_t actionButton, int32_t buttonState,
@@ -79,7 +81,11 @@
nsecs_t mDownTime = 0;
MotionClassification mCurrentClassification = MotionClassification::NONE;
+ // Only used when mCurrentClassification is MULTI_FINGER_SWIPE.
uint32_t mSwipeFingerCount = 0;
+ static constexpr float INITIAL_PINCH_SEPARATION_PX = 200.0;
+ // Only used when mCurrentClassification is PINCH.
+ float mPinchFingerSeparation;
static constexpr size_t MAX_FAKE_FINGERS = 4;
// We never need any PointerProperties other than the finger tool type, so we can just keep a
// const array of them.
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 683e78e..b22c741 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -40,7 +40,7 @@
static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
static constexpr int32_t EVENTHUB_ID = 1;
static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
- static constexpr float POINTER_X = 100;
+ static constexpr float POINTER_X = 500;
static constexpr float POINTER_Y = 200;
void SetUp() {
@@ -96,7 +96,7 @@
WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
WithPressure(0.0f)));
- ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
}
TEST_F(GestureConverterTest, Move_Rotated) {
@@ -114,7 +114,7 @@
WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
WithPressure(0.0f)));
- ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110, 205));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
}
TEST_F(GestureConverterTest, ButtonsChange) {
@@ -218,7 +218,7 @@
WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
- ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
// Release the button
Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -574,4 +574,134 @@
WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
}
+TEST_F(GestureConverterTest, Pinch_Inwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(0.8f, EPSILON),
+ WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
+ WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_Outwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.2f, EPSILON),
+ WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
+ WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_ClearsClassificationAndScaleFactorAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+
+ 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),
+ WithGesturePinchScaleFactor(0, EPSILON)));
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 53e4066..b9d9607 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -81,6 +81,14 @@
return argX == x && argY == y;
}
+MATCHER_P3(WithPointerCoords, pointer, x, y, "InputEvent with specified coords for pointer") {
+ const auto argX = arg.pointerCoords[pointer].getX();
+ const auto argY = arg.pointerCoords[pointer].getY();
+ *result_listener << "expected pointer " << pointer << " to have coords (" << x << ", " << y
+ << "), but got (" << argX << ", " << argY << ")";
+ return argX == x && argY == y;
+}
+
MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
@@ -113,6 +121,15 @@
return xDiff <= epsilon && yDiff <= epsilon;
}
+MATCHER_P2(WithGesturePinchScaleFactor, factor, epsilon,
+ "InputEvent with specified touchpad pinch gesture scale factor") {
+ const auto argScaleFactor =
+ arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR);
+ *result_listener << "expected gesture scale factor " << factor << " within " << epsilon
+ << " but got " << argScaleFactor;
+ return fabs(argScaleFactor - factor) <= 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;