Merge "Allow multi-window multiple device streams" into main
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 45649dd..7dfbf94 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -634,11 +634,10 @@
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 @@
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 @@
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 @@
// Identify targets.
std::vector<InputTarget> inputTargets;
- bool conflictingPointerActions = false;
InputEventInjectionResult injectionResult;
if (isPointerEvent) {
// Pointer event. (eg. touchscreen)
@@ -1917,8 +1916,7 @@
}
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 @@
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 @@
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<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 @@
}
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 @@
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) {
+ isSplit = false;
}
- 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 @@
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 @@
}
// 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 @@
/* 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 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 @@
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 @@
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 @@
: 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 @@
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 @@
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 @@
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 @@
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 @@
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 0020301..a1127a0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -524,7 +524,7 @@
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 @@
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 b348808..09b5186 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 @@
* 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 @@
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 @@
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 3adbba0..686c432 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -48,6 +48,10 @@
// 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 @@
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 9dcf615..2ead171 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 @@
*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::clearHoveringPointers() {
+void TouchState::clearHoveringPointers(DeviceId deviceId) {
for (TouchedWindow& touchedWindow : windows) {
- touchedWindow.clearHoveringPointers();
+ touchedWindow.removeAllHoveringPointersForDevice(deviceId);
}
clearWindowsWithoutPointers();
}
@@ -82,9 +74,16 @@
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 @@
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 f016936..e79c73b 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -39,8 +39,6 @@
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 @@
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 @@
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 ff4b425..5367751 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -45,12 +45,16 @@
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;
}
-
- std::erase_if(mDeviceStates, [](const auto& pair) { return !pair.second.hasPointers(); });
+ DeviceState& state = stateIt->second;
+ state.hoveringPointerIds.reset();
+ if (!state.hasPointers()) {
+ mDeviceStates.erase(stateIt);
+ }
}
bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const {
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 3f760c0..6d2283e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -71,7 +71,7 @@
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 34323c3..3c87f71 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2409,9 +2409,10 @@
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 @@
.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 @@
.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 @@
.deviceId(touchDeviceId)
.pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
.build());
- window->consumeMotionEvent(
- AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
- window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
- window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // 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)));
+
+ // 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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 @@
* 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 @@
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 @@
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 @@
* 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 @@
.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 @@
.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 @@
// 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 @@
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 @@
.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 @@
.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();