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

Commit f270ca49 authored by Arthur Hung's avatar Arthur Hung Committed by Automerger Merge Worker
Browse files

Fix drag and drop may stuck in multi touch am: 54745651

parents 810b0028 54745651
Loading
Loading
Loading
Loading
+4 −1
Original line number Original line Diff line number Diff line
@@ -26,7 +26,8 @@ namespace android {
namespace inputdispatcher {
namespace inputdispatcher {


struct DragState {
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 = "");
    void dump(std::string& dump, const char* prefix = "");


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


} // namespace inputdispatcher
} // namespace inputdispatcher
+79 −43
Original line number Original line 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,
void InputDispatcher::enqueueDragEventLocked(const sp<WindowInfoHandle>& windowHandle,
                                             bool isExiting, const MotionEntry& motionEntry) {
                                             bool isExiting, const int32_t rawX,
    // If the window needs enqueue a drag event, the pointerCount should be 1 and the action should
                                             const int32_t rawY) {
    // be AMOTION_EVENT_ACTION_MOVE, that could guarantee the first pointer is always valid.
    const vec2 xy = windowHandle->getInfo()->transform.transform(vec2(rawX, rawY));
    LOG_ALWAYS_FATAL_IF(motionEntry.pointerCount != 1);
    PointerCoords pointerCoords;
    pointerCoords.copyFrom(motionEntry.pointerCoords[0]);
    pointerCoords.transform(windowHandle->getInfo()->transform);

    std::unique_ptr<DragEntry> dragEntry =
    std::unique_ptr<DragEntry> dragEntry =
            std::make_unique<DragEntry>(mIdGenerator.nextId(), motionEntry.eventTime,
            std::make_unique<DragEntry>(mIdGenerator.nextId(), now(), windowHandle->getToken(),
                                        windowHandle->getToken(), isExiting, pointerCoords.getX(),
                                        isExiting, xy.x, xy.y);
                                        pointerCoords.getY());


    enqueueInboundEventLocked(std::move(dragEntry));
    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);
        vec2 local = dropWindow->getInfo()->transform.transform(x, y);
        sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
        sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
    } else {
    } else {
        ALOGW("No window found when drop.");
        sendDropWindowCommandLocked(nullptr, 0, 0);
        sendDropWindowCommandLocked(nullptr, 0, 0);
    }
    }
    mDragState.reset();
    mDragState.reset();
}
}


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


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


    int32_t maskedAction = entry.action & AMOTION_EVENT_ACTION_MASK;
    // Find the pointer index by id.
    int32_t x = static_cast<int32_t>(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
    int32_t pointerIndex = 0;
    int32_t y = static_cast<int32_t>(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
    for (; static_cast<uint32_t>(pointerIndex) < entry.pointerCount; pointerIndex++) {
    if (maskedAction == AMOTION_EVENT_ACTION_MOVE) {
        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.
            // 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) {
            if (mDragState->isStylusButtonDownAtStart && !isStylusButtonDown) {
                finishDragAndDrop(entry.displayId, x, y);
                finishDragAndDrop(entry.displayId, x, y);
                return;
                return;
            }
            }


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


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


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


        // Store the dragging window.
        // Store the dragging window.
        if (isDragDrop) {
        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.
        // Synthesize cancel for old window and down for new window.
+2 −1
Original line number Original line Diff line number Diff line
@@ -221,7 +221,8 @@ private:
                                 const std::string& reason) REQUIRES(mLock);
                                 const std::string& reason) REQUIRES(mLock);
    // Enqueues a drag event.
    // Enqueues a drag event.
    void enqueueDragEventLocked(const sp<android::gui::WindowInfoHandle>& windowToken,
    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.
    // Adds an event to a queue of recent events for debugging purposes.
    void addRecentEventLocked(std::shared_ptr<EventEntry> entry) REQUIRES(mLock);
    void addRecentEventLocked(std::shared_ptr<EventEntry> entry) REQUIRES(mLock);
+102 −7
Original line number Original line Diff line number Diff line
@@ -6110,8 +6110,7 @@ protected:
        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
    }
    }


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


        // Window should receive motion event.
        // Window should receive motion event.
        mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
        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
        // The drag window covers the entire display
        mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
        mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
@@ -6126,11 +6134,15 @@ protected:
                {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});
                {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});


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


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


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


    // Set second window invisible.
    // Set second window invisible.
@@ -6312,6 +6324,89 @@ TEST_F(InputDispatcherDragTests, DragAndDrop_InvalidWindow) {
    mSecondWindow->assertNoEvents();
    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 {};
class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {};


TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {
TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {