diff options
-rw-r--r-- | include/android/input.h | 8 | ||||
-rw-r--r-- | include/input/Input.h | 6 | ||||
-rw-r--r-- | libs/input/Input.cpp | 2 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/TouchpadInputMapper.cpp | 2 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/gestures/GestureConverter.cpp | 142 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/gestures/GestureConverter.h | 26 | ||||
-rw-r--r-- | services/inputflinger/tests/GestureConverter_test.cpp | 256 | ||||
-rw-r--r-- | services/inputflinger/tests/TestInputListenerMatchers.h | 25 |
8 files changed, 438 insertions, 29 deletions
diff --git a/include/android/input.h b/include/android/input.h index 5d19c5cb13..a0b46de260 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -870,6 +870,14 @@ enum AMotionClassification : uint32_t { * The current event stream represents the user swiping with two fingers on a touchpad. */ AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE = 3, + /** + * Classification constant: multi-finger swipe. + * + * The current event stream represents the user swiping with three or more fingers on a + * touchpad. Unlike two-finger swipes, these are only to be handled by the system UI, which is + * why they have a separate constant from two-finger swipes. + */ + AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE = 4, }; /** diff --git a/include/input/Input.h b/include/input/Input.h index 1a35196036..7e62ac005e 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -302,6 +302,12 @@ enum class MotionClassification : uint8_t { * The current gesture represents the user swiping with two fingers on a touchpad. */ TWO_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE, + /** + * The current gesture represents the user swiping with three or more fingers on a touchpad. + * Unlike two-finger swipes, these are only to be handled by the system UI, which is why they + * have a separate constant from two-finger swipes. + */ + MULTI_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE, }; /** diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index d893cb99ba..000775b42a 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -72,6 +72,8 @@ const char* motionClassificationToString(MotionClassification classification) { return "DEEP_PRESS"; case MotionClassification::TWO_FINGER_SWIPE: return "TWO_FINGER_SWIPE"; + case MotionClassification::MULTI_FINGER_SWIPE: + return "MULTI_FINGER_SWIPE"; } } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 23d7fdf91d..3b51be83ab 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -92,7 +92,7 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), mPointerController(getContext()->getPointerController(getDeviceId())), mStateConverter(deviceContext), - mGestureConverter(*getContext(), getDeviceId()) { + mGestureConverter(*getContext(), deviceContext, getDeviceId()) { mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); // Even though we don't explicitly delete copy/move semantics, it's safe to diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 8600065395..ffc0523e97 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -17,6 +17,7 @@ #include "gestures/GestureConverter.h" #include <android/input.h> +#include <linux/input-event-codes.h> #include "TouchCursorInputMapperCommon.h" #include "input/Input.h" @@ -44,10 +45,14 @@ uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) { } // namespace -GestureConverter::GestureConverter(InputReaderContext& readerContext, int32_t deviceId) +GestureConverter::GestureConverter(InputReaderContext& readerContext, + const InputDeviceContext& deviceContext, int32_t deviceId) : mDeviceId(deviceId), mReaderContext(readerContext), - mPointerController(readerContext.getPointerController(deviceId)) {} + mPointerController(readerContext.getPointerController(deviceId)) { + deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo); + deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); +} void GestureConverter::reset() { mButtonState = 0; @@ -60,6 +65,15 @@ std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t read return {handleMove(when, readTime, gesture)}; case kGestureTypeButtonsChange: return handleButtonsChange(when, readTime, gesture); + case kGestureTypeSwipe: + return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx, + gesture.details.swipe.dy); + case kGestureTypeFourFingerSwipe: + return handleMultiFingerSwipe(when, readTime, 4, gesture.details.four_finger_swipe.dx, + gesture.details.four_finger_swipe.dy); + case kGestureTypeSwipeLift: + case kGestureTypeFourFingerSwipeLift: + return handleMultiFingerSwipeLift(when, readTime); default: // TODO(b/251196347): handle more gesture types. return {}; @@ -67,11 +81,6 @@ std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t read } NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { - PointerProperties props; - props.clear(); - props.id = 0; - props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - float deltaX = gesture.details.move.dx; float deltaY = gesture.details.move.dy; rotateDelta(mOrientation, &deltaX, &deltaY); @@ -93,7 +102,8 @@ NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Ge const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE; return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState, - /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition); + /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition); } std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime, @@ -103,11 +113,6 @@ std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - PointerProperties props; - props.clear(); - props.id = 0; - props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - float xCursorPosition, yCursorPosition; mPointerController->getPosition(&xCursorPosition, &yCursorPosition); @@ -131,15 +136,16 @@ std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ newButtonState |= actionButton; pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, newButtonState, - /* pointerCount= */ 1, &props, &coords, - xCursorPosition, yCursorPosition)); + /* pointerCount= */ 1, mFingerProps.data(), + &coords, xCursorPosition, yCursorPosition)); } } if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) { mDownTime = when; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1, - &props, &coords, xCursorPosition, yCursorPosition)); + mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition)); } out.splice(out.end(), pressEvents); @@ -155,19 +161,109 @@ std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ newButtonState &= ~actionButton; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, newButtonState, /* pointerCount= */ 1, - &props, &coords, xCursorPosition, yCursorPosition)); + mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition)); } } if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) { coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, - newButtonState, /* pointerCount= */ 1, &props, &coords, - xCursorPosition, yCursorPosition)); + newButtonState, /* pointerCount= */ 1, mFingerProps.data(), + &coords, xCursorPosition, yCursorPosition)); } mButtonState = newButtonState; return out; } +[[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when, + nsecs_t readTime, + uint32_t fingerCount, + float dx, float dy) { + std::list<NotifyArgs> out = {}; + float xCursorPosition, yCursorPosition; + mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) { + // If the user changes the number of fingers mid-way through a swipe (e.g. they start with + // three and then put a fourth finger down), the gesture library will treat it as two + // separate swipes with an appropriate lift event between them, so we don't have to worry + // about the finger count changing mid-swipe. + mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE; + mSwipeFingerCount = fingerCount; + + constexpr float FAKE_FINGER_SPACING = 100; + float xCoord = xCursorPosition - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2; + for (size_t i = 0; i < mSwipeFingerCount; i++) { + PointerCoords& coords = mFakeFingerCoords[i]; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCoord); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + xCoord += FAKE_FINGER_SPACING; + } + + mDownTime = when; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, + /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + for (size_t i = 1; i < mSwipeFingerCount; i++) { + out.push_back(makeMotionArgs(when, readTime, + AMOTION_EVENT_ACTION_POINTER_DOWN | + (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + /* actionButton= */ 0, mButtonState, + /* pointerCount= */ i + 1, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + } + } + for (size_t i = 0; i < mSwipeFingerCount; i++) { + PointerCoords& coords = mFakeFingerCoords[i]; + coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) + dx); + // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate + // the Y values. + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) - dy); + } + float xOffset = dx / (mXAxisInfo.maxValue - mXAxisInfo.minValue); + // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y + // values. + float yOffset = -dy / (mYAxisInfo.maxValue - mYAxisInfo.minValue); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, + mButtonState, /* pointerCount= */ mSwipeFingerCount, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + return out; +} + +[[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipeLift(nsecs_t when, + nsecs_t readTime) { + std::list<NotifyArgs> out = {}; + if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) { + return out; + } + float xCursorPosition, yCursorPosition; + mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0); + + for (size_t i = mSwipeFingerCount; i > 1; i--) { + out.push_back(makeMotionArgs(when, readTime, + AMOTION_EVENT_ACTION_POINTER_UP | + ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + /* actionButton= */ 0, mButtonState, /* pointerCount= */ i, + 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; + mSwipeFingerCount = 0; + return out; +} + NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, uint32_t pointerCount, @@ -181,10 +277,10 @@ NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime mPointerController->getDisplayId(), /* policyFlags= */ 0, action, /* actionButton= */ actionButton, /* flags= */ 0, mReaderContext.getGlobalMetaState(), buttonState, - MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, - pointerProperties, pointerCoords, - /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition, - yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {}); + mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, + pointerProperties, pointerCoords, /* xPrecision= */ 1.0f, + /* yPrecision= */ 1.0f, xCursorPosition, yCursorPosition, + /* downTime= */ mDownTime, /* videoFrames= */ {}); } } // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index d6a51d2da8..ae5581d3ec 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -16,12 +16,15 @@ #pragma once +#include <array> #include <list> #include <memory> #include <PointerControllerInterface.h> #include <utils/Timers.h> +#include "EventHub.h" +#include "InputDevice.h" #include "InputReaderContext.h" #include "NotifyArgs.h" #include "ui/Rotation.h" @@ -34,7 +37,8 @@ namespace android { // PointerController calls. class GestureConverter { public: - GestureConverter(InputReaderContext& readerContext, int32_t deviceId); + GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext, + int32_t deviceId); void setOrientation(ui::Rotation orientation) { mOrientation = orientation; } void reset(); @@ -46,6 +50,10 @@ private: 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> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime, + uint32_t fingerCount, float dx, + float dy); + [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime); NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, @@ -59,10 +67,26 @@ private: std::shared_ptr<PointerControllerInterface> mPointerController; ui::Rotation mOrientation = ui::ROTATION_0; + RawAbsoluteAxisInfo mXAxisInfo; + RawAbsoluteAxisInfo mYAxisInfo; + // The current button state according to the gestures library, but converted into MotionEvent // button values (AMOTION_EVENT_BUTTON_...). uint32_t mButtonState = 0; nsecs_t mDownTime = 0; + + MotionClassification mCurrentClassification = MotionClassification::NONE; + uint32_t mSwipeFingerCount = 0; + 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. + const std::array<PointerProperties, MAX_FAKE_FINGERS> mFingerProps = {{ + {.id = 0, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, + {.id = 1, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, + {.id = 2, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, + {.id = 3, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, + }}; + std::array<PointerCoords, MAX_FAKE_FINGERS> mFakeFingerCoords = {}; }; } // namespace android diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index 74c2028b6f..1c7ec7665a 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -38,6 +38,7 @@ using testing::AllOf; class GestureConverterTest : public testing::Test { protected: 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_Y = 200; @@ -48,6 +49,9 @@ protected: mFakeListener = std::make_unique<TestInputListener>(); mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy, *mFakeListener); + mDevice = newDevice(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, -500, 500, 0, 0, 20); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 500, 0, 0, 20); mFakePointerController = std::make_shared<FakePointerController>(); mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); @@ -55,15 +59,32 @@ protected: mFakePolicy->setPointerController(mFakePointerController); } + std::shared_ptr<InputDevice> newDevice() { + InputDeviceIdentifier identifier; + identifier.name = "device"; + identifier.location = "USB1"; + identifier.bus = 0; + std::shared_ptr<InputDevice> device = + std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, /* generation= */ 2, + identifier); + mReader->pushNextDevice(device); + mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD, + identifier.bus); + mReader->loopOnce(); + return device; + } + std::shared_ptr<FakeEventHub> mFakeEventHub; sp<FakeInputReaderPolicy> mFakePolicy; std::unique_ptr<TestInputListener> mFakeListener; std::unique_ptr<InstrumentedInputReader> mReader; + std::shared_ptr<InputDevice> mDevice; std::shared_ptr<FakePointerController> mFakePointerController; }; TEST_F(GestureConverterTest, Move) { - GestureConverter converter(*mReader->getContext(), DEVICE_ID); + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); @@ -79,7 +100,8 @@ TEST_F(GestureConverterTest, Move) { } TEST_F(GestureConverterTest, Move_Rotated) { - GestureConverter converter(*mReader->getContext(), DEVICE_ID); + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); @@ -96,7 +118,8 @@ TEST_F(GestureConverterTest, Move_Rotated) { } TEST_F(GestureConverterTest, ButtonsChange) { - GestureConverter converter(*mReader->getContext(), DEVICE_ID); + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); // Press left and right buttons at once Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -161,7 +184,8 @@ TEST_F(GestureConverterTest, ButtonsChange) { } TEST_F(GestureConverterTest, DragWithButton) { - GestureConverter converter(*mReader->getContext(), DEVICE_ID); + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); // Press the button Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -215,4 +239,228 @@ TEST_F(GestureConverterTest, DragWithButton) { WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); } +TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAndOffsetsAfterGesture) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, + /* dy= */ 0); + std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ -5, + /* dy= */ 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), + WithGestureOffset(0, 0, EPSILON))); +} + +TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { + // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you + // start swiping up and then start moving left or right, it'll return gesture events with only Y + // deltas until you lift your fingers and start swiping again. That's why each of these tests + // only checks movement in one dimension. + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, + /* dy= */ 10); + std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + ASSERT_EQ(4u, args.size()); + + // Three fake fingers should be created. We don't actually care where they are, so long as they + // move appropriately. + NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + PointerCoords finger0Start = arg.pointerCoords[0]; + args.pop_front(); + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + PointerCoords finger1Start = arg.pointerCoords[1]; + args.pop_front(); + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + PointerCoords finger2Start = arg.pointerCoords[2]; + args.pop_front(); + + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithGestureOffset(0, -0.01, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); + EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); + EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); + EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10); + EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10); + EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10); + + Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* dx= */ 0, /* dy= */ 5); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + ASSERT_EQ(1u, args.size()); + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithGestureOffset(0, -0.005, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); + EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); + EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); + EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15); + EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15); + EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15); + + Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + ASSERT_EQ(3u, args.size()); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); +} + +TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* dx= */ 10, /* dy= */ 0); + std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + ASSERT_EQ(5u, args.size()); + + // Four fake fingers should be created. We don't actually care where they are, so long as they + // move appropriately. + NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + PointerCoords finger0Start = arg.pointerCoords[0]; + args.pop_front(); + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + PointerCoords finger1Start = arg.pointerCoords[1]; + args.pop_front(); + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + PointerCoords finger2Start = arg.pointerCoords[2]; + args.pop_front(); + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + PointerCoords finger3Start = arg.pointerCoords[3]; + args.pop_front(); + + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithGestureOffset(0.01, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10); + EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10); + EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10); + EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10); + EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY()); + EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY()); + EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); + EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY()); + + Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* dx= */ 5, /* dy= */ 0); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + ASSERT_EQ(1u, args.size()); + arg = std::get<NotifyMotionArgs>(args.front()); + ASSERT_THAT(arg, + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithGestureOffset(0.005, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15); + EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15); + EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15); + EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15); + EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY()); + EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY()); + EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); + EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY()); + + Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + ASSERT_EQ(4u, args.size()); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); +} + } // namespace android diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index e5a4b14f00..64c2c7511b 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -16,6 +16,8 @@ #pragma once +#include <cmath> + #include <android/input.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -66,6 +68,11 @@ MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") { return arg.keyCode == keyCode; } +MATCHER_P(WithPointerCount, count, "MotionEvent with specified number of pointers") { + *result_listener << "expected " << count << " pointer(s), but got " << arg.pointerCount; + return arg.pointerCount == count; +} + MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { const auto argX = arg.pointerCoords[0].getX(); const auto argY = arg.pointerCoords[0].getY(); @@ -82,6 +89,17 @@ MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion" return argX == x && argY == y; } +MATCHER_P3(WithGestureOffset, dx, dy, epsilon, + "InputEvent with specified touchpad gesture offset") { + const auto argGestureX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET); + const auto argGestureY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET); + const double xDiff = fabs(argGestureX - dx); + const double yDiff = fabs(argGestureY - dy); + *result_listener << "expected gesture offset (" << dx << ", " << dy << ") within " << epsilon + << ", but got (" << argGestureX << ", " << argGestureY << ")"; + 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; @@ -100,6 +118,13 @@ MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { return arg.flags == flags; } +MATCHER_P(WithMotionClassification, classification, + "InputEvent with specified MotionClassification") { + *result_listener << "expected classification " << motionClassificationToString(classification) + << ", but got " << motionClassificationToString(arg.classification); + return arg.classification == classification; +} + MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") { *result_listener << "expected button state " << buttons << ", but got " << arg.buttonState; return arg.buttonState == buttons; |