Fix window jumping when crossing a display boundary
This CL does the following:
- Do not resample from an event on a different display
- Do not put events from different displays into one batch event
to prevent from sending an event with meaningless coordinates.
Bug: 402625886
Test: libinput_tests
Test: go/cd-smoke
Flag: com.android.window.flags.enable_connected_displays_window_drag
Change-Id: I9aba34a2962f4a0171ee995d9e749e7a9dd226e3
diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h
index 611478c..4a12d82 100644
--- a/include/input/InputConsumer.h
+++ b/include/input/InputConsumer.h
@@ -135,6 +135,7 @@
BitSet32 idBits;
int32_t idToIndex[MAX_POINTER_ID + 1];
PointerCoords pointers[MAX_POINTERS];
+ int32_t displayId;
void initializeFrom(const InputMessage& msg) {
eventTime = msg.body.motion.eventTime;
@@ -145,6 +146,7 @@
idToIndex[id] = i;
pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
}
+ displayId = msg.body.motion.displayId;
}
void initializeFrom(const History& other) {
@@ -157,6 +159,7 @@
pointers[index].copyFrom(other.pointers[index]);
}
idBits = other.idBits; // final copy
+ displayId = other.displayId;
}
const PointerCoords& getPointerById(uint32_t id) const { return pointers[idToIndex[id]]; }
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 70d00d1..724ebe7 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -260,6 +260,11 @@
* the batched MotionEvent that it received.
*/
std::map<uint32_t, std::vector<uint32_t>> mBatchedSequenceNumbers;
+
+ /**
+ * Checks if a given input event is okay to be added to an existing batch or not.
+ */
+ bool isBatchableEvent(const InputMessage& message) const;
};
} // namespace android
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index 1eeb4e6..b073833 100644
--- a/libs/input/InputConsumer.cpp
+++ b/libs/input/InputConsumer.cpp
@@ -658,6 +658,11 @@
}
}
+ if (current->displayId != other->displayId) {
+ ALOGD_IF(debugResampling(), "Not resampled, the other is on a different display");
+ return;
+ }
+
// Resample touch coordinates.
History oldLastResample;
oldLastResample.initializeFrom(touchState.lastResample);
@@ -840,9 +845,11 @@
const InputMessage& head = batch.samples[0];
uint32_t pointerCount = msg->body.motion.pointerCount;
if (head.body.motion.pointerCount != pointerCount ||
- head.body.motion.action != msg->body.motion.action) {
+ head.body.motion.action != msg->body.motion.action ||
+ head.body.motion.displayId != msg->body.motion.displayId) {
return false;
}
+
for (size_t i = 0; i < pointerCount; i++) {
if (head.body.motion.pointers[i].properties != msg->body.motion.pointers[i].properties) {
return false;
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 9578639..c13354f 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -348,10 +348,7 @@
const int32_t action = msg.body.motion.action;
const DeviceId deviceId = msg.body.motion.deviceId;
const int32_t source = msg.body.motion.source;
- const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE ||
- action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
- (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
- isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+ const bool batchableEvent = isBatchableEvent(msg);
const bool canResample = (mResamplerCreator != nullptr) &&
(isFromSource(source, AINPUT_SOURCE_CLASS_POINTER));
@@ -624,4 +621,25 @@
return out;
}
+bool InputConsumerNoResampling::isBatchableEvent(const InputMessage& message) const {
+ const int32_t action = message.body.motion.action;
+ const int32_t source = message.body.motion.source;
+ const bool batchableEventTypeAndSource =
+ (action == AMOTION_EVENT_ACTION_MOVE || action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
+ (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
+ isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+ if (!batchableEventTypeAndSource) {
+ return false;
+ }
+
+ const DeviceId deviceId = message.body.motion.deviceId;
+ const auto& it = mBatches.find(deviceId);
+ // If there is no pending event from the device, it's okay to add the event to the batch.
+ if (it == mBatches.end() || it->second.size() == 0) {
+ return true;
+ }
+ // It's okay to add the event to the existing batch if it's on the same display.
+ return it->second.front().body.motion.displayId == message.body.motion.displayId;
+}
+
} // namespace android
diff --git a/libs/input/tests/InputConsumerResampling_test.cpp b/libs/input/tests/InputConsumerResampling_test.cpp
index 97688a8..748cc69 100644
--- a/libs/input/tests/InputConsumerResampling_test.cpp
+++ b/libs/input/tests/InputConsumerResampling_test.cpp
@@ -56,6 +56,7 @@
std::chrono::nanoseconds eventTime{0};
std::vector<Pointer> pointers{};
int32_t action{-1};
+ ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
};
} // namespace
@@ -137,7 +138,8 @@
.eventTime(entry.eventTime.count())
.deviceId(1)
.action(entry.action)
- .downTime(0);
+ .downTime(0)
+ .displayId(entry.displayId);
for (const Pointer& pointer : entry.pointers) {
messageBuilder.pointer(pointer.asPointerBuilder());
}
@@ -741,4 +743,50 @@
AMOTION_EVENT_ACTION_MOVE}});
}
+/**
+ * Events should not be resampled when they are on different displays.
+ */
+TEST_F(InputConsumerResamplingTest, EventsOnDifferentDisplaysAreNotResampled) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y, but on
+ // different displays
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE,
+ ui::LogicalDisplayId::DEFAULT}));
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE,
+ ui::LogicalDisplayId{1}}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY * 2}.count());
+
+ // MotionEvent should not be resampled because the resample time falls exactly on the existing
+ // event time.
+ assertReceivedMotionEvent({InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE,
+ ui::LogicalDisplayId::DEFAULT}});
+ assertReceivedMotionEvent({InputEventEntry{20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE,
+ ui::LogicalDisplayId{1}}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
} // namespace android
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 9841c03..1bac644 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -40,6 +40,7 @@
std::chrono::nanoseconds eventTime;
std::vector<Pointer> pointers;
int32_t action;
+ ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
};
} // namespace
@@ -65,9 +66,10 @@
status_t publishSimpleMotionEventWithCoords(int32_t action, nsecs_t eventTime,
const std::vector<PointerProperties>& properties,
- const std::vector<PointerCoords>& coords);
+ const std::vector<PointerCoords>& coords,
+ ui::LogicalDisplayId displayId);
void publishSimpleMotionEvent(int32_t action, nsecs_t eventTime,
- const std::vector<Pointer>& pointers);
+ const std::vector<Pointer>& pointers, ui::LogicalDisplayId);
void publishInputEventEntries(const std::vector<InputEventEntry>& entries);
void consumeInputEventEntries(const std::vector<InputEventEntry>& entries,
std::chrono::nanoseconds frameTime);
@@ -76,7 +78,7 @@
status_t TouchResamplingTest::publishSimpleMotionEventWithCoords(
int32_t action, nsecs_t eventTime, const std::vector<PointerProperties>& properties,
- const std::vector<PointerCoords>& coords) {
+ const std::vector<PointerCoords>& coords, ui::LogicalDisplayId displayId) {
const ui::Transform identityTransform;
const nsecs_t downTime = 0;
@@ -84,8 +86,8 @@
ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)";
}
return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1,
- AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
- INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0,
+ AINPUT_SOURCE_TOUCHSCREEN, displayId, INVALID_HMAC,
+ action, /*actionButton=*/0, /*flags=*/0,
/*edgeFlags=*/0, AMETA_NONE, /*buttonState=*/0,
MotionClassification::NONE, identityTransform,
/*xPrecision=*/0, /*yPrecision=*/0,
@@ -96,7 +98,8 @@
}
void TouchResamplingTest::publishSimpleMotionEvent(int32_t action, nsecs_t eventTime,
- const std::vector<Pointer>& pointers) {
+ const std::vector<Pointer>& pointers,
+ ui::LogicalDisplayId displayId) {
std::vector<PointerProperties> properties;
std::vector<PointerCoords> coords;
@@ -112,7 +115,8 @@
coords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, pointer.y);
}
- status_t result = publishSimpleMotionEventWithCoords(action, eventTime, properties, coords);
+ status_t result =
+ publishSimpleMotionEventWithCoords(action, eventTime, properties, coords, displayId);
ASSERT_EQ(OK, result);
}
@@ -122,7 +126,8 @@
*/
void TouchResamplingTest::publishInputEventEntries(const std::vector<InputEventEntry>& entries) {
for (const InputEventEntry& entry : entries) {
- publishSimpleMotionEvent(entry.action, entry.eventTime.count(), entry.pointers);
+ publishSimpleMotionEvent(entry.action, entry.eventTime.count(), entry.pointers,
+ entry.displayId);
}
}
@@ -178,6 +183,7 @@
ASSERT_EQ(entry.action, motionEvent->getAction());
ASSERT_EQ(entry.eventTime.count(), motionEvent->getHistoricalEventTime(i));
ASSERT_EQ(entry.pointers.size(), motionEvent->getPointerCount());
+ ASSERT_EQ(entry.displayId, motionEvent->getDisplayId());
for (size_t p = 0; p < motionEvent->getPointerCount(); p++) {
SCOPED_TRACE(p);
@@ -697,4 +703,45 @@
consumeInputEventEntries(expectedEntries, frameTime);
}
+TEST_F(TouchResamplingTest, EventsOnDifferentDisplaysAreNotResampled) {
+ std::chrono::nanoseconds frameTime;
+ std::vector<InputEventEntry> entries, expectedEntries;
+
+ // Initial ACTION_DOWN should be separate, because the first consume event will only return
+ // InputEvent with a single action.
+ entries = {
+ // id x y
+ {0ms, {{0, 10, 20}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 5ms;
+ expectedEntries = {
+ // id x y
+ {0ms, {{0, 10, 20}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y, but on
+ // different displays.
+ entries = {
+ // id x y
+ {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId::DEFAULT},
+ {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId(1)},
+ };
+ publishInputEventEntries(entries);
+
+ // They are not resampled and sent as two separate events.
+ frameTime = 35ms;
+ expectedEntries = {
+ // id x y
+ {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId::DEFAULT},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+ expectedEntries = {
+ // id x y
+ {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId(1)},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+}
+
} // namespace android