Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 54745651 authored by Arthur Hung's avatar Arthur Hung
Browse files

Fix drag and drop may stuck in multi touch

Currently, we only check the motion entry if the point count is 1 during
drag and drop, that could lead some unexpected behavior if user already
had another pointer touched on screen and it can perform drag. That
symptom may happen when user is using split screen or device has the
waterfall touch screen.

This CL will track the pointer id when starting the drag and drop, so it
could prevent the drag surface jumping to another pointer or cause the
touch didn't work after drop finish.

Bug: 228497357
Test: atest inputflinger_tests CrossAppDragAndDropTests
Change-Id: I8d152d95d1d9ccd65bf953052223ac61a1e910df
parent 0f24a7c3
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -26,7 +26,8 @@ namespace android {
namespace inputdispatcher {

struct DragState {
    DragState(const sp<android::gui::WindowInfoHandle>& windowHandle) : dragWindow(windowHandle) {}
    DragState(const sp<android::gui::WindowInfoHandle>& windowHandle, int32_t pointerId)
          : dragWindow(windowHandle), pointerId(pointerId) {}
    void dump(std::string& dump, const char* prefix = "");

    // The window being dragged.
@@ -37,6 +38,8 @@ struct DragState {
    bool isStartDrag = false;
    // Indicate if the stylus button is down at the start of the drag.
    bool isStylusButtonDownAtStart = false;
    // Indicate which pointer id is tracked by the drag and drop.
    const int32_t pointerId;
};

} // namespace inputdispatcher
+79 −43
Original line number Diff line number Diff line
@@ -1747,18 +1747,12 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<
}

void InputDispatcher::enqueueDragEventLocked(const sp<WindowInfoHandle>& windowHandle,
                                             bool isExiting, const MotionEntry& motionEntry) {
    // If the window needs enqueue a drag event, the pointerCount should be 1 and the action should
    // be AMOTION_EVENT_ACTION_MOVE, that could guarantee the first pointer is always valid.
    LOG_ALWAYS_FATAL_IF(motionEntry.pointerCount != 1);
    PointerCoords pointerCoords;
    pointerCoords.copyFrom(motionEntry.pointerCoords[0]);
    pointerCoords.transform(windowHandle->getInfo()->transform);

                                             bool isExiting, const int32_t rawX,
                                             const int32_t rawY) {
    const vec2 xy = windowHandle->getInfo()->transform.transform(vec2(rawX, rawY));
    std::unique_ptr<DragEntry> dragEntry =
            std::make_unique<DragEntry>(mIdGenerator.nextId(), motionEntry.eventTime,
                                        windowHandle->getToken(), isExiting, pointerCoords.getX(),
                                        pointerCoords.getY());
            std::make_unique<DragEntry>(mIdGenerator.nextId(), now(), windowHandle->getToken(),
                                        isExiting, xy.x, xy.y);

    enqueueInboundEventLocked(std::move(dragEntry));
}
@@ -2546,13 +2540,14 @@ void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
        vec2 local = dropWindow->getInfo()->transform.transform(x, y);
        sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
    } else {
        ALOGW("No window found when drop.");
        sendDropWindowCommandLocked(nullptr, 0, 0);
    }
    mDragState.reset();
}

void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
    if (entry.pointerCount != 1 || !mDragState) {
    if (!mDragState) {
        return;
    }

@@ -2562,42 +2557,75 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
                (entry.buttonState & AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) != 0;
    }

    int32_t maskedAction = entry.action & AMOTION_EVENT_ACTION_MASK;
    int32_t x = static_cast<int32_t>(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
    int32_t y = static_cast<int32_t>(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
    if (maskedAction == AMOTION_EVENT_ACTION_MOVE) {
    // Find the pointer index by id.
    int32_t pointerIndex = 0;
    for (; static_cast<uint32_t>(pointerIndex) < entry.pointerCount; pointerIndex++) {
        const PointerProperties& pointerProperties = entry.pointerProperties[pointerIndex];
        if (pointerProperties.id == mDragState->pointerId) {
            break;
        }
    }

    if (uint32_t(pointerIndex) == entry.pointerCount) {
        LOG_ALWAYS_FATAL("Should find a valid pointer index by id %d", mDragState->pointerId);
        sendDropWindowCommandLocked(nullptr, 0, 0);
        mDragState.reset();
        return;
    }

    const int32_t maskedAction = entry.action & AMOTION_EVENT_ACTION_MASK;
    const int32_t x = entry.pointerCoords[pointerIndex].getX();
    const int32_t y = entry.pointerCoords[pointerIndex].getY();

    switch (maskedAction) {
        case AMOTION_EVENT_ACTION_MOVE: {
            // Handle the special case : stylus button no longer pressed.
        bool isStylusButtonDown = (entry.buttonState & AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) != 0;
            bool isStylusButtonDown =
                    (entry.buttonState & AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) != 0;
            if (mDragState->isStylusButtonDownAtStart && !isStylusButtonDown) {
                finishDragAndDrop(entry.displayId, x, y);
                return;
            }

        // Prevent stylus interceptor windows from affecting drag and drop behavior for now, until
        // we have an explicit reason to support it.
            // Prevent stylus interceptor windows from affecting drag and drop behavior for now,
            // until we have an explicit reason to support it.
            constexpr bool isStylus = false;

            const sp<WindowInfoHandle> hoverWindowHandle =
                findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/, isStylus,
                                          false /*addOutsideTargets*/, true /*ignoreDragWindow*/);
                    findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/,
                                              isStylus, false /*addOutsideTargets*/,
                                              true /*ignoreDragWindow*/);
            // enqueue drag exit if needed.
            if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
                !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
                if (mDragState->dragHoverWindowHandle != nullptr) {
                enqueueDragEventLocked(mDragState->dragHoverWindowHandle, true /*isExiting*/,
                                       entry);
                    enqueueDragEventLocked(mDragState->dragHoverWindowHandle, true /*isExiting*/, x,
                                           y);
                }
                mDragState->dragHoverWindowHandle = hoverWindowHandle;
            }
            // enqueue drag location if needed.
            if (hoverWindowHandle != nullptr) {
            enqueueDragEventLocked(hoverWindowHandle, false /*isExiting*/, entry);
                enqueueDragEventLocked(hoverWindowHandle, false /*isExiting*/, x, y);
            }
            break;
        }

        case AMOTION_EVENT_ACTION_POINTER_UP:
            if (getMotionEventActionPointerIndex(entry.action) != pointerIndex) {
                break;
            }
    } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
            // The drag pointer is up.
            [[fallthrough]];
        case AMOTION_EVENT_ACTION_UP:
            finishDragAndDrop(entry.displayId, x, y);
    } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
            break;
        case AMOTION_EVENT_ACTION_CANCEL: {
            ALOGD("Receiving cancel when drag and drop.");
            sendDropWindowCommandLocked(nullptr, 0, 0);
            mDragState.reset();
            break;
        }
    }
}

