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

Commit f105bb11 authored by Chavi Weingarten's avatar Chavi Weingarten Committed by Android (Google) Code Review
Browse files

Merge "Handle different scale and offset for pointers in InputTarget."

parents 5d248dd9 5d22a235
Loading
Loading
Loading
Loading
+106 −48
Original line number Diff line number Diff line
@@ -256,6 +256,67 @@ static bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry) {
    return currentTime - entry.eventTime >= STALE_EVENT_TIMEOUT;
}

static std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget,
                                                          EventEntry* eventEntry,
                                                          int32_t inputTargetFlags) {
    if (inputTarget.useDefaultPointerInfo()) {
        const PointerInfo& pointerInfo = inputTarget.getDefaultPointerInfo();
        return std::make_unique<DispatchEntry>(eventEntry, // increments ref
                                               inputTargetFlags, pointerInfo.xOffset,
                                               pointerInfo.yOffset, inputTarget.globalScaleFactor,
                                               pointerInfo.windowXScale, pointerInfo.windowYScale);
    }

    ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
    const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);

    PointerCoords pointerCoords[MAX_POINTERS];

    // Use the first pointer information to normalize all other pointers. This could be any pointer
    // as long as all other pointers are normalized to the same value and the final DispatchEntry
    // uses the offset and scale for the normalized pointer.
    const PointerInfo& firstPointerInfo =
            inputTarget.pointerInfos[inputTarget.pointerIds.firstMarkedBit()];

    // Iterate through all pointers in the event to normalize against the first.
    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.pointerCount; pointerIndex++) {
        const PointerProperties& pointerProperties = motionEntry.pointerProperties[pointerIndex];
        uint32_t pointerId = uint32_t(pointerProperties.id);
        const PointerInfo& currPointerInfo = inputTarget.pointerInfos[pointerId];

        // The scale factor is the ratio of the current pointers scale to the normalized scale.
        float scaleXDiff = currPointerInfo.windowXScale / firstPointerInfo.windowXScale;
        float scaleYDiff = currPointerInfo.windowYScale / firstPointerInfo.windowYScale;

        pointerCoords[pointerIndex].copyFrom(motionEntry.pointerCoords[pointerIndex]);
        // First apply the current pointers offset to set the window at 0,0
        pointerCoords[pointerIndex].applyOffset(currPointerInfo.xOffset, currPointerInfo.yOffset);
        // Next scale the coordinates.
        pointerCoords[pointerIndex].scale(1, scaleXDiff, scaleYDiff);
        // Lastly, offset the coordinates so they're in the normalized pointer's frame.
        pointerCoords[pointerIndex].applyOffset(-firstPointerInfo.xOffset,
                                                -firstPointerInfo.yOffset);
    }

    MotionEntry* combinedMotionEntry =
            new MotionEntry(motionEntry.sequenceNum, motionEntry.eventTime, motionEntry.deviceId,
                            motionEntry.source, motionEntry.displayId, motionEntry.policyFlags,
                            motionEntry.action, motionEntry.actionButton, motionEntry.flags,
                            motionEntry.metaState, motionEntry.buttonState,
                            motionEntry.classification, motionEntry.edgeFlags,
                            motionEntry.xPrecision, motionEntry.yPrecision,
                            motionEntry.xCursorPosition, motionEntry.yCursorPosition,
                            motionEntry.downTime, motionEntry.pointerCount,
                            motionEntry.pointerProperties, pointerCoords, 0 /* xOffset */,
                            0 /* yOffset */);

    return std::make_unique<DispatchEntry>(combinedMotionEntry, // increments ref
                                           inputTargetFlags, firstPointerInfo.xOffset,
                                           firstPointerInfo.yOffset, inputTarget.globalScaleFactor,
                                           firstPointerInfo.windowXScale,
                                           firstPointerInfo.windowYScale);
}

// --- InputDispatcherThread ---

