Loading services/inputflinger/dispatcher/InputDispatcher.cpp +74 −13 Original line number Original line Diff line number Diff line Loading @@ -2686,6 +2686,19 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason, connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason, options.mode); options.mode); #endif #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++) { for (size_t i = 0; i < cancelationEvents.size(); i++) { EventEntry* cancelationEventEntry = cancelationEvents[i]; EventEntry* cancelationEventEntry = cancelationEvents[i]; switch (cancelationEventEntry->type) { switch (cancelationEventEntry->type) { Loading @@ -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; InputTarget target; sp<InputWindowHandle> windowHandle = sp<InputWindowHandle> windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken()); getWindowHandleLocked(connection->inputChannel->getConnectionToken()); Loading @@ -2723,10 +2765,28 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( target.inputChannel = connection->inputChannel; target.inputChannel = connection->inputChannel; target.flags = InputTarget::FLAG_DISPATCH_AS_IS; 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); target, InputTarget::FLAG_DISPATCH_AS_IS); cancelationEventEntry->release(); downEventEntry->release(); } } startDispatchCycleLocked(currentTime, connection); startDispatchCycleLocked(currentTime, connection); Loading Loading @@ -3770,11 +3830,12 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< sp<Connection> fromConnection = getConnectionLocked(fromToken); sp<Connection> fromConnection = getConnectionLocked(fromToken); sp<Connection> toConnection = getConnectionLocked(toToken); sp<Connection> toConnection = getConnectionLocked(toToken); if (fromConnection != nullptr && toConnection != nullptr) { if (fromConnection != nullptr && toConnection != nullptr) { fromConnection->inputState.copyPointerStateTo(toConnection->inputState); fromConnection->inputState.mergePointerStateTo(toConnection->inputState); CancelationOptions CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, options(CancelationOptions::CANCEL_POINTER_EVENTS, "transferring touch focus from this window to another window"); "transferring touch focus from this window to another window"); synthesizeCancelationEventsForConnectionLocked(fromConnection, options); synthesizeCancelationEventsForConnectionLocked(fromConnection, options); synthesizePointerDownEventsForConnectionLocked(toConnection); } } if (DEBUG_FOCUS) { if (DEBUG_FOCUS) { Loading services/inputflinger/dispatcher/InputDispatcher.h +3 −0 Original line number Original line Diff line number Diff line Loading @@ -417,6 +417,9 @@ private: const CancelationOptions& options) const CancelationOptions& options) REQUIRES(mLock); REQUIRES(mLock); void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection) REQUIRES(mLock); // Splitting motion events across windows. // Splitting motion events across windows. MotionEntry* splitMotionEvent(const MotionEntry& originalMotionEntry, BitSet32 pointerIds); MotionEntry* splitMotionEvent(const MotionEntry& originalMotionEntry, BitSet32 pointerIds); Loading services/inputflinger/dispatcher/InputState.cpp +84 −10 Original line number Original line Diff line number Diff line Loading @@ -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. // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP. return true; return true; } } if (index >= 0) { if (index >= 0) { MotionMemento& memento = mMotionMementos[index]; MotionMemento& memento = mMotionMementos[index]; if (memento.firstNewPointerIdx < 0) { memento.setPointers(entry); memento.setPointers(entry); return true; return true; } } } #if DEBUG_OUTBOUND_EVENT_DETAILS #if DEBUG_OUTBOUND_EVENT_DETAILS ALOGD("Dropping inconsistent motion pointer up/down or move event: " ALOGD("Dropping inconsistent motion pointer up/down or move event: " "deviceId=%d, source=%08x, displayId=%" PRId32 ", actionMasked=%d", "deviceId=%d, source=%08x, displayId=%" PRId32 ", actionMasked=%d", Loading Loading @@ -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( std::vector<EventEntry*> InputState::synthesizeCancelationEvents( nsecs_t currentTime, const CancelationOptions& options) { nsecs_t currentTime, const CancelationOptions& options) { std::vector<EventEntry*> events; std::vector<EventEntry*> events; Loading Loading @@ -282,30 +296,90 @@ std::vector<EventEntry*> InputState::synthesizeCancelationEvents( return events; 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() { void InputState::clear() { mKeyMementos.clear(); mKeyMementos.clear(); mMotionMementos.clear(); mMotionMementos.clear(); mFallbackKeys.clear(); mFallbackKeys.clear(); } } void InputState::copyPointerStateTo(InputState& other) const { void InputState::mergePointerStateTo(InputState& other) { for (size_t i = 0; i < mMotionMementos.size(); i++) { 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) { if (memento.source & AINPUT_SOURCE_CLASS_POINTER) { for (size_t j = 0; j < other.mMotionMementos.size();) { bool merged = false; const MotionMemento& otherMemento = other.mMotionMementos[j]; for (size_t j = 0; j < other.mMotionMementos.size(); j++) { MotionMemento& otherMemento = other.mMotionMementos[j]; if (memento.deviceId == otherMemento.deviceId && if (memento.deviceId == otherMemento.deviceId && memento.source == otherMemento.source && memento.source == otherMemento.source && memento.displayId == otherMemento.displayId) { memento.displayId == otherMemento.displayId) { other.mMotionMementos.erase(other.mMotionMementos.begin() + j); memento.mergePointerStateTo(otherMemento); } else { merged = true; j += 1; break; } } } } if (!merged) { memento.firstNewPointerIdx = 0; other.mMotionMementos.push_back(memento); other.mMotionMementos.push_back(memento); } } } } } } } int32_t InputState::getFallbackKey(int32_t originalKeyCode) { int32_t InputState::getFallbackKey(int32_t originalKeyCode) { ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); Loading services/inputflinger/dispatcher/InputState.h +10 −2 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,8 @@ namespace android::inputdispatcher { namespace android::inputdispatcher { static constexpr int32_t INVALID_POINTER_INDEX = -1; /* Tracks dispatched key and motion event state so that cancellation events can be /* Tracks dispatched key and motion event state so that cancellation events can be * synthesized when events are dropped. */ * synthesized when events are dropped. */ class InputState { class InputState { Loading Loading @@ -52,11 +54,14 @@ public: std::vector<EventEntry*> synthesizeCancelationEvents(nsecs_t currentTime, std::vector<EventEntry*> synthesizeCancelationEvents(nsecs_t currentTime, const CancelationOptions& options); const CancelationOptions& options); // Synthesizes down events for the current state. std::vector<EventEntry*> synthesizePointerDownEvents(nsecs_t currentTime); // Clears the current state. // Clears the current state. void clear(); void clear(); // Copies pointer-related parts of the input state to another instance. // Merges pointer-related parts of the input state into another instance. void copyPointerStateTo(InputState& other) const; void mergePointerStateTo(InputState& other); // Gets the fallback key associated with a keycode. // Gets the fallback key associated with a keycode. // Returns -1 if none. // Returns -1 if none. Loading Loading @@ -97,10 +102,13 @@ private: uint32_t pointerCount; uint32_t pointerCount; PointerProperties pointerProperties[MAX_POINTERS]; PointerProperties pointerProperties[MAX_POINTERS]; PointerCoords pointerCoords[MAX_POINTERS]; PointerCoords pointerCoords[MAX_POINTERS]; // Track for which pointers the target doesn't know about. int32_t firstNewPointerIdx = INVALID_POINTER_INDEX; bool hovering; bool hovering; uint32_t policyFlags; uint32_t policyFlags; void setPointers(const MotionEntry& entry); void setPointers(const MotionEntry& entry); void mergePointerStateTo(MotionMemento& other) const; }; }; std::vector<KeyMemento> mKeyMementos; std::vector<KeyMemento> mKeyMementos; Loading services/inputflinger/tests/InputDispatcher_test.cpp +185 −2 Original line number Original line Diff line number Diff line Loading @@ -594,12 +594,40 @@ public: expectedFlags); 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, consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); 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, consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); expectedFlags); } } Loading Loading @@ -923,6 +951,161 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { 0 /*expectedFlags*/); 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) { TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { sp<FakeApplicationHandle> application = new FakeApplicationHandle(); sp<FakeApplicationHandle> application = new FakeApplicationHandle(); sp<FakeWindowHandle> window = sp<FakeWindowHandle> window = Loading Loading
services/inputflinger/dispatcher/InputDispatcher.cpp +74 −13 Original line number Original line Diff line number Diff line Loading @@ -2686,6 +2686,19 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason, connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason, options.mode); options.mode); #endif #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++) { for (size_t i = 0; i < cancelationEvents.size(); i++) { EventEntry* cancelationEventEntry = cancelationEvents[i]; EventEntry* cancelationEventEntry = cancelationEvents[i]; switch (cancelationEventEntry->type) { switch (cancelationEventEntry->type) { Loading @@ -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; InputTarget target; sp<InputWindowHandle> windowHandle = sp<InputWindowHandle> windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken()); getWindowHandleLocked(connection->inputChannel->getConnectionToken()); Loading @@ -2723,10 +2765,28 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( target.inputChannel = connection->inputChannel; target.inputChannel = connection->inputChannel; target.flags = InputTarget::FLAG_DISPATCH_AS_IS; 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); target, InputTarget::FLAG_DISPATCH_AS_IS); cancelationEventEntry->release(); downEventEntry->release(); } } startDispatchCycleLocked(currentTime, connection); startDispatchCycleLocked(currentTime, connection); Loading Loading @@ -3770,11 +3830,12 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< sp<Connection> fromConnection = getConnectionLocked(fromToken); sp<Connection> fromConnection = getConnectionLocked(fromToken); sp<Connection> toConnection = getConnectionLocked(toToken); sp<Connection> toConnection = getConnectionLocked(toToken); if (fromConnection != nullptr && toConnection != nullptr) { if (fromConnection != nullptr && toConnection != nullptr) { fromConnection->inputState.copyPointerStateTo(toConnection->inputState); fromConnection->inputState.mergePointerStateTo(toConnection->inputState); CancelationOptions CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, options(CancelationOptions::CANCEL_POINTER_EVENTS, "transferring touch focus from this window to another window"); "transferring touch focus from this window to another window"); synthesizeCancelationEventsForConnectionLocked(fromConnection, options); synthesizeCancelationEventsForConnectionLocked(fromConnection, options); synthesizePointerDownEventsForConnectionLocked(toConnection); } } if (DEBUG_FOCUS) { if (DEBUG_FOCUS) { Loading
services/inputflinger/dispatcher/InputDispatcher.h +3 −0 Original line number Original line Diff line number Diff line Loading @@ -417,6 +417,9 @@ private: const CancelationOptions& options) const CancelationOptions& options) REQUIRES(mLock); REQUIRES(mLock); void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection) REQUIRES(mLock); // Splitting motion events across windows. // Splitting motion events across windows. MotionEntry* splitMotionEvent(const MotionEntry& originalMotionEntry, BitSet32 pointerIds); MotionEntry* splitMotionEvent(const MotionEntry& originalMotionEntry, BitSet32 pointerIds); Loading
services/inputflinger/dispatcher/InputState.cpp +84 −10 Original line number Original line Diff line number Diff line Loading @@ -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. // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP. return true; return true; } } if (index >= 0) { if (index >= 0) { MotionMemento& memento = mMotionMementos[index]; MotionMemento& memento = mMotionMementos[index]; if (memento.firstNewPointerIdx < 0) { memento.setPointers(entry); memento.setPointers(entry); return true; return true; } } } #if DEBUG_OUTBOUND_EVENT_DETAILS #if DEBUG_OUTBOUND_EVENT_DETAILS ALOGD("Dropping inconsistent motion pointer up/down or move event: " ALOGD("Dropping inconsistent motion pointer up/down or move event: " "deviceId=%d, source=%08x, displayId=%" PRId32 ", actionMasked=%d", "deviceId=%d, source=%08x, displayId=%" PRId32 ", actionMasked=%d", Loading Loading @@ -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( std::vector<EventEntry*> InputState::synthesizeCancelationEvents( nsecs_t currentTime, const CancelationOptions& options) { nsecs_t currentTime, const CancelationOptions& options) { std::vector<EventEntry*> events; std::vector<EventEntry*> events; Loading Loading @@ -282,30 +296,90 @@ std::vector<EventEntry*> InputState::synthesizeCancelationEvents( return events; 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() { void InputState::clear() { mKeyMementos.clear(); mKeyMementos.clear(); mMotionMementos.clear(); mMotionMementos.clear(); mFallbackKeys.clear(); mFallbackKeys.clear(); } } void InputState::copyPointerStateTo(InputState& other) const { void InputState::mergePointerStateTo(InputState& other) { for (size_t i = 0; i < mMotionMementos.size(); i++) { 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) { if (memento.source & AINPUT_SOURCE_CLASS_POINTER) { for (size_t j = 0; j < other.mMotionMementos.size();) { bool merged = false; const MotionMemento& otherMemento = other.mMotionMementos[j]; for (size_t j = 0; j < other.mMotionMementos.size(); j++) { MotionMemento& otherMemento = other.mMotionMementos[j]; if (memento.deviceId == otherMemento.deviceId && if (memento.deviceId == otherMemento.deviceId && memento.source == otherMemento.source && memento.source == otherMemento.source && memento.displayId == otherMemento.displayId) { memento.displayId == otherMemento.displayId) { other.mMotionMementos.erase(other.mMotionMementos.begin() + j); memento.mergePointerStateTo(otherMemento); } else { merged = true; j += 1; break; } } } } if (!merged) { memento.firstNewPointerIdx = 0; other.mMotionMementos.push_back(memento); other.mMotionMementos.push_back(memento); } } } } } } } int32_t InputState::getFallbackKey(int32_t originalKeyCode) { int32_t InputState::getFallbackKey(int32_t originalKeyCode) { ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); Loading
services/inputflinger/dispatcher/InputState.h +10 −2 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,8 @@ namespace android::inputdispatcher { namespace android::inputdispatcher { static constexpr int32_t INVALID_POINTER_INDEX = -1; /* Tracks dispatched key and motion event state so that cancellation events can be /* Tracks dispatched key and motion event state so that cancellation events can be * synthesized when events are dropped. */ * synthesized when events are dropped. */ class InputState { class InputState { Loading Loading @@ -52,11 +54,14 @@ public: std::vector<EventEntry*> synthesizeCancelationEvents(nsecs_t currentTime, std::vector<EventEntry*> synthesizeCancelationEvents(nsecs_t currentTime, const CancelationOptions& options); const CancelationOptions& options); // Synthesizes down events for the current state. std::vector<EventEntry*> synthesizePointerDownEvents(nsecs_t currentTime); // Clears the current state. // Clears the current state. void clear(); void clear(); // Copies pointer-related parts of the input state to another instance. // Merges pointer-related parts of the input state into another instance. void copyPointerStateTo(InputState& other) const; void mergePointerStateTo(InputState& other); // Gets the fallback key associated with a keycode. // Gets the fallback key associated with a keycode. // Returns -1 if none. // Returns -1 if none. Loading Loading @@ -97,10 +102,13 @@ private: uint32_t pointerCount; uint32_t pointerCount; PointerProperties pointerProperties[MAX_POINTERS]; PointerProperties pointerProperties[MAX_POINTERS]; PointerCoords pointerCoords[MAX_POINTERS]; PointerCoords pointerCoords[MAX_POINTERS]; // Track for which pointers the target doesn't know about. int32_t firstNewPointerIdx = INVALID_POINTER_INDEX; bool hovering; bool hovering; uint32_t policyFlags; uint32_t policyFlags; void setPointers(const MotionEntry& entry); void setPointers(const MotionEntry& entry); void mergePointerStateTo(MotionMemento& other) const; }; }; std::vector<KeyMemento> mKeyMementos; std::vector<KeyMemento> mKeyMementos; Loading
services/inputflinger/tests/InputDispatcher_test.cpp +185 −2 Original line number Original line Diff line number Diff line Loading @@ -594,12 +594,40 @@ public: expectedFlags); 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, consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); 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, consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); expectedFlags); } } Loading Loading @@ -923,6 +951,161 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { 0 /*expectedFlags*/); 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) { TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { sp<FakeApplicationHandle> application = new FakeApplicationHandle(); sp<FakeApplicationHandle> application = new FakeApplicationHandle(); sp<FakeWindowHandle> window = sp<FakeWindowHandle> window = Loading