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

Commit 5d3bc374 authored by Svet Ganov's avatar Svet Ganov
Browse files

Generate down events when transferring touch focus

When we transfer touch focus to another window we are
now sending down events for all pointers that would
be delivered tot his window to ensure it sees a consistent
event stream.

Bug:146535667

Test: atest CtsAutofillTestCases
      atest inputflinger_tests
      atest com.android.server.wm.DragDropControllerTests
      atest android.server.wm.DragDropTest
      atest android.server.wm.CrossAppDragAndDropTests

      added dedicated tests to inputflinger_tests

Change-Id: Ic745df966dbd873da94bb6d8061552ce1a5b27b0
parent 011eee1c
Loading
Loading
Loading
Loading
+74 −13
Original line number Diff line number Diff line
@@ -2686,6 +2686,19 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
          connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason,
          options.mode);
#endif

    InputTarget target;
    sp<InputWindowHandle> windowHandle =
            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
    if (windowHandle != nullptr) {
        const InputWindowInfo* windowInfo = windowHandle->getInfo();
        target.setDefaultPointerInfo(-windowInfo->frameLeft, -windowInfo->frameTop,
                                     windowInfo->windowXScale, windowInfo->windowYScale);
        target.globalScaleFactor = windowInfo->globalScaleFactor;
    }
    target.inputChannel = connection->inputChannel;
    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;

    for (size_t i = 0; i < cancelationEvents.size(); i++) {
        EventEntry* cancelationEventEntry = cancelationEvents[i];
        switch (cancelationEventEntry->type) {
@@ -2711,6 +2724,35 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
            }
        }

        enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
                                   target, InputTarget::FLAG_DISPATCH_AS_IS);

        cancelationEventEntry->release();
    }

    startDispatchCycleLocked(currentTime, connection);
}

void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
        const sp<Connection>& connection) {
    if (connection->status == Connection::STATUS_BROKEN) {
        return;
    }

    nsecs_t currentTime = now();

    std::vector<EventEntry*> downEvents =
            connection->inputState.synthesizePointerDownEvents(currentTime);

    if (downEvents.empty()) {
        return;
    }

#if DEBUG_OUTBOUND_EVENT_DETAILS
        ALOGD("channel '%s' ~ Synthesized %zu down events to ensure consistent event stream.",
              connection->getInputChannelName().c_str(), downEvents.size());
#endif

    InputTarget target;
    sp<InputWindowHandle> windowHandle =
            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
@@ -2723,10 +2765,28 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
    target.inputChannel = connection->inputChannel;
    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;

        enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
    for (EventEntry* downEventEntry : downEvents) {
        switch (downEventEntry->type) {
            case EventEntry::Type::MOTION: {
                logOutboundMotionDetails("down - ",
                        static_cast<const MotionEntry&>(*downEventEntry));
                break;
            }

            case EventEntry::Type::KEY:
            case EventEntry::Type::FOCUS:
            case EventEntry::Type::CONFIGURATION_CHANGED:
            case EventEntry::Type::DEVICE_RESET: {
                LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
                                     EventEntry::typeToString(downEventEntry->type));
                break;
            }
        }

        enqueueDispatchEntryLocked(connection, downEventEntry, // increments ref
                                   target, InputTarget::FLAG_DISPATCH_AS_IS);

        cancelationEventEntry->release();
        downEventEntry->release();
    }

    startDispatchCycleLocked(currentTime, connection);
@@ -3770,11 +3830,12 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<
        sp<Connection> fromConnection = getConnectionLocked(fromToken);
        sp<Connection> toConnection = getConnectionLocked(toToken);
        if (fromConnection != nullptr && toConnection != nullptr) {
            fromConnection->inputState.copyPointerStateTo(toConnection->inputState);
            fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
            CancelationOptions
                    options(CancelationOptions::CANCEL_POINTER_EVENTS,
                            "transferring touch focus from this window to another window");
            synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
            synthesizePointerDownEventsForConnectionLocked(toConnection);
        }

        if (DEBUG_FOCUS) {
+3 −0
Original line number Diff line number Diff line
@@ -417,6 +417,9 @@ private:
                                                        const CancelationOptions& options)
            REQUIRES(mLock);

    void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection)
            REQUIRES(mLock);

    // Splitting motion events across windows.
    MotionEntry* splitMotionEvent(const MotionEntry& originalMotionEntry, BitSet32 pointerIds);

