diff options
-rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.cpp | 272 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.h | 8 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputState.cpp | 184 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputState.h | 7 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchState.cpp | 37 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchState.h | 8 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchedWindow.cpp | 14 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchedWindow.h | 2 | ||||
-rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 311 |
9 files changed, 598 insertions, 245 deletions
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 45649dd146..7dfbf94883 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -634,11 +634,10 @@ std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState, const MotionEntry& entry) { std::vector<TouchedWindow> out; const int32_t maskedAction = MotionEvent::getActionMasked(entry.action); - if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER && - maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE && - maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { - // Not a hover event - don't need to do anything - return out; + + if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + // ACTION_SCROLL events should not affect the hovering pointer dispatch + return {}; } // We should consider all hovering pointers here. But for now, just use the first one @@ -1315,8 +1314,9 @@ std::vector<InputTarget> InputDispatcher::findOutsideTargetsLocked( if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) { std::bitset<MAX_POINTER_ID + 1> pointerIds; pointerIds.set(pointerId); - addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, pointerIds, - /*firstDownTimeInTarget=*/std::nullopt, outsideTargets); + addPointerWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, + pointerIds, + /*firstDownTimeInTarget=*/std::nullopt, outsideTargets); } } return outsideTargets; @@ -1821,7 +1821,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key std::vector<InputTarget> inputTargets; addWindowTargetLocked(focusedWindow, InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS, - /*pointerIds=*/{}, getDownTime(*entry), inputTargets); + getDownTime(*entry), inputTargets); // Add monitor channels from event's or focused display. addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); @@ -1905,7 +1905,6 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< // Identify targets. std::vector<InputTarget> inputTargets; - bool conflictingPointerActions = false; InputEventInjectionResult injectionResult; if (isPointerEvent) { // Pointer event. (eg. touchscreen) @@ -1917,8 +1916,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< } inputTargets = - findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions, - /*byref*/ injectionResult); + findTouchedWindowTargetsLocked(currentTime, *entry, /*byref*/ injectionResult); LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED && !inputTargets.empty()); } else { @@ -1930,7 +1928,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< addWindowTargetLocked(focusedWindow, InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS, - /*pointerIds=*/{}, getDownTime(*entry), inputTargets); + getDownTime(*entry), inputTargets); } } if (injectionResult == InputEventInjectionResult::PENDING) { @@ -1954,11 +1952,6 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); // Dispatch the motion. - if (conflictingPointerActions) { - CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "conflicting pointer actions"); - synthesizeCancelationEventsForAllConnectionsLocked(options); - } dispatchEventLocked(currentTime, entry, inputTargets); return true; } @@ -2258,7 +2251,7 @@ std::vector<Monitor> InputDispatcher::selectResponsiveMonitorsLocked( } std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( - nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, + nsecs_t currentTime, const MotionEntry& entry, InputEventInjectionResult& outInjectionResult) { ATRACE_CALL(); @@ -2283,20 +2276,13 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } bool isSplit = shouldSplitTouch(tempTouchState, entry); - bool switchedDevice = false; - if (oldState != nullptr) { - std::set<int32_t> oldActiveDevices = oldState->getActiveDeviceIds(); - const bool anotherDeviceIsActive = - oldActiveDevices.count(entry.deviceId) == 0 && !oldActiveDevices.empty(); - switchedDevice |= anotherDeviceIsActive; - } const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT); // A DOWN could be generated from POINTER_DOWN if the initial pointers did not land into any // touchable windows. - const bool wasDown = oldState != nullptr && oldState->isDown(); + const bool wasDown = oldState != nullptr && oldState->isDown(entry.deviceId); const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) || (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown); const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || @@ -2304,34 +2290,19 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE; const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); - // If pointers are already down, let's finish the current gesture and ignore the new events - // from another device. However, if the new event is a down event, let's cancel the current - // touch and let the new one take over. - if (switchedDevice && wasDown && !isDown) { - LOG(INFO) << "Dropping event because a pointer for another device " - << " is already down in display " << displayId << ": " << entry.getDescription(); - // TODO(b/211379801): test multiple simultaneous input streams. - outInjectionResult = InputEventInjectionResult::FAILED; - return {}; // wrong device - } - if (newGesture) { - // If a new gesture is starting, clear the touch state completely. - tempTouchState.reset(); isSplit = false; - } else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) { - ALOGI("Dropping move event because a pointer for a different device is already active " - "in display %" PRId32, - displayId); - // TODO(b/211379801): test multiple simultaneous input streams. - outInjectionResult = InputEventInjectionResult::FAILED; - return {}; // wrong device + } + + if (isDown && tempTouchState.hasHoveringPointers(entry.deviceId)) { + // Compatibility behaviour: ACTION_DOWN causes HOVER_EXIT to get generated. + tempTouchState.clearHoveringPointers(entry.deviceId); } if (isHoverAction) { // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase // all of the existing hovering pointers and recompute. - tempTouchState.clearHoveringPointers(); + tempTouchState.clearHoveringPointers(entry.deviceId); } if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { @@ -2402,8 +2373,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( continue; } - if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || - maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { + if (isHoverAction) { // The "windowHandle" is the target of this hovering pointer. tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId); } @@ -2426,31 +2396,24 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } // Update the temporary touch state. - std::bitset<MAX_POINTER_ID + 1> pointerIds; + if (!isHoverAction) { + std::bitset<MAX_POINTER_ID + 1> pointerIds; pointerIds.set(pointerId); - } - - const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || - maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; - - // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would - // still add a window to the touch state. We should avoid doing that, but some of the - // later checks ("at least one foreground window") rely on this in order to dispatch - // the event properly, so that needs to be updated, possibly by looking at InputTargets. - tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId, pointerIds, - isDownOrPointerDown - ? std::make_optional(entry.eventTime) - : std::nullopt); - - // If this is the pointer going down and the touched window has a wallpaper - // then also add the touched wallpaper windows so they are locked in for the duration - // of the touch gesture. - // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper - // engine only supports touch events. We would need to add a mechanism similar - // to View.onGenericMotionEvent to enable wallpapers to handle these events. - if (isDownOrPointerDown) { - if (targetFlags.test(InputTarget::Flags::FOREGROUND) && + const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || + maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; + tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId, + pointerIds, + isDownOrPointerDown + ? std::make_optional(entry.eventTime) + : std::nullopt); + // If this is the pointer going down and the touched window has a wallpaper + // then also add the touched wallpaper windows so they are locked in for the + // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or + // SCROLL because the wallpaper engine only supports touch events. We would need to + // add a mechanism similar to View.onGenericMotionEvent to enable wallpapers to + // handle these events. + if (isDownOrPointerDown && targetFlags.test(InputTarget::Flags::FOREGROUND) && windowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle); @@ -2488,8 +2451,10 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ // If the pointer is not currently down, then ignore the event. - if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { - LOG(INFO) << "Dropping event because the pointer is not down or we previously " + if (!tempTouchState.isDown(entry.deviceId) && + maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { + LOG(INFO) << "Dropping event because the pointer for device " << entry.deviceId + << " is not down or we previously " "dropped the pointer down event in display " << displayId << ": " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; @@ -2538,7 +2503,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( if (newTouchedWindowHandle != nullptr && !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) { - ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32, + ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32, oldTouchedWindowHandle->getName().c_str(), newTouchedWindowHandle->getName().c_str(), displayId); @@ -2549,9 +2514,11 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const TouchedWindow& touchedWindow = tempTouchState.getTouchedWindow(oldTouchedWindowHandle); - addWindowTargetLocked(oldTouchedWindowHandle, - InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, pointerIds, - touchedWindow.getDownTimeInTarget(entry.deviceId), targets); + addPointerWindowTargetLocked(oldTouchedWindowHandle, + InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, + pointerIds, + touchedWindow.getDownTimeInTarget(entry.deviceId), + targets); // Make a slippery entrance into the new window. if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) { @@ -2664,16 +2631,14 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // Output targets from the touch state. for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - if (!touchedWindow.hasTouchingPointers(entry.deviceId) && - !touchedWindow.hasHoveringPointers(entry.deviceId)) { - // Windows with hovering pointers are getting persisted inside TouchState. - // Do not send this event to those windows. + std::bitset<MAX_POINTER_ID + 1> touchingPointers = + touchedWindow.getTouchingPointers(entry.deviceId); + if (touchingPointers.none()) { continue; } - - addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, - touchedWindow.getTouchingPointers(entry.deviceId), - touchedWindow.getDownTimeInTarget(entry.deviceId), targets); + addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, + touchingPointers, + touchedWindow.getDownTimeInTarget(entry.deviceId), targets); } // During targeted injection, only allow owned targets to receive events @@ -2706,37 +2671,30 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } outInjectionResult = InputEventInjectionResult::SUCCEEDED; - // Drop the outside or hover touch windows since we will not care about them - // in the next iteration. - tempTouchState.filterNonAsIsTouchWindows(); - // Update final pieces of touch state if the injector had permission. - if (switchedDevice) { - if (DEBUG_FOCUS) { - ALOGD("Conflicting pointer actions: Switched to a different device."); + for (TouchedWindow& touchedWindow : tempTouchState.windows) { + // Targets that we entered in a slippery way will now become AS-IS targets + if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) { + touchedWindow.targetFlags.clear(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER); + touchedWindow.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS; } - *outConflictingPointerActions = true; } + // Update final pieces of touch state if the injector had permission. if (isHoverAction) { - // Started hovering, therefore no longer down. - if (oldState && oldState->isDown()) { - ALOGD_IF(DEBUG_FOCUS, - "Conflicting pointer actions: Hover received while pointer was down."); - *outConflictingPointerActions = true; + if (oldState && oldState->isDown(entry.deviceId)) { + // Started hovering, but the device is already down: reject the hover event + LOG(ERROR) << "Got hover event " << entry.getDescription() + << " but the device is already down " << oldState->dump(); + outInjectionResult = InputEventInjectionResult::FAILED; + return {}; } } else if (maskedAction == AMOTION_EVENT_ACTION_UP) { // Pointer went up. tempTouchState.removeTouchingPointer(entry.deviceId, entry.pointerProperties[0].id); } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) { // All pointers up or canceled. - tempTouchState.reset(); - } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { - // First pointer went down. - if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) { - ALOGD("Conflicting pointer actions: Down received while already down or hovering."); - *outConflictingPointerActions = true; - } + tempTouchState.removeAllPointersForDevice(entry.deviceId); } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { // One pointer went up. const int32_t pointerIndex = MotionEvent::getActionIndex(action); @@ -2885,7 +2843,6 @@ std::optional<InputTarget> InputDispatcher::createInputTargetLocked( void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle, ftl::Flags<InputTarget::Flags> targetFlags, - std::bitset<MAX_POINTER_ID + 1> pointerIds, std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const { std::vector<InputTarget>::iterator it = @@ -2907,8 +2864,54 @@ void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHa it = inputTargets.end() - 1; } - ALOG_ASSERT(it->flags == targetFlags); - ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor); + LOG_ALWAYS_FATAL_IF(it->flags != targetFlags); + LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor); +} + +void InputDispatcher::addPointerWindowTargetLocked( + const sp<android::gui::WindowInfoHandle>& windowHandle, + ftl::Flags<InputTarget::Flags> targetFlags, std::bitset<MAX_POINTER_ID + 1> pointerIds, + std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const + REQUIRES(mLock) { + if (pointerIds.none()) { + for (const auto& target : inputTargets) { + LOG(INFO) << "Target: " << target; + } + LOG(FATAL) << "No pointers specified for " << windowHandle->getName(); + return; + } + std::vector<InputTarget>::iterator it = + std::find_if(inputTargets.begin(), inputTargets.end(), + [&windowHandle](const InputTarget& inputTarget) { + return inputTarget.inputChannel->getConnectionToken() == + windowHandle->getToken(); + }); + + // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that + // causes a HOVER_EXIT to be generated. That means that the same entry of ACTION_DOWN would + // have DISPATCH_AS_HOVER_EXIT and DISPATCH_AS_IS. And therefore, we have to create separate + // input targets for hovering pointers and for touching pointers. + // If we picked an existing input target above, but it's for HOVER_EXIT - let's use a new + // target instead. + if (it != inputTargets.end() && it->flags.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) { + // Force the code below to create a new input target + it = inputTargets.end(); + } + + const WindowInfo* windowInfo = windowHandle->getInfo(); + + if (it == inputTargets.end()) { + std::optional<InputTarget> target = + createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget); + if (!target) { + return; + } + inputTargets.push_back(*target); + it = inputTargets.end() - 1; + } + + LOG_ALWAYS_FATAL_IF(it->flags != targetFlags); + LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor); it->addPointers(pointerIds, windowInfo->transform); } @@ -3366,6 +3369,27 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connectio dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; } + // Check if we need to cancel any of the ongoing gestures. We don't support multiple + // devices being active at the same time in the same window, so if a new device is + // active, cancel the gesture from the old device. + + std::unique_ptr<EventEntry> cancelEvent = + connection->inputState + .cancelConflictingInputStream(motionEntry, + dispatchEntry->resolvedAction); + if (cancelEvent != nullptr) { + LOG(INFO) << "Canceling pointers for device " << motionEntry.deviceId << " in " + << connection->getInputChannelName() << " with event " + << cancelEvent->getDescription(); + std::unique_ptr<DispatchEntry> cancelDispatchEntry = + createDispatchEntry(inputTarget, std::move(cancelEvent), + InputTarget::Flags::DISPATCH_AS_IS); + + // Send these cancel events to the queue before sending the event from the new + // device. + connection->outboundQueue.emplace_back(std::move(cancelDispatchEntry)); + } + if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) { LOG(WARNING) << "channel " << connection->getInputChannelName() @@ -3975,7 +3999,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( : std::nullopt; if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) { addWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS, - /*pointerIds=*/{}, keyEntry.downTime, targets); + keyEntry.downTime, targets); } else { targets.emplace_back(fallbackTarget); } @@ -3994,8 +4018,8 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( pointerIndex++) { pointerIds.set(motionEntry.pointerProperties[pointerIndex].id); } - addWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS, pointerIds, - motionEntry.downTime, targets); + addPointerWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS, + pointerIds, motionEntry.downTime, targets); } else { targets.emplace_back(fallbackTarget); const auto it = mDisplayInfos.find(motionEntry.displayId); @@ -4069,8 +4093,8 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( pointerIndex++) { pointerIds.set(motionEntry.pointerProperties[pointerIndex].id); } - addWindowTargetLocked(windowHandle, targetFlags, pointerIds, - motionEntry.downTime, targets); + addPointerWindowTargetLocked(windowHandle, targetFlags, pointerIds, + motionEntry.downTime, targets); } else { targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel, .flags = targetFlags}); @@ -5139,10 +5163,8 @@ void InputDispatcher::setInputWindowsLocked( for (size_t i = 0; i < state.windows.size();) { TouchedWindow& touchedWindow = state.windows[i]; if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) { - if (DEBUG_FOCUS) { - ALOGD("Touched window was removed: %s in display %" PRId32, - touchedWindow.windowHandle->getName().c_str(), displayId); - } + LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName() + << " in display %" << displayId; std::shared_ptr<InputChannel> touchedInputChannel = getInputChannelLocked(touchedWindow.windowHandle->getToken()); if (touchedInputChannel != nullptr) { @@ -5973,9 +5995,8 @@ status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) { return BAD_VALUE; } std::set<int32_t> deviceIds = windowPtr->getTouchingDeviceIds(); - if (deviceIds.size() != 1) { - LOG(WARNING) << "Can't pilfer. Currently touching devices: " << dumpSet(deviceIds) - << " in window: " << windowPtr->dump(); + if (deviceIds.empty()) { + LOG(WARNING) << "Can't pilfer: no touching devices in window: " << windowPtr->dump(); return BAD_VALUE; } @@ -6818,10 +6839,11 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFl if (oldWallpaper != nullptr) { const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper); - addWindowTargetLocked(oldWallpaper, - oldTouchedWindow.targetFlags | - InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, - pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId), targets); + addPointerWindowTargetLocked(oldWallpaper, + oldTouchedWindow.targetFlags | + InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, + pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId), + targets); state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper); } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 002030168b..a1127a0f45 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -524,7 +524,7 @@ private: nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); std::vector<InputTarget> findTouchedWindowTargetsLocked( - nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, + nsecs_t currentTime, const MotionEntry& entry, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); std::vector<Monitor> selectResponsiveMonitorsLocked( const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock); @@ -535,9 +535,13 @@ private: std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock); void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle, ftl::Flags<InputTarget::Flags> targetFlags, - std::bitset<MAX_POINTER_ID + 1> pointerIds, std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const REQUIRES(mLock); + void addPointerWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle, + ftl::Flags<InputTarget::Flags> targetFlags, + std::bitset<MAX_POINTER_ID + 1> pointerIds, + std::optional<nsecs_t> firstDownTimeInTarget, + std::vector<InputTarget>& inputTargets) const REQUIRES(mLock); void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId) REQUIRES(mLock); void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index b348808b13..09b5186a69 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -24,6 +24,19 @@ namespace android::inputdispatcher { +namespace { +bool isHoverAction(int32_t action) { + switch (MotionEvent::getActionMasked(action)) { + case AMOTION_EVENT_ACTION_HOVER_ENTER: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + case AMOTION_EVENT_ACTION_HOVER_EXIT: { + return true; + } + } + return false; +} +} // namespace + InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {} InputState::~InputState() {} @@ -89,6 +102,28 @@ bool InputState::trackKey(const KeyEntry& entry, int32_t action, int32_t flags) * false if the incoming event should be dropped. */ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) { + // Don't track non-pointer events + if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) { + // This is a focus-dispatched event; we don't track its state. + return true; + } + + if (!mMotionMementos.empty()) { + const MotionMemento& lastMemento = mMotionMementos.back(); + if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && + !isStylusEvent(entry.source, entry.pointerProperties)) { + // We already have a stylus stream, and the new event is not from stylus. + if (!lastMemento.hovering) { + // If stylus is currently down, reject the new event unconditionally. + return false; + } + } + if (!lastMemento.hovering && isHoverAction(action)) { + // Reject hovers if already down + return false; + } + } + int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK; switch (actionMasked) { case AMOTION_EVENT_ACTION_UP: @@ -266,6 +301,136 @@ size_t InputState::MotionMemento::getPointerCount() const { return pointerProperties.size(); } +bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry, + int32_t resolvedAction) const { + if (!isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER)) { + // This is a focus-dispatched event that should not affect the previous stream. + return false; + } + + // New MotionEntry pointer event is coming in. + + // If this is a new gesture, and it's from a different device, then, in general, we will cancel + // the current gesture. + // However, because stylus should be preferred over touch, we need to treat some cases in a + // special way. + if (mMotionMementos.empty()) { + // There is no ongoing pointer gesture, so there is nothing to cancel + return false; + } + + const MotionMemento& lastMemento = mMotionMementos.back(); + const int32_t actionMasked = MotionEvent::getActionMasked(resolvedAction); + + // For compatibility, only one input device can be active at a time in the same window. + if (lastMemento.deviceId == motionEntry.deviceId) { + // In general, the same device should produce self-consistent streams so nothing needs to + // be canceled. But there is one exception: + // Sometimes ACTION_DOWN is received without a corresponding HOVER_EXIT. To account for + // that, cancel the previous hovering stream + if (actionMasked == AMOTION_EVENT_ACTION_DOWN && lastMemento.hovering) { + return true; + } + + // Use the previous stream cancellation logic to generate all HOVER_EXIT events. + // If this hover event was generated as a result of the pointer leaving the window, + // the HOVER_EXIT event should have the same coordinates as the previous + // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same + // coordinates as the previous event by cancelling the stream here. With this approach, the + // HOVER_EXIT event is generated from the previous event. + if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) { + return true; + } + + // If the stream changes its source, just cancel the current gesture to be safe. It's + // possible that the app isn't handling source changes properly + if (motionEntry.source != lastMemento.source) { + LOG(INFO) << "Canceling stream: last source was " + << inputEventSourceToString(lastMemento.source) << " and new event is " + << motionEntry; + return true; + } + + // If the injection is happening into two different displays, the same injected device id + // could be going into both. And at this time, if mirroring is active, the same connection + // would receive different events from each display. Since the TouchStates are per-display, + // it's unlikely that those two streams would be consistent with each other. Therefore, + // cancel the previous gesture if the display id changes. + if (motionEntry.displayId != lastMemento.displayId) { + LOG(INFO) << "Canceling stream: last displayId was " + << inputEventSourceToString(lastMemento.displayId) << " and new event is " + << motionEntry; + return true; + } + + return false; + } + + // We want stylus down to block touch and other source types, but stylus hover should not + // have such an effect. + if (isHoverAction(motionEntry.action) && !lastMemento.hovering) { + // New event is a hover. Keep the current non-hovering gesture instead + return false; + } + + if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !lastMemento.hovering) { + // We have non-hovering stylus already active. + if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) && + actionMasked == AMOTION_EVENT_ACTION_DOWN) { + // If this new event is a stylus from a different device going down, then cancel the old + // stylus and allow the new stylus to take over + return true; + } + + // Keep the current stylus gesture. + return false; + } + + // Cancel the current gesture if this is a start of a new gesture from a new device. + if (actionMasked == AMOTION_EVENT_ACTION_DOWN || + actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) { + return true; + } + // By default, don't cancel any events. + return false; +} + +std::unique_ptr<EventEntry> InputState::cancelConflictingInputStream(const MotionEntry& motionEntry, + int32_t resolvedAction) { + if (!shouldCancelPreviousStream(motionEntry, resolvedAction)) { + return {}; + } + + const MotionMemento& memento = mMotionMementos.back(); + + // Cancel the last device stream + std::unique_ptr<MotionEntry> cancelEntry = + createCancelEntryForMemento(memento, motionEntry.eventTime); + + if (!trackMotion(*cancelEntry, cancelEntry->action, cancelEntry->flags)) { + LOG(FATAL) << "Generated inconsistent cancel event!"; + } + return cancelEntry; +} + +std::unique_ptr<MotionEntry> InputState::createCancelEntryForMemento(const MotionMemento& memento, + nsecs_t eventTime) const { + const int32_t action = + memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL; + int32_t flags = memento.flags; + if (action == AMOTION_EVENT_ACTION_CANCEL) { + flags |= AMOTION_EVENT_FLAG_CANCELED; + } + return std::make_unique<MotionEntry>(mIdGenerator.nextId(), eventTime, memento.deviceId, + memento.source, memento.displayId, memento.policyFlags, + action, /*actionButton=*/0, flags, AMETA_NONE, + /*buttonState=*/0, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, + memento.yPrecision, memento.xCursorPosition, + memento.yCursorPosition, memento.downTime, + memento.pointerProperties, memento.pointerCoords); +} + std::vector<std::unique_ptr<EventEntry>> InputState::synthesizeCancelationEvents( nsecs_t currentTime, const CancelationOptions& options) { std::vector<std::unique_ptr<EventEntry>> events; @@ -284,24 +449,7 @@ std::vector<std::unique_ptr<EventEntry>> InputState::synthesizeCancelationEvents for (const MotionMemento& memento : mMotionMementos) { if (shouldCancelMotion(memento, options)) { if (options.pointerIds == std::nullopt) { - const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT - : AMOTION_EVENT_ACTION_CANCEL; - int32_t flags = memento.flags; - if (action == AMOTION_EVENT_ACTION_CANCEL) { - flags |= AMOTION_EVENT_FLAG_CANCELED; - } - events.push_back( - std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime, - memento.deviceId, memento.source, - memento.displayId, memento.policyFlags, - action, /*actionButton=*/0, flags, AMETA_NONE, - /*buttonState=*/0, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, - memento.xPrecision, memento.yPrecision, - memento.xCursorPosition, - memento.yCursorPosition, memento.downTime, - memento.pointerProperties, - memento.pointerCoords)); + events.push_back(createCancelEntryForMemento(memento, currentTime)); } else { std::vector<std::unique_ptr<MotionEntry>> pointerCancelEvents = synthesizeCancelationEventsForPointers(memento, options.pointerIds.value(), diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index 3adbba0902..686c4323a4 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -48,6 +48,10 @@ public: // and should be skipped. bool trackMotion(const MotionEntry& entry, int32_t action, int32_t flags); + // Create cancel events for the previous stream if the current motionEntry requires it. + std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry, + int32_t resolvedAction); + // Synthesizes cancelation events for the current state and resets the tracked state. std::vector<std::unique_ptr<EventEntry>> synthesizeCancelationEvents( nsecs_t currentTime, const CancelationOptions& options); @@ -123,6 +127,9 @@ private: static bool shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options); static bool shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options); + bool shouldCancelPreviousStream(const MotionEntry& motionEntry, int32_t resolvedAction) const; + std::unique_ptr<MotionEntry> createCancelEntryForMemento(const MotionMemento& memento, + nsecs_t eventTime) const; // Synthesizes pointer cancel events for a particular set of pointers. std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers( diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 9dcf615479..2ead171155 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <android-base/logging.h> #include <android-base/stringprintf.h> #include <gui/WindowInfo.h> @@ -31,15 +32,6 @@ void TouchState::reset() { *this = TouchState(); } -std::set<int32_t> TouchState::getActiveDeviceIds() const { - std::set<int32_t> out; - for (const TouchedWindow& w : windows) { - std::set<int32_t> deviceIds = w.getActiveDeviceIds(); - out.insert(deviceIds.begin(), deviceIds.end()); - } - return out; -} - bool TouchState::hasTouchingPointers(DeviceId deviceId) const { return std::any_of(windows.begin(), windows.end(), [&](const TouchedWindow& window) { return window.hasTouchingPointers(deviceId); @@ -65,9 +57,9 @@ void TouchState::removeTouchingPointerFromWindow( } } -void TouchState::clearHoveringPointers() { +void TouchState::clearHoveringPointers(DeviceId deviceId) { for (TouchedWindow& touchedWindow : windows) { - touchedWindow.clearHoveringPointers(); + touchedWindow.removeAllHoveringPointersForDevice(deviceId); } clearWindowsWithoutPointers(); } @@ -82,9 +74,16 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> touchingPointerIds, std::optional<nsecs_t> firstDownTimeInTarget) { + if (touchingPointerIds.none()) { + LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName(); + return; + } for (TouchedWindow& touchedWindow : windows) { // We do not compare windows by token here because two windows that share the same token - // may have a different transform + // may have a different transform. They will be combined later when we create InputTargets. + // At that point, per-pointer window transform will be considered. + // An alternative design choice here would have been to compare here by token, but to + // store per-pointer transform. if (touchedWindow.windowHandle == windowHandle) { touchedWindow.targetFlags |= targetFlags; if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) { @@ -237,14 +236,16 @@ const TouchedWindow& TouchState::getTouchedWindow(const sp<WindowInfoHandle>& wi return *it; } -bool TouchState::isDown() const { - return std::any_of(windows.begin(), windows.end(), - [](const TouchedWindow& window) { return window.hasTouchingPointers(); }); +bool TouchState::isDown(DeviceId deviceId) const { + return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) { + return window.hasTouchingPointers(deviceId); + }); } -bool TouchState::hasHoveringPointers() const { - return std::any_of(windows.begin(), windows.end(), - [](const TouchedWindow& window) { return window.hasHoveringPointers(); }); +bool TouchState::hasHoveringPointers(DeviceId deviceId) const { + return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) { + return window.hasHoveringPointers(deviceId); + }); } std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId, diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index f01693662c..e79c73b5e5 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -39,8 +39,6 @@ struct TouchState { void reset(); void clearWindowsWithoutPointers(); - std::set<DeviceId> getActiveDeviceIds() const; - bool hasTouchingPointers(DeviceId deviceId) const; void removeTouchingPointer(DeviceId deviceId, int32_t pointerId); void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId, @@ -52,7 +50,7 @@ struct TouchState { void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, DeviceId deviceId, int32_t hoveringPointerId); void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId); - void clearHoveringPointers(); + void clearHoveringPointers(DeviceId deviceId); void removeAllPointersForDevice(DeviceId deviceId); void removeWindowByToken(const sp<IBinder>& token); @@ -72,8 +70,8 @@ struct TouchState { const TouchedWindow& getTouchedWindow( const sp<android::gui::WindowInfoHandle>& windowHandle) const; // Whether any of the windows are currently being touched - bool isDown() const; - bool hasHoveringPointers() const; + bool isDown(DeviceId deviceId) const; + bool hasHoveringPointers(DeviceId deviceId) const; std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer( DeviceId deviceId, int32_t pointerId) const; diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index ff4b425da3..536775100a 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -45,12 +45,16 @@ bool TouchedWindow::hasHoveringPointers(DeviceId deviceId) const { return state.hoveringPointerIds.any(); } -void TouchedWindow::clearHoveringPointers() { - for (auto& [_, state] : mDeviceStates) { - state.hoveringPointerIds.reset(); +void TouchedWindow::clearHoveringPointers(DeviceId deviceId) { + auto stateIt = mDeviceStates.find(deviceId); + if (stateIt == mDeviceStates.end()) { + return; + } + DeviceState& state = stateIt->second; + state.hoveringPointerIds.reset(); + if (!state.hasPointers()) { + mDeviceStates.erase(stateIt); } - - std::erase_if(mDeviceStates, [](const auto& pair) { return !pair.second.hasPointers(); }); } bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const { diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 3f760c0fac..6d2283e0af 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -71,7 +71,7 @@ struct TouchedWindow { void removeAllTouchingPointersForDevice(DeviceId deviceId); void removeAllHoveringPointersForDevice(DeviceId deviceId); - void clearHoveringPointers(); + void clearHoveringPointers(DeviceId deviceId); std::string dump() const; private: diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 34323c33c2..3c87f7194d 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2409,9 +2409,10 @@ TEST_F(InputDispatcherTest, HoverMoveAndScroll) { using InputDispatcherMultiDeviceTest = InputDispatcherTest; /** - * One window. Stylus down on the window. Next, touch from another device goes down. + * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that + * touch is dropped, because stylus should be preferred over touch. */ -TEST_F(InputDispatcherMultiDeviceTest, StylusDownAndTouchDown) { +TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2434,34 +2435,31 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownAndTouchDown) { .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) .build()); - // Touch cancels stylus - window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId), - WithCoords(100, 110))); - window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId), - WithCoords(140, 145))); // Touch move mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) .build()); - window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId), - WithCoords(141, 146))); + // Touch is ignored because stylus is already down - // Subsequent stylus movements are dropped + // Subsequent stylus movements are delivered correctly mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + window->assertNoEvents(); } /** * One window and one spy window. Stylus down on the window. Next, touch from another device goes - * down. + * down. Ensure that touch is dropped, because stylus should be preferred over touch. * Similar test as above, but with added SPY window. */ -TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyAndTouchDown) { +TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2497,30 +2495,28 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyAndTouchDown) { .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) .build()); - window->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); - spyWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); - window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); - spyWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); - window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); - spyWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); - // Subsequent stylus movements are dropped + + // Touch is ignored because stylus is already down + + // Subsequent stylus movements are delivered correctly mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); window->assertNoEvents(); spyWindow->assertNoEvents(); } /** - * One window. Stylus hover on the window. Next, touch from another device goes down. + * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that + * touch is not dropped, because stylus hover should be ignored. */ -TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchDown) { +TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2548,19 +2544,174 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchDown) { .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) .build()); - window->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId))); + + // Stylus hover is canceled because touch is down + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), + WithDeviceId(stylusDeviceId), WithCoords(100, 110))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId), + WithCoords(140, 145))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId), + WithCoords(141, 146))); + + // Subsequent stylus movements are ignored + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + + // but subsequent touches continue to be delivered + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId), + WithCoords(142, 147))); +} + +/** + * One window. Touch down on the window. Then, stylus hover on the window from another device. + * Ensure that touch is not canceled, because stylus hover should be dropped. + */ +TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); - // Subsequent stylus movements are ignored + + // Stylus hover on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) .build()); + // Stylus hover movement is dropped + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147)) + .build()); + // Subsequent touch movements are delivered correctly + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId), + WithCoords(142, 147))); +} + +/** + * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that + * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should + * become active. + */ +TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t stylusDeviceId1 = 3; + constexpr int32_t stylusDeviceId2 = 5; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1))); + + // Second stylus down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId2) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId2) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11)) + .build()); + + // First stylus is canceled, second one takes over. + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId1))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102)) + .build()); + // Subsequent stylus movements are delivered correctly window->assertNoEvents(); } /** + * One window. Touch down on the window. Then, stylus down on the window from another device. + * Ensure that is canceled, because stylus down should be preferred over touch. + */ +TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Stylus down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Subsequent stylus movements are delivered correctly + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); +} + +/** * Two windows: a window on the left and a window on the right. * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains * down. Then, on the left window, also place second touch pointer down. @@ -2617,8 +2768,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) .build()); - leftWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId))); + leftWindow->assertNoEvents(); + rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); // Second touch pointer down on left window @@ -2627,6 +2778,11 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) .build()); + // Since this is now a new splittable pointer going down on the left window, and it's coming + // from a different device, the current gesture in the left window (pointer down) should first + // be canceled. + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId))); leftWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); // This MOVE event is not necessary (doesn't carry any new information), but it's there in the @@ -2672,8 +2828,6 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) { .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(300).y(100)) .build()); - leftWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); rightWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); @@ -2683,18 +2837,14 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) { .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120)) .build()); leftWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); - rightWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId))); + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(mouseDeviceId))); mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(310).y(110)) .build()); - leftWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); rightWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); leftWindow->assertNoEvents(); rightWindow->assertNoEvents(); @@ -2745,29 +2895,30 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) .build()); - leftWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); - spyWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); + leftWindow->assertNoEvents(); rightWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); - spyWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); - // Stylus movements continue, but are ignored because the touch went down more recently. + // Spy window does not receive touch events, because stylus events take precedence, and it + // already has an active stylus gesture. + + // Stylus movements continue. They should be delivered to the left window and to the spy window mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + // Further MOVE events keep going to the right window only mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110)) .build()); rightWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); - spyWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); spyWindow->assertNoEvents(); leftWindow->assertNoEvents(); @@ -2779,8 +2930,10 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { * both. * Check hover in left window and touch down in the right window. * At first, spy should receive hover, but the touch down should cancel hovering inside spy. + * At the same time, left and right should be getting independent streams of hovering and touch, + * respectively. */ -TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverAndTouchWithSpy) { +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = @@ -2813,13 +2966,13 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverAndTouchWithSpy) { spyWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); - // Touch down on the right window. + // Touch down on the right window. Spy doesn't receive this touch because it already has + // stylus hovering there. mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) .build()); - leftWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId))); + leftWindow->assertNoEvents(); spyWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId))); spyWindow->consumeMotionEvent( @@ -2827,11 +2980,13 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverAndTouchWithSpy) { rightWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); - // Stylus movements continue, but are ignored because the touch is down. + // Stylus movements continue. They should be delivered to the left window only. mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); // Touch movements continue. They should be delivered to the right window and to the spy mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) @@ -3054,7 +3209,7 @@ TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { * While the touch is down, new hover events from the stylus device should be ignored. After the * touch is gone, stylus hovering should start working again. */ -TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchTap) { +TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -3080,20 +3235,20 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchTap) { .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); + // The touch device should cause hover to stop! window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId))); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); - // Continue hovering with stylus. Injection will fail because touch is already down. - ASSERT_EQ(InputEventInjectionResult::FAILED, + // Continue hovering with stylus. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60)) .build())); - // No event should be sent. This event should be ignored because a pointer from another device - // is already down. + // Hovers are now ignored // Lift up the finger ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -3105,7 +3260,6 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchTap) { .build())); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId))); - // Now that the touch is gone, stylus hovering should start working again ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, @@ -7822,10 +7976,23 @@ private: // should be ANR'd first. TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - FOCUSED_WINDOW_LOCATION)) - << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + injectMotionEvent(*mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(FOCUSED_WINDOW_LOCATION.x) + .y(FOCUSED_WINDOW_LOCATION.y)) + .build())); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(FOCUSED_WINDOW_LOCATION.x) + .y(FOCUSED_WINDOW_LOCATION.y)) + .build())); mFocusedWindow->consumeMotionDown(); + mFocusedWindow->consumeMotionUp(); mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, ADISPLAY_ID_DEFAULT, /*flags=*/0); // We consumed all events, so no ANR @@ -7833,17 +8000,20 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { mFakePolicy->assertNotifyAnrWasNotCalled(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - FOCUSED_WINDOW_LOCATION)); + injectMotionEvent(*mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(FOCUSED_WINDOW_LOCATION.x) + .y(FOCUSED_WINDOW_LOCATION.y)) + .build())); std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent(); ASSERT_TRUE(unfocusedSequenceNum); const std::chrono::duration timeout = mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow); - // Because we injected two DOWN events in a row, CANCEL is enqueued for the first event - // sequence to make it consistent - mFocusedWindow->consumeMotionCancel(); + mUnfocusedWindow->finishEvent(*unfocusedSequenceNum); mFocusedWindow->consumeMotionDown(); // This cancel is generated because the connection was unresponsive @@ -10356,13 +10526,12 @@ TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { .build()); rightWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); - leftWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); - spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); - spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + spy->assertNoEvents(); // Act: pilfer from spy. Spy is currently receiving touch events. EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); rightWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); @@ -10376,7 +10545,7 @@ TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { .deviceId(touchDeviceId) .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52)) .build()); - spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); spy->assertNoEvents(); leftWindow->assertNoEvents(); |