class InputDispatcher::InputDispatcherThread : public Thread {
@@ -1111,7 +1172,7 @@ void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* event
        sp<Connection> connection =
                getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
        if (connection != nullptr) {
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
        } else {
            if (DEBUG_FOCUS) {
                ALOGD("Dropping event delivery to target with channel '%s' because it "
@@ -1785,23 +1846,34 @@ Unresponsive:
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
                                            int32_t targetFlags, BitSet32 pointerIds,
                                            std::vector<InputTarget>& inputTargets) {
    std::vector<InputTarget>::iterator it =
            std::find_if(inputTargets.begin(), inputTargets.end(),
                         [&windowHandle](const InputTarget& inputTarget) {
                             return inputTarget.inputChannel->getConnectionToken() ==
                                     windowHandle->getToken();
                         });

    const InputWindowInfo* windowInfo = windowHandle->getInfo();

    if (it == inputTargets.end()) {
        InputTarget inputTarget;
        sp<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
        if (inputChannel == nullptr) {
            ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
            return;
        }
        inputTarget.inputChannel = inputChannel;
        inputTarget.flags = targetFlags;
        inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
        inputTargets.push_back(inputTarget);
        it = inputTargets.end() - 1;
    }

    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    InputTarget target;
    target.inputChannel = inputChannel;
    target.flags = targetFlags;
    target.xOffset = -windowInfo->frameLeft;
    target.yOffset = -windowInfo->frameTop;
    target.globalScaleFactor = windowInfo->globalScaleFactor;
    target.windowXScale = windowInfo->windowXScale;
    target.windowYScale = windowInfo->windowYScale;
    target.pointerIds = pointerIds;
    inputTargets.push_back(target);
    ALOG_ASSERT(it->flags == targetFlags);
    ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);

    it->addPointers(pointerIds, -windowInfo->frameLeft, -windowInfo->frameTop,
                    windowInfo->windowXScale, windowInfo->windowYScale);
}

void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
@@ -1824,10 +1896,7 @@ void InputDispatcher::addMonitoringTargetLocked(const Monitor& monitor, float xO
    InputTarget target;
    target.inputChannel = monitor.inputChannel;
    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
    target.xOffset = xOffset;
    target.yOffset = yOffset;
    target.pointerIds.clear();
    target.globalScaleFactor = 1.0f;
    target.setDefaultPointerInfo(xOffset, yOffset, 1 /* windowXScale */, 1 /* windowYScale */);
    inputTargets.push_back(target);
}

@@ -2045,7 +2114,7 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
                                                 const sp<Connection>& connection,
                                                 EventEntry* eventEntry,
                                                 const InputTarget* inputTarget) {
                                                 const InputTarget& inputTarget) {
    if (ATRACE_ENABLED()) {
        std::string message =
                StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, sequenceNum=%" PRIu32 ")",
@@ -2056,9 +2125,9 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
    ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
          "xOffset=%f, yOffset=%f, globalScaleFactor=%f, "
          "windowScaleFactor=(%f, %f), pointerIds=0x%x",
          connection->getInputChannelName().c_str(), inputTarget->flags, inputTarget->xOffset,
          inputTarget->yOffset, inputTarget->globalScaleFactor, inputTarget->windowXScale,
          inputTarget->windowYScale, inputTarget->pointerIds.value);
          connection->getInputChannelName().c_str(), inputTarget.flags, inputTarget.xOffset,
          inputTarget.yOffset, inputTarget.globalScaleFactor, inputTarget.windowXScale,
          inputTarget.windowYScale, inputTarget.pointerIds.value);
#endif

    // Skip this event if the connection status is not normal.
@@ -2072,13 +2141,13 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
    }

    // Split a motion event if needed.
    if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
    if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
        ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);

        const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
        if (inputTarget->pointerIds.count() != originalMotionEntry.pointerCount) {
        if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
            MotionEntry* splitMotionEntry =
                    splitMotionEvent(originalMotionEntry, inputTarget->pointerIds);
                    splitMotionEvent(originalMotionEntry, inputTarget.pointerIds);
            if (!splitMotionEntry) {
                return; // split event was dropped
            }
@@ -2100,7 +2169,7 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
                                                   const sp<Connection>& connection,
                                                   EventEntry* eventEntry,
                                                   const InputTarget* inputTarget) {
                                                   const InputTarget& inputTarget) {
    if (ATRACE_ENABLED()) {
        std::string message =
                StringPrintf("enqueueDispatchEntriesLocked(inputChannel=%s, sequenceNum=%" PRIu32
@@ -2133,7 +2202,7 @@ void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,

void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
                                                 EventEntry* eventEntry,
                                                 const InputTarget* inputTarget,
                                                 const InputTarget& inputTarget,
                                                 int32_t dispatchMode) {
    if (ATRACE_ENABLED()) {
        std::string message = StringPrintf("enqueueDispatchEntry(inputChannel=%s, dispatchMode=%s)",
@@ -2141,7 +2210,7 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connectio
                                           dispatchModeToString(dispatchMode).c_str());
        ATRACE_NAME(message.c_str());
    }
    int32_t inputTargetFlags = inputTarget->flags;
    int32_t inputTargetFlags = inputTarget.flags;
    if (!(inputTargetFlags & dispatchMode)) {
        return;
    }
@@ -2149,11 +2218,8 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connectio

    // This is a new event.
    // Enqueue a new dispatch entry onto the outbound queue for this connection.
    DispatchEntry* dispatchEntry =
            new DispatchEntry(eventEntry, // increments ref
                              inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
                              inputTarget->globalScaleFactor, inputTarget->windowXScale,
                              inputTarget->windowYScale);
    std::unique_ptr<DispatchEntry> dispatchEntry =
            createDispatchEntry(inputTarget, eventEntry, inputTargetFlags);

    // Apply target flags and update the connection's input state.
    switch (eventEntry->type) {
@@ -2168,7 +2234,6 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connectio
                ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
                      connection->getInputChannelName().c_str());
#endif
                delete dispatchEntry;
                return; // skip the inconsistent event
            }
            break;
@@ -2215,12 +2280,11 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connectio
                      "event",
                      connection->getInputChannelName().c_str());
#endif
                delete dispatchEntry;
                return; // skip the inconsistent event
            }

            dispatchPointerDownOutsideFocus(motionEntry.source, dispatchEntry->resolvedAction,
                                            inputTarget->inputChannel->getConnectionToken());
                                            inputTarget.inputChannel->getConnectionToken());

            break;
        }
@@ -2238,7 +2302,7 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connectio
    }

    // Enqueue the dispatch entry.
    connection->outboundQueue.push_back(dispatchEntry);
    connection->outboundQueue.push_back(dispatchEntry.release());
    traceOutboundQueueLength(connection);
}

@@ -2611,21 +2675,15 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
                    getWindowHandleLocked(connection->inputChannel->getConnectionToken());
            if (windowHandle != nullptr) {
                const InputWindowInfo* windowInfo = windowHandle->getInfo();
                target.xOffset = -windowInfo->frameLeft;
                target.yOffset = -windowInfo->frameTop;
                target.setDefaultPointerInfo(-windowInfo->frameLeft, -windowInfo->frameTop,
                                             windowInfo->windowXScale, windowInfo->windowYScale);
                target.globalScaleFactor = windowInfo->globalScaleFactor;
                target.windowXScale = windowInfo->windowXScale;
                target.windowYScale = windowInfo->windowYScale;
            } else {
                target.xOffset = 0;
                target.yOffset = 0;
                target.globalScaleFactor = 1.0f;
            }
            target.inputChannel = connection->inputChannel;
            target.flags = InputTarget::FLAG_DISPATCH_AS_IS;

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

            cancelationEventEntry->release();
        }