+84 −10
Original line number Diff line number Diff line
@@ -145,11 +145,14 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f
                // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP.
                return true;
            }

            if (index >= 0) {
                MotionMemento& memento = mMotionMementos[index];
                if (memento.firstNewPointerIdx < 0) {
                    memento.setPointers(entry);
                    return true;
                }
            }
#if DEBUG_OUTBOUND_EVENT_DETAILS
            ALOGD("Dropping inconsistent motion pointer up/down or move event: "
                  "deviceId=%d, source=%08x, displayId=%" PRId32 ", actionMasked=%d",
@@ -249,6 +252,17 @@ void InputState::MotionMemento::setPointers(const MotionEntry& entry) {
    }
}

void InputState::MotionMemento::mergePointerStateTo(MotionMemento& other) const {
    for (uint32_t i = 0; i < pointerCount; i++) {
        if (other.firstNewPointerIdx < 0) {
            other.firstNewPointerIdx = other.pointerCount;
        }
        other.pointerProperties[other.pointerCount].copyFrom(pointerProperties[i]);
        other.pointerCoords[other.pointerCount].copyFrom(pointerCoords[i]);
        other.pointerCount++;
    }
}

std::vector<EventEntry*> InputState::synthesizeCancelationEvents(
        nsecs_t currentTime, const CancelationOptions& options) {
    std::vector<EventEntry*> events;
@@ -282,30 +296,90 @@ std::vector<EventEntry*> InputState::synthesizeCancelationEvents(
    return events;
}

std::vector<EventEntry*> InputState::synthesizePointerDownEvents(nsecs_t currentTime) {
    std::vector<EventEntry*> events;
    for (MotionMemento& memento : mMotionMementos) {
        if (!(memento.source & AINPUT_SOURCE_CLASS_POINTER)) {
            continue;
        }

        if (memento.firstNewPointerIdx < 0) {
            continue;
        }

        uint32_t pointerCount = 0;
        PointerProperties pointerProperties[MAX_POINTERS];
        PointerCoords pointerCoords[MAX_POINTERS];

        // We will deliver all pointers the target already knows about
        for (uint32_t i = 0; i < static_cast<uint32_t>(memento.firstNewPointerIdx); i++) {
            pointerProperties[i].copyFrom(memento.pointerProperties[i]);
            pointerCoords[i].copyFrom(memento.pointerCoords[i]);
            pointerCount++;
        }

        // We will send explicit events for all pointers the target doesn't know about
        for (uint32_t i = static_cast<uint32_t>(memento.firstNewPointerIdx);
                i < memento.pointerCount; i++) {

            pointerProperties[i].copyFrom(memento.pointerProperties[i]);
            pointerCoords[i].copyFrom(memento.pointerCoords[i]);
            pointerCount++;

            // Down only if the first pointer, pointer down otherwise
            const int32_t action = (pointerCount <= 1)
                    ? AMOTION_EVENT_ACTION_DOWN
                    : AMOTION_EVENT_ACTION_POINTER_DOWN
                            | (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);

            events.push_back(new MotionEntry(SYNTHESIZED_EVENT_SEQUENCE_NUM, currentTime,
                                             memento.deviceId, memento.source, memento.displayId,
                                             memento.policyFlags, action, 0 /*actionButton*/,
                                             memento.flags, AMETA_NONE, 0 /*buttonState*/,
                                             MotionClassification::NONE,
                                             AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                             memento.yPrecision, memento.xCursorPosition,
                                             memento.yCursorPosition, memento.downTime,
                                             pointerCount, pointerProperties,
                                             pointerCoords, 0 /*xOffset*/, 0 /*yOffset*/));
        }

        memento.firstNewPointerIdx = INVALID_POINTER_INDEX;
    }

    return events;
}

void InputState::clear() {
    mKeyMementos.clear();
    mMotionMementos.clear();
    mFallbackKeys.clear();
}

void InputState::copyPointerStateTo(InputState& other) const {
void InputState::mergePointerStateTo(InputState& other) {
    for (size_t i = 0; i < mMotionMementos.size(); i++) {
        const MotionMemento& memento = mMotionMementos[i];
        MotionMemento& memento = mMotionMementos[i];
        // Since we support split pointers we need to merge touch events
        // from the same source + device + screen.
        if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
            for (size_t j = 0; j < other.mMotionMementos.size();) {
                const MotionMemento& otherMemento = other.mMotionMementos[j];
            bool merged = false;
            for (size_t j = 0; j < other.mMotionMementos.size(); j++) {
                MotionMemento& otherMemento = other.mMotionMementos[j];
                if (memento.deviceId == otherMemento.deviceId &&
                    memento.source == otherMemento.source &&
                    memento.displayId == otherMemento.displayId) {
                    other.mMotionMementos.erase(other.mMotionMementos.begin() + j);
                } else {
                    j += 1;
                    memento.mergePointerStateTo(otherMemento);
                    merged = true;
                    break;
                }
            }
            if (!merged) {
                memento.firstNewPointerIdx = 0;
                other.mMotionMementos.push_back(memento);
            }
        }
    }
}

int32_t InputState::getFallbackKey(int32_t originalKeyCode) {
    ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
+10 −2
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@

namespace android::inputdispatcher {

static constexpr int32_t INVALID_POINTER_INDEX = -1;

/* Tracks dispatched key and motion event state so that cancellation events can be
 * synthesized when events are dropped. */
class InputState {
@@ -52,11 +54,14 @@ public:
    std::vector<EventEntry*> synthesizeCancelationEvents(nsecs_t currentTime,
                                                         const CancelationOptions& options);

    // Synthesizes down events for the current state.
    std::vector<EventEntry*> synthesizePointerDownEvents(nsecs_t currentTime);

    // Clears the current state.
    void clear();

    // Copies pointer-related parts of the input state to another instance.
    void copyPointerStateTo(InputState& other) const;
    // Merges pointer-related parts of the input state into another instance.
    void mergePointerStateTo(InputState& other);

    // Gets the fallback key associated with a keycode.
    // Returns -1 if none.
@@ -97,10 +102,13 @@ private:
        uint32_t pointerCount;
        PointerProperties pointerProperties[MAX_POINTERS];
        PointerCoords pointerCoords[MAX_POINTERS];
        // Track for which pointers the target doesn't know about.
        int32_t firstNewPointerIdx = INVALID_POINTER_INDEX;
        bool hovering;
        uint32_t policyFlags;

        void setPointers(const MotionEntry& entry);
        void mergePointerStateTo(MotionMemento& other) const;
    };

    std::vector<KeyMemento> mKeyMementos;
+185 −2
Original line number Diff line number Diff line
@@ -594,12 +594,40 @@ public:
                     expectedFlags);
    }

    void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
    void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
            int32_t expectedFlags = 0) {
        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId,
                     expectedFlags);
    }

    void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
            int32_t expectedFlags = 0) {
        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId,
                     expectedFlags);
    }

    void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
            int32_t expectedFlags = 0) {
        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
                     expectedFlags);
    }

    void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
    void consumeMotionPointerDown(int32_t pointerIdx,
            int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) {
        int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN
                | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
        consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
    }

    void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
            int32_t expectedFlags = 0) {
        int32_t action = AMOTION_EVENT_ACTION_POINTER_UP
                | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
        consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
    }

    void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
            int32_t expectedFlags = 0) {
        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
                     expectedFlags);
    }
