/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "gestures/GestureConverter.h" #include #include #include #include #include #include #include #include "TouchCursorInputMapperCommon.h" #include "input/Input.h" namespace android { namespace { uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) { switch (gesturesButton) { case GESTURES_BUTTON_LEFT: return AMOTION_EVENT_BUTTON_PRIMARY; case GESTURES_BUTTON_MIDDLE: return AMOTION_EVENT_BUTTON_TERTIARY; case GESTURES_BUTTON_RIGHT: return AMOTION_EVENT_BUTTON_SECONDARY; case GESTURES_BUTTON_BACK: return AMOTION_EVENT_BUTTON_BACK; case GESTURES_BUTTON_FORWARD: return AMOTION_EVENT_BUTTON_FORWARD; default: return 0; } } } // namespace GestureConverter::GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext, int32_t deviceId) : mDeviceId(deviceId), mReaderContext(readerContext), mPointerController(readerContext.getPointerController(deviceId)) { deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo); deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); } std::string GestureConverter::dump() const { std::stringstream out; out << "Orientation: " << ftl::enum_string(mOrientation) << "\n"; out << "Axis info:\n"; out << " X: " << mXAxisInfo << "\n"; out << " Y: " << mYAxisInfo << "\n"; out << StringPrintf("Button state: 0x%08x\n", mButtonState); out << "Down time: " << mDownTime << "\n"; out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n"; return out.str(); } std::list GestureConverter::reset(nsecs_t when) { std::list out; switch (mCurrentClassification) { case MotionClassification::TWO_FINGER_SWIPE: out.push_back(endScroll(when, when)); break; case MotionClassification::MULTI_FINGER_SWIPE: out += handleMultiFingerSwipeLift(when, when); break; case MotionClassification::PINCH: out += endPinch(when, when); break; case MotionClassification::NONE: // When a button is pressed, the Gestures library always ends the current gesture, // so we don't have to worry about the case where buttons need to be lifted during a // pinch or swipe. if (mButtonState) { out += releaseAllButtons(when, when); } break; default: break; } mCurrentClassification = MotionClassification::NONE; mDownTime = 0; return out; } void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const { info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0); // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture // is enabled. if (std::optional rect = mPointerController->getBounds(); rect.has_value()) { info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, rect->left, rect->right, 0, 0, 0); info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, rect->top, rect->bottom, 0, 0, 0); } info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0); info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0); // The other axes that can be reported don't have ranges that are easy to define. RELATIVE_X/Y // and GESTURE_SCROLL_X/Y_DISTANCE are the result of acceleration functions being applied to // finger movements, so their maximum values can't simply be derived from the size of the // touchpad. GESTURE_PINCH_SCALE_FACTOR's maximum value depends on the minimum finger separation // that the pad can report, which cannot be determined from its raw axis information. (Assuming // a minimum finger separation of 1 unit would let us calculate a theoretical maximum, but it // would be orders of magnitude too high, so probably not very useful.) } std::list GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { switch (gesture.type) { case kGestureTypeMove: 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); 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); case kGestureTypePinch: return handlePinch(when, readTime, gesture); default: return {}; } } NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { float deltaX = gesture.details.move.dx; float deltaY = gesture.details.move.dy; rotateDelta(mOrientation, &deltaX, &deltaY); mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); mPointerController->move(deltaX, deltaY); mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); PointerCoords coords; coords.clear(); coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); const bool down = isPointerDown(mButtonState); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE; return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition, yCursorPosition); } std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { std::list out = {}; mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); PointerCoords coords; coords.clear(); coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); const uint32_t buttonsPressed = gesture.details.buttons.down; bool pointerDown = isPointerDown(mButtonState) || buttonsPressed & (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); uint32_t newButtonState = mButtonState; std::list pressEvents = {}; for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { if (buttonsPressed & button) { uint32_t actionButton = gesturesButtonToMotionEventButton(button); newButtonState |= actionButton; pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, newButtonState, /* 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, mFingerProps.data(), &coords, xCursorPosition, yCursorPosition)); } out.splice(out.end(), pressEvents); // The same button may be in both down and up in the same gesture, in which case we should treat // it as having gone down and then up. So, we treat a single button change gesture as two state // changes: a set of buttons going down, followed by a set of buttons going up. mButtonState = newButtonState; const uint32_t buttonsReleased = gesture.details.buttons.up; for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { if (buttonsReleased & button) { uint32_t actionButton = gesturesButtonToMotionEventButton(button); newButtonState &= ~actionButton; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, newButtonState, /* pointerCount= */ 1, 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, mFingerProps.data(), &coords, xCursorPosition, yCursorPosition)); } mButtonState = newButtonState; return out; } std::list GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) { std::list out; const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); PointerCoords coords; coords.clear(); coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); const bool pointerDown = isPointerDown(mButtonState); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); uint32_t newButtonState = mButtonState; for (uint32_t button = AMOTION_EVENT_BUTTON_PRIMARY; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) { if (mButtonState & button) { newButtonState &= ~button; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, button, newButtonState, /*pointerCount=*/1, mFingerProps.data(), &coords, xCursorPosition, yCursorPosition)); } } if (pointerDown) { coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, newButtonState, /*pointerCount=*/1, mFingerProps.data(), &coords, xCursorPosition, yCursorPosition)); } mButtonState = 0; return out; } std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { std::list out; PointerCoords& coords = mFakeFingerCoords[0]; const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); 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; NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; out.push_back(args); } 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); NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; out.push_back(args); return out; } std::list GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { switch (gesture.details.fling.fling_state) { case GESTURES_FLING_START: if (mCurrentClassification == MotionClassification::TWO_FINGER_SWIPE) { // 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. return {endScroll(when, readTime)}; } break; case GESTURES_FLING_TAP_DOWN: if (mCurrentClassification == MotionClassification::NONE) { // Use the tap down state of a fling gesture as an indicator that a contact // has been initiated with the touchpad. We treat this as a move event with zero // magnitude, which will also result in the pointer icon being updated. // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been // initiated with a touchpad. return {handleMove(when, readTime, Gesture(kGestureMove, gesture.start_time, gesture.end_time, /*dx=*/0.f, /*dy=*/0.f))}; } break; default: break; } return {}; } NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0); NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; mCurrentClassification = MotionClassification::NONE; return args; } [[nodiscard]] std::list GestureConverter::handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime, uint32_t fingerCount, float dx, float dy) { std::list out = {}; const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); 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)); } } float rotatedDeltaX = dx, rotatedDeltaY = -dy; rotateDelta(mOrientation, &rotatedDeltaX, &rotatedDeltaY); for (size_t i = 0; i < mSwipeFingerCount; i++) { PointerCoords& coords = mFakeFingerCoords[i]; coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) + rotatedDeltaX); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + rotatedDeltaY); } float xOffset = dx / (mXAxisInfo.maxValue - mXAxisInfo.minValue); 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 GestureConverter::handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime) { std::list out = {}; if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) { return out; } const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); 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; } [[nodiscard]] std::list GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); // 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; std::list out; 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) { return endPinch(when, readTime); } 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); return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, mButtonState, /*pointerCount=*/2, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)}; } std::list GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) { std::list out; const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); 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; } NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, uint32_t pointerCount, const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, float xCursorPosition, float yCursorPosition) { return {mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE, action, /* actionButton= */ actionButton, /* flags= */ 0, mReaderContext.getGlobalMetaState(), buttonState, mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, pointerCoords, /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition, yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {}}; } } // namespace android