+3 −3
Original line number Diff line number Diff line
@@ -378,13 +378,13 @@ private:
    // with the mutex held makes it easier to ensure that connection invariants are maintained.
    // If needed, the methods post commands to run later once the critical bits are done.
    void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
                                    EventEntry* eventEntry, const InputTarget* inputTarget)
                                    EventEntry* eventEntry, const InputTarget& inputTarget)
            REQUIRES(mLock);
    void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection,
                                      EventEntry* eventEntry, const InputTarget* inputTarget)
                                      EventEntry* eventEntry, const InputTarget& inputTarget)
            REQUIRES(mLock);
    void enqueueDispatchEntryLocked(const sp<Connection>& connection, EventEntry* eventEntry,
                                    const InputTarget* inputTarget, int32_t dispatchMode)
                                    const InputTarget& inputTarget, int32_t dispatchMode)
            REQUIRES(mLock);
    void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection)
            REQUIRES(mLock);
+39 −0
Original line number Diff line number Diff line
@@ -42,4 +42,43 @@ std::string dispatchModeToString(int32_t dispatchMode) {
    return StringPrintf("%" PRId32, dispatchMode);
}

void InputTarget::addPointers(BitSet32 newPointerIds, float xOffset, float yOffset,
                              float windowXScale, float windowYScale) {
    // The pointerIds can be empty, but still a valid InputTarget. This can happen for Monitors
    // and non splittable windows since we will just use all the pointers from the input event.
    if (newPointerIds.isEmpty()) {
        setDefaultPointerInfo(xOffset, yOffset, windowXScale, windowYScale);
        return;
    }

    // Ensure that the new set of pointers doesn't overlap with the current set of pointers.
    ALOG_ASSERT((pointerIds & newPointerIds) == 0);

    pointerIds |= newPointerIds;
    while (!newPointerIds.isEmpty()) {
        int32_t pointerId = newPointerIds.clearFirstMarkedBit();
        pointerInfos[pointerId].xOffset = xOffset;
        pointerInfos[pointerId].yOffset = yOffset;
        pointerInfos[pointerId].windowXScale = windowXScale;
        pointerInfos[pointerId].windowYScale = windowYScale;
    }
}

void InputTarget::setDefaultPointerInfo(float xOffset, float yOffset, float windowXScale,
                                        float windowYScale) {
    pointerIds.clear();
    pointerInfos[0].xOffset = xOffset;
    pointerInfos[0].yOffset = yOffset;
    pointerInfos[0].windowXScale = windowXScale;
    pointerInfos[0].windowYScale = windowYScale;
}

bool InputTarget::useDefaultPointerInfo() const {
    return pointerIds.isEmpty();
}

const PointerInfo& InputTarget::getDefaultPointerInfo() const {
    return pointerInfos[0];
}

} // namespace android::inputdispatcher
+39 −8
Original line number Diff line number Diff line
@@ -23,6 +23,22 @@

namespace android::inputdispatcher {

/*
 * Information about each pointer for an InputTarget. This includes offset and scale so
 * all pointers can be normalized to a single offset and scale.
 */
struct PointerInfo {
    // The x and y offset to add to a MotionEvent as it is delivered.
    // (ignored for KeyEvents)
    float xOffset = 0.0f;
    float yOffset = 0.0f;

    // Scaling factor to apply to MotionEvent as it is delivered.
    // (ignored for KeyEvents)
    float windowXScale = 1.0f;
    float windowYScale = 1.0f;
};

/*
 * An input target specifies how an input event is to be dispatched to a particular window
 * including the window's input channel, control flags, a timeout, and an X / Y offset to
@@ -95,20 +111,35 @@ struct InputTarget {
    // Flags for the input target.
    int32_t flags = 0;

    // The x and y offset to add to a MotionEvent as it is delivered.
    // (ignored for KeyEvents)
    float xOffset = 0.0f;
    float yOffset = 0.0f;

    // Scaling factor to apply to MotionEvent as it is delivered.
    // (ignored for KeyEvents)
    float globalScaleFactor = 1.0f;
    float windowXScale = 1.0f;
    float windowYScale = 1.0f;

    // The subset of pointer ids to include in motion events dispatched to this input target
    // if FLAG_SPLIT is set.
    BitSet32 pointerIds{};
    BitSet32 pointerIds;
    // The data is stored by the pointerId. Use the marked bits in pointerIds to look up PointerInfo
    // per pointerId.
    PointerInfo pointerInfos[MAX_POINTERS];

    void addPointers(BitSet32 pointerIds, float xOffset, float yOffset, float windowXScale,
                     float windowYScale);
    void setDefaultPointerInfo(float xOffset, float yOffset, float windowXScale,
                               float windowYScale);

    /**
     * Returns whether the default pointer information should be used. This will be true when the
     * InputTarget doesn't have any bits set in the pointerIds bitset. This can happen for monitors
     * and non splittable windows since we want all pointers for the EventEntry to go to this
     * target.
     */
    bool useDefaultPointerInfo() const;

    /**
     * Returns the default PointerInfo object. This should be used when useDefaultPointerInfo is
     * true.
     */
    const PointerInfo& getDefaultPointerInfo() const;
};

std::string dispatchModeToString(int32_t dispatchMode);
+103 −0
Original line number Diff line number Diff line
@@ -1439,4 +1439,107 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchDifferentScale) {
    consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_DOWN, {expectedPoint});
}

TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentScale) {
    mWindow2->setWindowScale(0.5f, 0.5f);

    // Touch Window 1
    std::vector<PointF> touchedPoints = {PointF{10, 10}};
    std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])};

    NotifyMotionArgs motionArgs =
            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                               ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);
    consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_DOWN, expectedPoints);

    // Touch Window 2
    int32_t actionPointerDown =
            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
    touchedPoints.emplace_back(PointF{150, 150});
    expectedPoints.emplace_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));

    motionArgs = generateMotionArgs(actionPointerDown, AINPUT_SOURCE_TOUCHSCREEN,
                                    ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);

    // Consuming from window1 since it's the window that has the InputReceiver
    consumeMotionEvent(mWindow1, actionPointerDown, expectedPoints);
}

TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentScale) {
    mWindow2->setWindowScale(0.5f, 0.5f);

    // Touch Window 1
    std::vector<PointF> touchedPoints = {PointF{10, 10}};
    std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])};

    NotifyMotionArgs motionArgs =
            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                               ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);
    consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_DOWN, expectedPoints);

    // Touch Window 2
    int32_t actionPointerDown =
            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
    touchedPoints.emplace_back(PointF{150, 150});
    expectedPoints.emplace_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));

    motionArgs = generateMotionArgs(actionPointerDown, AINPUT_SOURCE_TOUCHSCREEN,
                                    ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);

    // Consuming from window1 since it's the window that has the InputReceiver
    consumeMotionEvent(mWindow1, actionPointerDown, expectedPoints);

    // Move both windows
    touchedPoints = {{20, 20}, {175, 175}};
    expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]),
                      getPointInWindow(mWindow2->getInfo(), touchedPoints[1])};

    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                    ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);

    consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_MOVE, expectedPoints);
}

TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithScale) {
    mWindow1->setWindowScale(0.5f, 0.5f);

    // Touch Window 1
    std::vector<PointF> touchedPoints = {PointF{10, 10}};
    std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])};

    NotifyMotionArgs motionArgs =
            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                               ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);
    consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_DOWN, expectedPoints);

    // Touch Window 2
    int32_t actionPointerDown =
            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
    touchedPoints.emplace_back(PointF{150, 150});
    expectedPoints.emplace_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));

    motionArgs = generateMotionArgs(actionPointerDown, AINPUT_SOURCE_TOUCHSCREEN,
                                    ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);

    // Consuming from window1 since it's the window that has the InputReceiver
    consumeMotionEvent(mWindow1, actionPointerDown, expectedPoints);

    // Move both windows
    touchedPoints = {{20, 20}, {175, 175}};
    expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]),
                      getPointInWindow(mWindow2->getInfo(), touchedPoints[1])};

    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
                                    ADISPLAY_ID_DEFAULT, touchedPoints);
    mDispatcher->notifyMotion(&motionArgs);

    consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_MOVE, expectedPoints);
}

} // namespace android::inputdispatcher