@@ -923,6 +951,161 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) {
                         0 /*expectedFlags*/);
}

TEST_F(InputDispatcherTest, TransferTouchFocus_OnePointer) {
    sp<FakeApplicationHandle> application = new FakeApplicationHandle();

    // Create a couple of windows
    sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
            "First Window", ADISPLAY_ID_DEFAULT);
    sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
            "Second Window", ADISPLAY_ID_DEFAULT);

    // Add the windows to the dispatcher
    mDispatcher->setInputWindows({firstWindow, secondWindow}, ADISPLAY_ID_DEFAULT);

    // Send down to the first window
    NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
    mDispatcher->notifyMotion(&downMotionArgs);
    // Only the first window should get the down event
    firstWindow->consumeMotionDown();
    secondWindow->assertNoEvents();

    // Transfer touch focus to the second window
    mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken());
    // The first window gets cancel and the second gets down
    firstWindow->consumeMotionCancel();
    secondWindow->consumeMotionDown();

    // Send up event to the second window
    NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
    mDispatcher->notifyMotion(&upMotionArgs);
    // The first  window gets no events and the second gets up
    firstWindow->assertNoEvents();
    secondWindow->consumeMotionUp();
}

TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointerNoSplitTouch) {
    sp<FakeApplicationHandle> application = new FakeApplicationHandle();

    PointF touchPoint = {10, 10};

    // Create a couple of windows
    sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
            "First Window", ADISPLAY_ID_DEFAULT);
    sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
            "Second Window", ADISPLAY_ID_DEFAULT);

    // Add the windows to the dispatcher
    mDispatcher->setInputWindows({firstWindow, secondWindow}, ADISPLAY_ID_DEFAULT);

    // Send down to the first window
    NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint});
    mDispatcher->notifyMotion(&downMotionArgs);
    // Only the first window should get the down event
    firstWindow->consumeMotionDown();
    secondWindow->assertNoEvents();

    // Send pointer down to the first window
    NotifyMotionArgs pointerDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN
            | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint});
    mDispatcher->notifyMotion(&pointerDownMotionArgs);
    // Only the first window should get the pointer down event
    firstWindow->consumeMotionPointerDown(1);
    secondWindow->assertNoEvents();

    // Transfer touch focus to the second window
    mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken());
    // The first window gets cancel and the second gets down and pointer down
    firstWindow->consumeMotionCancel();
    secondWindow->consumeMotionDown();
    secondWindow->consumeMotionPointerDown(1);

    // Send pointer up to the second window
    NotifyMotionArgs pointerUpMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP
            | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint});
    mDispatcher->notifyMotion(&pointerUpMotionArgs);
    // The first window gets nothing and the second gets pointer up
    firstWindow->assertNoEvents();
    secondWindow->consumeMotionPointerUp(1);

    // Send up event to the second window
    NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
    mDispatcher->notifyMotion(&upMotionArgs);
    // The first window gets nothing and the second gets up
    firstWindow->assertNoEvents();
    secondWindow->consumeMotionUp();
}

TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) {
    sp<FakeApplicationHandle> application = new FakeApplicationHandle();

    // Create a non touch modal window that supports split touch
    sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
            "First Window", ADISPLAY_ID_DEFAULT);
    firstWindow->setFrame(Rect(0, 0, 600, 400));
    firstWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL
            | InputWindowInfo::FLAG_SPLIT_TOUCH);

    // Create a non touch modal window that supports split touch
    sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
            "Second Window", ADISPLAY_ID_DEFAULT);
    secondWindow->setFrame(Rect(0, 400, 600, 800));
    secondWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL
            | InputWindowInfo::FLAG_SPLIT_TOUCH);

    // Add the windows to the dispatcher
    mDispatcher->setInputWindows({firstWindow, secondWindow}, ADISPLAY_ID_DEFAULT);

    PointF pointInFirst = {300, 200};
    PointF pointInSecond = {300, 600};

    // Send down to the first window
    NotifyMotionArgs firstDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst});
    mDispatcher->notifyMotion(&firstDownMotionArgs);
    // Only the first window should get the down event
    firstWindow->consumeMotionDown();
    secondWindow->assertNoEvents();

    // Send down to the second window
    NotifyMotionArgs secondDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN
            | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond});
    mDispatcher->notifyMotion(&secondDownMotionArgs);
    // The first window gets a move and the second a down
    firstWindow->consumeMotionMove();
    secondWindow->consumeMotionDown();

    // Transfer touch focus to the second window
    mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken());
    // The first window gets cancel and the new gets pointer down (it already saw down)
    firstWindow->consumeMotionCancel();
    secondWindow->consumeMotionPointerDown(1);

    // Send pointer up to the second window
    NotifyMotionArgs pointerUpMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP
            | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond});
    mDispatcher->notifyMotion(&pointerUpMotionArgs);
    // The first window gets nothing and the second gets pointer up
    firstWindow->assertNoEvents();
    secondWindow->consumeMotionPointerUp(1);

    // Send up event to the second window
    NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
    mDispatcher->notifyMotion(&upMotionArgs);
    // The first window gets nothing and the second gets up
    firstWindow->assertNoEvents();
    secondWindow->consumeMotionUp();
}

TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) {
    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
    sp<FakeWindowHandle> window =