@@ -5117,7 +5145,15 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<

        // Store the dragging window.
        if (isDragDrop) {
            mDragState = std::make_unique<DragState>(toWindowHandle);
            if (pointerIds.count() > 1) {
                ALOGW("The drag and drop cannot be started when there is more than 1 pointer on the"
                      " window.");
                return false;
            }
            // If the window didn't not support split or the source is mouse, the pointerIds count
            // would be 0, so we have to track the pointer 0.
            const int32_t id = pointerIds.count() == 0 ? 0 : pointerIds.firstMarkedBit();
            mDragState = std::make_unique<DragState>(toWindowHandle, id);
        }

        // Synthesize cancel for old window and down for new window.
+2 −1
Original line number Diff line number Diff line
@@ -221,7 +221,8 @@ private:
                                 const std::string& reason) REQUIRES(mLock);
    // Enqueues a drag event.
    void enqueueDragEventLocked(const sp<android::gui::WindowInfoHandle>& windowToken,
                                bool isExiting, const MotionEntry& motionEntry) REQUIRES(mLock);
                                bool isExiting, const int32_t rawX, const int32_t rawY)
            REQUIRES(mLock);

    // Adds an event to a queue of recent events for debugging purposes.
    void addRecentEventLocked(std::shared_ptr<EventEntry> entry) REQUIRES(mLock);
+102 −7
Original line number Diff line number Diff line
@@ -6110,8 +6110,7 @@ protected:
        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
    }

    // Start performing drag, we will create a drag window and transfer touch to it.
    void performDrag() {
    void injectDown() {
        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                  injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                   {50, 50}))
@@ -6119,6 +6118,15 @@ protected:

        // Window should receive motion event.
        mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
    }

    // Start performing drag, we will create a drag window and transfer touch to it.
    // @param sendDown : if true, send a motion down on first window before perform drag and drop.
    // Returns true on success.
    bool performDrag(bool sendDown = true) {
        if (sendDown) {
            injectDown();
        }

        // The drag window covers the entire display
        mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
@@ -6126,11 +6134,15 @@ protected:
                {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});

        // Transfer touch focus to the drag window
        bool transferred =
                mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(),
                                                true /* isDragDrop */);
        if (transferred) {
            mWindow->consumeMotionCancel();
            mDragWindow->consumeMotionDown();
        }
        return transferred;
    }

    // Start performing drag, we will create a drag window and transfer touch to it.
    void performStylusDrag() {
@@ -6276,7 +6288,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) {
    mSecondWindow->assertNoEvents();
}

TEST_F(InputDispatcherDragTests, DragAndDrop_InvalidWindow) {
TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) {
    performDrag();

    // Set second window invisible.
@@ -6312,6 +6324,89 @@ TEST_F(InputDispatcherDragTests, DragAndDrop_InvalidWindow) {
    mSecondWindow->assertNoEvents();
}

TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) {
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                               {50, 50}))
            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);

    const MotionEvent secondFingerDownEvent =
            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                    .displayId(ADISPLAY_ID_DEFAULT)
                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50))
                    .build();
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                InputEventInjectionSync::WAIT_FOR_RESULT))
            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
    mWindow->consumeMotionPointerDown(1 /* pointerIndex */);

    // Should not perform drag and drop when window has multi fingers.
    ASSERT_FALSE(performDrag(false));
}

TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) {
    // First down on second window.
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                               {150, 50}))
            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";

    mSecondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);

    // Second down on first window.
    const MotionEvent secondFingerDownEvent =
            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                    .displayId(ADISPLAY_ID_DEFAULT)
                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                    .pointer(
                            PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                    .build();
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                InputEventInjectionSync::WAIT_FOR_RESULT))
            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);

    // Perform drag and drop from first window.
    ASSERT_TRUE(performDrag(false));

    // Move on window.
    const MotionEvent secondFingerMoveEvent =
            MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                    .pointer(
                            PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                    .build();
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
              injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
                                InputEventInjectionSync::WAIT_FOR_RESULT));
    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
    mWindow->consumeDragEvent(false, 50, 50);
    mSecondWindow->consumeMotionMove();

    // Release the drag pointer should perform drop.
    const MotionEvent secondFingerUpEvent =
            MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                    .pointer(
                            PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                    .build();
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
              injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
                                InputEventInjectionSync::WAIT_FOR_RESULT));
    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
    mFakePolicy->assertDropTargetEquals(mWindow->getToken());
    mWindow->assertNoEvents();
    mSecondWindow->consumeMotionMove();
}

class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {};

TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {