diff options
author | 2025-01-23 16:27:38 -0800 | |
---|---|---|
committer | 2025-01-29 16:16:20 -0800 | |
commit | 231813376544ba24936bac405fed22967e98eaf3 (patch) | |
tree | 765016a1060426e2a9e826e71c34eac8e842b7c9 | |
parent | d1f563013dfe3a2b5f8b412b9ee19b42a36d5932 (diff) |
Ensure canceled touch stream uses previous display id
We are observing inconsistent event streams being sent out of
TouchInputMapper. Whenever display id associated with a specific device
changes while touch is in progress, the generated ACTION_CANCEL event
has the new display id instead of the original one.
This causes issues later in the pipeline. In the case when a11y is ON,
the events are sent to a11y and get later re-injected into
InputDispatcher. The InputDispatcher will not resolve the correct
verifier, and as a result will incorrectly reject the event.
In this CL, we are storing the previous display id before
reconfiguration occurs, and then using this stored display id to abort
touches just before reset() is called.
In reset(), we are also canceling touches. That behaviour has to be
maintained, since reset() is a public API that can be called from other
places.
Bug: 378308551
Test: TEST=inputflinger_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_filter="*ChangeAssociatedDisplayIdWhenTouchIsActive*"
Flag: EXEMPT bugfix
Change-Id: I275bdd03929be6dd024c250c1276c05622a0e2ce
3 files changed, 159 insertions, 61 deletions
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 5cfda037d9..8deff6b3aa 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -302,6 +302,8 @@ std::list<NotifyArgs> TouchInputMapper::reconfigure(nsecs_t when, ConfigurationChanges changes) { std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes); + std::optional<ui::LogicalDisplayId> previousDisplayId = getAssociatedDisplayId(); + mConfig = config; // Full configuration should happen the first time configure is called and @@ -350,6 +352,8 @@ std::list<NotifyArgs> TouchInputMapper::reconfigure(nsecs_t when, } if (changes.any() && resetNeeded) { + // Touches should be aborted using the previous display id, so that the stream is consistent + out += abortTouches(when, when, /*policyFlags=*/0, previousDisplayId); out += reset(when); // Send reset, unless this is the first time the device has been configured, @@ -1657,6 +1661,10 @@ bool TouchInputMapper::isTouchScreen() { mParameters.hasAssociatedDisplay; } +ui::LogicalDisplayId TouchInputMapper::resolveDisplayId() const { + return getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID); +}; + void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) { if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus()) { // If any of the external buttons are already pressed by the touch device, ignore them. @@ -1928,8 +1936,9 @@ NotifyKeyArgs TouchInputMapper::dispatchVirtualKey(nsecs_t when, nsecs_t readTim keyEventFlags, keyCode, scanCode, metaState, downTime); } -std::list<NotifyArgs> TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, - uint32_t policyFlags) { +std::list<NotifyArgs> TouchInputMapper::abortTouches( + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, + std::optional<ui::LogicalDisplayId> currentGestureDisplayId) { std::list<NotifyArgs> out; if (mCurrentMotionAborted) { // Current motion event was already aborted. @@ -1940,6 +1949,7 @@ std::list<NotifyArgs> TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readT int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentCookedState.buttonState; out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + currentGestureDisplayId.value_or(resolveDisplayId()), AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mCurrentCookedState.cookedPointerData.pointerProperties, @@ -1994,14 +2004,15 @@ std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t re if (!currentIdBits.isEmpty()) { // No pointer id changes so this is a move event. // The listener takes care of batching moves so we don't have to deal with that here. - out.push_back( - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, - 0, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, - -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE)); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), + AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + currentIdBits, -1, mOrientedXPrecision, + mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); } } else { // There may be pointers going up and pointers going down and pointers moving @@ -2031,7 +2042,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t re if (isCanceled) { ALOGI("Canceling pointer %d for the palm event was detected.", upId); } - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_POINTER_UP, 0, isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, buttonState, 0, @@ -2050,7 +2061,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t re // events, they do not generally handle them except when presented in a move event. if (moveNeeded && !moveIdBits.isEmpty()) { ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value); - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, @@ -2071,7 +2082,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t re } out.push_back( - dispatchMotion(when, readTime, policyFlags, mSource, + dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, @@ -2090,7 +2101,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t (mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty() || !mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty())) { int32_t metaState = getContext()->getGlobalMetaState(); - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState, mLastCookedState.buttonState, 0, mLastCookedState.cookedPointerData.pointerProperties, @@ -2111,7 +2122,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, !mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); if (!mSentHoverEnter) { - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, metaState, mCurrentRawState.buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties, @@ -2123,7 +2134,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, mSentHoverEnter = true; } - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, mCurrentRawState.buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties, @@ -2146,7 +2157,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsec while (!releasedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit()); buttonState &= ~actionButton; - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, metaState, buttonState, 0, mLastCookedState.cookedPointerData.pointerProperties, @@ -2168,7 +2179,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_ while (!pressedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit()); buttonState |= actionButton; - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState, buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties, @@ -2192,7 +2203,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchGestureButtonRelease(nsecs_t whe while (!releasedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit()); buttonState &= ~actionButton; - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, metaState, buttonState, 0, mPointerGesture.lastGestureProperties, @@ -2216,7 +2227,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchGestureButtonPress(nsecs_t when, while (!pressedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit()); buttonState |= actionButton; - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState, buttonState, 0, mPointerGesture.currentGestureProperties, mPointerGesture.currentGestureCoords, @@ -2564,7 +2575,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns if (!dispatchedGestureIdBits.isEmpty()) { if (cancelPreviousGesture) { const uint32_t cancelFlags = flags | AMOTION_EVENT_FLAG_CANCELED; - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_CANCEL, 0, cancelFlags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, @@ -2591,8 +2602,9 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns } const uint32_t id = upGestureIdBits.clearFirstMarkedBit(); out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, - AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, - buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + resolveDisplayId(), AMOTION_EVENT_ACTION_POINTER_UP, 0, + flags, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, @@ -2606,13 +2618,14 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns // Send motion events for all pointers that moved. if (moveNeeded) { - out.push_back( - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, - flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.currentGestureProperties, - mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, - 0, 0, mPointerGesture.downTime, classification)); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), + AMOTION_EVENT_ACTION_MOVE, 0, flags, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, -1, 0, 0, mPointerGesture.downTime, + classification)); } // Send motion events for all pointers that went down. @@ -2627,7 +2640,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns mPointerGesture.downTime = when; } - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_POINTER_DOWN, 0, flags, metaState, buttonState, 0, mPointerGesture.currentGestureProperties, mPointerGesture.currentGestureCoords, @@ -2645,7 +2658,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns // Send motion events for hover. if (mPointerGesture.currentGestureMode == PointerGesture::Mode::HOVER) { - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.currentGestureProperties, @@ -2704,7 +2717,7 @@ std::list<NotifyArgs> TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs if (!mPointerGesture.lastGestureIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentRawState.buttonState; - out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, resolveDisplayId(), AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, @@ -3498,8 +3511,7 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs hovering = false; } - return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, - getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID)); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, resolveDisplayId()); } std::list<NotifyArgs> TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, @@ -3662,9 +3674,10 @@ std::list<NotifyArgs> TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t } NotifyMotionArgs TouchInputMapper::dispatchMotion( - nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, - int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, - int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords, + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, + ui::LogicalDisplayId displayId, int32_t action, int32_t actionButton, int32_t flags, + int32_t metaState, int32_t buttonState, int32_t edgeFlags, + const PropertiesArray& properties, const CoordsArray& coords, const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime, MotionClassification classification) const { std::vector<PointerCoords> pointerCoords; @@ -3714,9 +3727,6 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( } } - const ui::LogicalDisplayId displayId = - getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID); - float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mDeviceMode == DeviceMode::POINTER) { @@ -3739,7 +3749,7 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( std::list<NotifyArgs> TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { std::list<NotifyArgs> out; out += abortPointerUsage(when, readTime, /*policyFlags=*/0); - out += abortTouches(when, readTime, /* policyFlags=*/0); + out += abortTouches(when, readTime, /* policyFlags=*/0, std::nullopt); return out; } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 96fc61b5ad..4ef0be8f8e 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -238,7 +238,7 @@ protected: // DeviceType::TOUCH_SCREEN, and will otherwise use DeviceType::POINTER by default. // This can be overridden by IDC files, using the `touch.deviceType` config. DeviceType deviceType; - bool hasAssociatedDisplay; + bool hasAssociatedDisplay = false; bool associatedDisplayIsExternal; bool orientationAware; @@ -779,8 +779,9 @@ private: nsecs_t readTime); const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData); void cookPointerData(); - [[nodiscard]] std::list<NotifyArgs> abortTouches(nsecs_t when, nsecs_t readTime, - uint32_t policyFlags); + [[nodiscard]] std::list<NotifyArgs> abortTouches( + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, + std::optional<ui::LogicalDisplayId> gestureDisplayId); [[nodiscard]] std::list<NotifyArgs> dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, @@ -836,15 +837,18 @@ private: // method will take care of setting the index and transmuting the action to DOWN or UP // it is the first / last pointer to go down / up. [[nodiscard]] NotifyMotionArgs dispatchMotion( - nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, - int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, - int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords, + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, + ui::LogicalDisplayId displayId, int32_t action, int32_t actionButton, int32_t flags, + int32_t metaState, int32_t buttonState, int32_t edgeFlags, + const PropertiesArray& properties, const CoordsArray& coords, const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime, MotionClassification classification) const; // Returns if this touch device is a touch screen with an associated display. bool isTouchScreen(); + ui::LogicalDisplayId resolveDisplayId() const; + bool isPointInsidePhysicalFrame(int32_t x, int32_t y) const; const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y); diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp index e8cca5f979..cc3d1239ce 100644 --- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp +++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp @@ -97,6 +97,17 @@ protected: EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, _)).WillRepeatedly(Return(false)); EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT)) .WillRepeatedly(Return(true)); + // The following EXPECT_CALL lines are not load-bearing, but without them gtest prints + // warnings about "uninteresting mocked call", which are distracting when developing the + // tests because this text is interleaved with logs of interest. + EXPECT_CALL(mMockEventHub, getVirtualKeyDefinitions(EVENTHUB_ID, _)) + .WillRepeatedly(Return()); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, _)) + .WillRepeatedly(testing::Return(false)); + EXPECT_CALL(mMockEventHub, getVideoFrames(EVENTHUB_ID)) + .WillRepeatedly(testing::Return(std::vector<TouchVideoFrame>{})); + EXPECT_CALL(mMockInputReaderContext, getExternalStylusDevices(_)).WillRepeatedly(Return()); + EXPECT_CALL(mMockInputReaderContext, getGlobalMetaState()).WillRepeatedly(Return(0)); // Axes that the device has setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/SLOT_COUNT - 1, /*resolution=*/0); @@ -164,24 +175,97 @@ protected: }); } - std::list<NotifyArgs> processPosition(int32_t x, int32_t y) { + [[nodiscard]] std::list<NotifyArgs> processPosition(int32_t x, int32_t y) { std::list<NotifyArgs> args; args += process(EV_ABS, ABS_MT_POSITION_X, x); args += process(EV_ABS, ABS_MT_POSITION_Y, y); return args; } - std::list<NotifyArgs> processId(int32_t id) { return process(EV_ABS, ABS_MT_TRACKING_ID, id); } + [[nodiscard]] std::list<NotifyArgs> processId(int32_t id) { + return process(EV_ABS, ABS_MT_TRACKING_ID, id); + } - std::list<NotifyArgs> processKey(int32_t code, int32_t value) { + [[nodiscard]] std::list<NotifyArgs> processKey(int32_t code, int32_t value) { return process(EV_KEY, code, value); } - std::list<NotifyArgs> processSlot(int32_t slot) { return process(EV_ABS, ABS_MT_SLOT, slot); } + [[nodiscard]] std::list<NotifyArgs> processSlot(int32_t slot) { + return process(EV_ABS, ABS_MT_SLOT, slot); + } - std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); } + [[nodiscard]] std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); } }; +/** + * While a gesture is active, change the display that the device is associated with. Make sure that + * the CANCEL event that's generated has the display id of the original DOWN event, rather than the + * new display id. + */ +TEST_F(MultiTouchInputMapperUnitTest, ChangeAssociatedDisplayIdWhenTouchIsActive) { + std::list<NotifyArgs> args; + + // Add a second viewport that later will be associated with our device. + DisplayViewport secondViewport = + createViewport(SECOND_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, "local:1", NO_PORT, ViewportType::EXTERNAL); + mFakePolicy->addDisplayViewport(secondViewport); + std::optional<DisplayViewport> firstViewport = + mFakePolicy->getDisplayViewportByUniqueId("local:0"); + + // InputReaderConfiguration contains information about how devices are associated with displays. + // The mapper receives this information. However, it doesn't actually parse it - that's done by + // InputDevice. The mapper asks InputDevice about the associated viewport, so that's what we + // need to mock here to simulate association. This abstraction is confusing and should be + // refactored. + + // Start with the first viewport + ON_CALL((*mDevice), getAssociatedViewport).WillByDefault(Return(firstViewport)); + args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + + int32_t x1 = 100, y1 = 125; + args += processKey(BTN_TOUCH, 1); + args += processPosition(x1, y1); + args += processId(1); + args += processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(DISPLAY_ID))))); + args.clear(); + + // Now associate with the second viewport, and reconfigure. + ON_CALL((*mDevice), getAssociatedViewport).WillByDefault(Return(secondViewport)); + args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + assertNotifyArgs(args, + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(DISPLAY_ID))), + VariantWith<NotifyDeviceResetArgs>(WithDeviceId(DEVICE_ID))); + + // The remainder of the gesture is ignored + // Move. + x1 += 10; + y1 += 15; + args = processPosition(x1, y1); + args += processSync(); + // Up + args += processKey(BTN_TOUCH, 0); + args += processId(-1); + args += processSync(); + + ASSERT_THAT(args, IsEmpty()); + + // New touch is delivered with the new display id. + args += processId(2); + args += processKey(BTN_TOUCH, 1); + args += processPosition(x1 + 20, y1 + 40); + args += processSync(); + assertNotifyArgs(args, + VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(ACTION_DOWN), + WithDisplayId(SECOND_DISPLAY_ID)))); +} + // This test simulates a multi-finger gesture with unexpected reset in between. This might happen // due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be // reset, MT slot state to be re-populated and the gesture should be cancelled and restarted. @@ -191,7 +275,7 @@ TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) { // Two fingers down at once. constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2; int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225; - processKey(BTN_TOUCH, 1); + args += processKey(BTN_TOUCH, 1); args += processPosition(x1, y1); args += processId(FIRST_TRACKING_ID); args += processSlot(1); @@ -199,7 +283,7 @@ TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) { args += processId(SECOND_TRACKING_ID); ASSERT_THAT(args, IsEmpty()); - args = processSync(); + args += processSync(); ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>( WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), @@ -272,8 +356,8 @@ TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) { ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP)))); // Second finger up. - processKey(BTN_TOUCH, 0); - args = processSlot(1); + args = processKey(BTN_TOUCH, 0); + args += processSlot(1); args += processId(-1); ASSERT_THAT(args, IsEmpty()); @@ -317,12 +401,12 @@ TEST_F(ExternalMultiTouchInputMapperTest, Viewports_Fallback) { InputReaderConfiguration::Change::DISPLAY_INFO); assertNotifyArgs(args, - VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(ACTION_CANCEL), - WithDisplayId(SECOND_DISPLAY_ID))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(DISPLAY_ID))), VariantWith<NotifyDeviceResetArgs>(WithDeviceId(DEVICE_ID))); // Lift up the old pointer. - processKey(BTN_TOUCH, 0); - args = processId(-1); + args = processKey(BTN_TOUCH, 0); + args += processId(-1); args += processSync(); // Send new pointer |