Loading libs/input/input_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -119,3 +119,10 @@ flag { description: "Controls the API to provide InputDevice view behavior." bug: "246946631" } flag { name: "enable_touchpad_fling_stop" namespace: "input" description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again" bug: "281106755" } services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +46 −10 Original line number Diff line number Diff line Loading @@ -67,7 +67,8 @@ GestureConverter::GestureConverter(InputReaderContext& readerContext, : mDeviceId(deviceId), mReaderContext(readerContext), mPointerController(readerContext.getPointerController(deviceId)), mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) { mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()), mEnableFlingStop(input_flags::enable_touchpad_fling_stop()) { deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo); deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); } Loading Loading @@ -400,21 +401,56 @@ std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTi // 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. mFlingMayBeInProgress = true; return endScroll(when, readTime); } break; case GESTURES_FLING_TAP_DOWN: if (mCurrentClassification == MotionClassification::NONE) { if (mEnableFlingStop && mFlingMayBeInProgress) { // The user has just touched the pad again after ending a two-finger scroll // motion, which might have started a fling. We want to stop the fling, but // unfortunately there's currently no API for doing so. Instead, send and // immediately cancel a fake finger to cause the scrolling to stop and hopefully // avoid side effects (e.g. activation of UI elements). // TODO(b/326056750): add an API for fling stops. mFlingMayBeInProgress = false; const auto [xCursorPosition, yCursorPosition] = mEnablePointerChoreographer ? FloatPoint{0, 0} : 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); std::list<NotifyArgs> out; mDownTime = when; out += exitHover(when, readTime, xCursorPosition, yCursorPosition); // TODO(b/281106755): add a MotionClassification value for fling stops. out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*buttonState=*/0, /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition)); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_CANCEL, /*actionButton=*/0, /*buttonState=*/0, /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition)); out += enterHover(when, readTime, xCursorPosition, yCursorPosition); return out; } else { // 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. // TODO(b/282023644): Add a signal in libgestures for when a stable contact has // been initiated with a touchpad. return handleMove(when, readTime, gestureStartTime, Gesture(kGestureMove, gesture.start_time, gesture.end_time, /*dx=*/0.f, /*dy=*/0.f)); } } break; default: break; Loading services/inputflinger/reader/mapper/gestures/GestureConverter.h +4 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ private: InputReaderContext& mReaderContext; std::shared_ptr<PointerControllerInterface> mPointerController; const bool mEnablePointerChoreographer; const bool mEnableFlingStop; std::optional<int32_t> mDisplayId; FloatRect mBoundsInLogicalDisplay{}; Loading @@ -120,6 +121,9 @@ private: // Whether we are currently in a hover state (i.e. a HOVER_ENTER event has been sent without a // matching HOVER_EXIT). bool mIsHovering = false; // Whether we've received a "fling start" gesture (i.e. the end of a scroll) but no "fling tap // down" gesture to match it yet. bool mFlingMayBeInProgress = false; MotionClassification mCurrentClassification = MotionClassification::NONE; // Only used when mCurrentClassification is MULTI_FINGER_SWIPE. Loading services/inputflinger/tests/GestureConverter_test.cpp +64 −0 Original line number Diff line number Diff line Loading @@ -1167,6 +1167,38 @@ TEST_F(GestureConverterTest, FlingTapDown) { ASSERT_TRUE(mFakePointerController->isPointerShown()); } TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); input_flags::enable_touchpad_fling_stop(true); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)), VariantWith<NotifyMotionArgs>( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Tap) { // Tap should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); Loading Loading @@ -2556,6 +2588,38 @@ TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) { WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); } TEST_F(GestureConverterTestWithChoreographer, FlingTapDownAfterScrollStopsFling) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); input_flags::enable_touchpad_fling_stop(true); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)), VariantWith<NotifyMotionArgs>( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Tap) { // Tap should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); Loading Loading
libs/input/input_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -119,3 +119,10 @@ flag { description: "Controls the API to provide InputDevice view behavior." bug: "246946631" } flag { name: "enable_touchpad_fling_stop" namespace: "input" description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again" bug: "281106755" }
services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +46 −10 Original line number Diff line number Diff line Loading @@ -67,7 +67,8 @@ GestureConverter::GestureConverter(InputReaderContext& readerContext, : mDeviceId(deviceId), mReaderContext(readerContext), mPointerController(readerContext.getPointerController(deviceId)), mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) { mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()), mEnableFlingStop(input_flags::enable_touchpad_fling_stop()) { deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo); deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); } Loading Loading @@ -400,21 +401,56 @@ std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTi // 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. mFlingMayBeInProgress = true; return endScroll(when, readTime); } break; case GESTURES_FLING_TAP_DOWN: if (mCurrentClassification == MotionClassification::NONE) { if (mEnableFlingStop && mFlingMayBeInProgress) { // The user has just touched the pad again after ending a two-finger scroll // motion, which might have started a fling. We want to stop the fling, but // unfortunately there's currently no API for doing so. Instead, send and // immediately cancel a fake finger to cause the scrolling to stop and hopefully // avoid side effects (e.g. activation of UI elements). // TODO(b/326056750): add an API for fling stops. mFlingMayBeInProgress = false; const auto [xCursorPosition, yCursorPosition] = mEnablePointerChoreographer ? FloatPoint{0, 0} : 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); std::list<NotifyArgs> out; mDownTime = when; out += exitHover(when, readTime, xCursorPosition, yCursorPosition); // TODO(b/281106755): add a MotionClassification value for fling stops. out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*buttonState=*/0, /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition)); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_CANCEL, /*actionButton=*/0, /*buttonState=*/0, /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition)); out += enterHover(when, readTime, xCursorPosition, yCursorPosition); return out; } else { // 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. // TODO(b/282023644): Add a signal in libgestures for when a stable contact has // been initiated with a touchpad. return handleMove(when, readTime, gestureStartTime, Gesture(kGestureMove, gesture.start_time, gesture.end_time, /*dx=*/0.f, /*dy=*/0.f)); } } break; default: break; Loading
services/inputflinger/reader/mapper/gestures/GestureConverter.h +4 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ private: InputReaderContext& mReaderContext; std::shared_ptr<PointerControllerInterface> mPointerController; const bool mEnablePointerChoreographer; const bool mEnableFlingStop; std::optional<int32_t> mDisplayId; FloatRect mBoundsInLogicalDisplay{}; Loading @@ -120,6 +121,9 @@ private: // Whether we are currently in a hover state (i.e. a HOVER_ENTER event has been sent without a // matching HOVER_EXIT). bool mIsHovering = false; // Whether we've received a "fling start" gesture (i.e. the end of a scroll) but no "fling tap // down" gesture to match it yet. bool mFlingMayBeInProgress = false; MotionClassification mCurrentClassification = MotionClassification::NONE; // Only used when mCurrentClassification is MULTI_FINGER_SWIPE. Loading
services/inputflinger/tests/GestureConverter_test.cpp +64 −0 Original line number Diff line number Diff line Loading @@ -1167,6 +1167,38 @@ TEST_F(GestureConverterTest, FlingTapDown) { ASSERT_TRUE(mFakePointerController->isPointerShown()); } TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); input_flags::enable_touchpad_fling_stop(true); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)), VariantWith<NotifyMotionArgs>( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Tap) { // Tap should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); Loading Loading @@ -2556,6 +2588,38 @@ TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) { WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); } TEST_F(GestureConverterTestWithChoreographer, FlingTapDownAfterScrollStopsFling) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); input_flags::enable_touchpad_fling_stop(true); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)), VariantWith<NotifyMotionArgs>( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Tap) { // Tap should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); Loading