blob: 135f8b41a2e0aa762d7921ddbde87f9f229e77ed [file] [log] [blame]
Paul Ramirezbe9c5442024-07-10 00:12:41 +00001/**
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <input/Resampler.h>
18
19#include <gtest/gtest.h>
20
21#include <chrono>
22#include <memory>
23#include <vector>
24
25#include <input/Input.h>
26#include <input/InputEventBuilders.h>
27#include <input/InputTransport.h>
28#include <utils/Timers.h>
29
30namespace android {
31
32namespace {
33
34using namespace std::literals::chrono_literals;
35
36constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
37
38struct Pointer {
39 int32_t id{0};
40 ToolType toolType{ToolType::FINGER};
41 float x{0.0f};
42 float y{0.0f};
43 bool isResampled{false};
44 /**
45 * Converts from Pointer to PointerCoords. Enables calling LegacyResampler methods and
46 * assertions only with the relevant data for tests.
47 */
48 operator PointerCoords() const;
49};
50
51Pointer::operator PointerCoords() const {
52 PointerCoords pointerCoords;
53 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
54 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
55 pointerCoords.isResampled = isResampled;
56 return pointerCoords;
57}
58
59struct InputSample {
60 std::chrono::milliseconds eventTime{0};
61 std::vector<Pointer> pointers{};
62 /**
63 * Converts from InputSample to InputMessage. Enables calling LegacyResampler methods only with
64 * the relevant data for tests.
65 */
66 operator InputMessage() const;
67};
68
69InputSample::operator InputMessage() const {
70 InputMessage message;
71 message.header.type = InputMessage::Type::MOTION;
72 message.body.motion.pointerCount = pointers.size();
73 message.body.motion.eventTime = static_cast<std::chrono::nanoseconds>(eventTime).count();
74 message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
75 message.body.motion.downTime = 0;
76 const uint32_t pointerCount = message.body.motion.pointerCount;
77 for (uint32_t i = 0; i < pointerCount; ++i) {
78 message.body.motion.pointers[i].properties.id = pointers[i].id;
79 message.body.motion.pointers[i].properties.toolType = pointers[i].toolType;
80 message.body.motion.pointers[i].coords.setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
81 message.body.motion.pointers[i].coords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
82 message.body.motion.pointers[i].coords.isResampled = pointers[i].isResampled;
83 }
84 return message;
85}
86
87struct InputStream {
88 std::vector<InputSample> samples{};
89 int32_t action{0};
90 DeviceId deviceId{0};
91 /**
92 * Converts from InputStream to MotionEvent. Enables calling LegacyResampler methods only with
93 * the relevant data for tests.
94 */
95 operator MotionEvent() const;
96};
97
98InputStream::operator MotionEvent() const {
99 const InputSample& firstSample{*samples.begin()};
100 MotionEventBuilder motionEventBuilder =
101 MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
102 .downTime(0)
103 .eventTime(static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count())
104 .deviceId(deviceId);
105 for (const Pointer& pointer : firstSample.pointers) {
106 const PointerBuilder pointerBuilder =
107 PointerBuilder(pointer.id, pointer.toolType).x(pointer.x).y(pointer.y);
108 motionEventBuilder.pointer(pointerBuilder);
109 }
110 MotionEvent motionEvent = motionEventBuilder.build();
111 const size_t numSamples = samples.size();
112 for (size_t i = 1; i < numSamples; ++i) {
113 std::vector<PointerCoords> pointersCoords{samples[i].pointers.begin(),
114 samples[i].pointers.end()};
115 motionEvent.addSample(static_cast<std::chrono::nanoseconds>(samples[i].eventTime).count(),
116 pointersCoords.data(), motionEvent.getId());
117 }
118 return motionEvent;
119}
120
121} // namespace
122
123class ResamplerTest : public testing::Test {
124protected:
125 ResamplerTest() : mResampler(std::make_unique<LegacyResampler>()) {}
126
127 ~ResamplerTest() override {}
128
129 void SetUp() override {}
130
131 void TearDown() override {}
132
133 std::unique_ptr<Resampler> mResampler;
134
135 MotionEvent buildMotionEvent(const int32_t action, const nsecs_t eventTime,
136 const std::vector<PointerBuilder>& pointers);
137
138 InputMessage createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
139 const int32_t action,
140 const std::vector<PointerProperties>& properties,
141 const std::vector<PointerCoords>& coords);
142
143 /**
144 * Checks that beforeCall and afterCall are equal except for the mutated attributes by addSample
145 * member function.
146 * @param beforeCall MotionEvent before passing it to resampleMotionEvent
147 * @param afterCall MotionEvent after passing it to resampleMotionEvent
148 */
149 void assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
150 const MotionEvent& afterCall);
151
152 /**
153 * Asserts the MotionEvent is resampled by checking an increment in history size and that the
154 * resampled coordinates are near the expected ones.
155 */
156 void assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
157 const MotionEvent& resampled,
158 const PointerCoords& expectedCoords);
159
160 void assertMotionEventIsNotResampled(const MotionEvent& original,
161 const MotionEvent& notResampled);
162};
163
164MotionEvent ResamplerTest::buildMotionEvent(const int32_t action, const nsecs_t eventTime,
165 const std::vector<PointerBuilder>& pointerBuilders) {
166 MotionEventBuilder motionEventBuilder = MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
167 .downTime(0)
168 .eventTime(eventTime);
169 for (const PointerBuilder& pointerBuilder : pointerBuilders) {
170 motionEventBuilder.pointer(pointerBuilder);
171 }
172 return motionEventBuilder.build();
173}
174
175InputMessage ResamplerTest::createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
176 const int32_t action,
177 const std::vector<PointerProperties>& properties,
178 const std::vector<PointerCoords>& coords) {
179 InputMessage message;
180 message.header.type = InputMessage::Type::MOTION;
181 message.body.motion.pointerCount = pointerCount;
182 message.body.motion.eventTime = eventTime;
183 message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
184 message.body.motion.downTime = 0;
185 for (uint32_t i = 0; i < pointerCount; ++i) {
186 message.body.motion.pointers[i].properties = properties[i];
187 message.body.motion.pointers[i].coords = coords[i];
188 }
189 return message;
190}
191
192void ResamplerTest::assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
193 const MotionEvent& afterCall) {
194 EXPECT_EQ(beforeCall.getDeviceId(), afterCall.getDeviceId());
195 EXPECT_EQ(beforeCall.getAction(), afterCall.getAction());
196 EXPECT_EQ(beforeCall.getActionButton(), afterCall.getActionButton());
197 EXPECT_EQ(beforeCall.getButtonState(), afterCall.getButtonState());
198 EXPECT_EQ(beforeCall.getFlags(), afterCall.getFlags());
199 EXPECT_EQ(beforeCall.getEdgeFlags(), afterCall.getEdgeFlags());
200 EXPECT_EQ(beforeCall.getClassification(), afterCall.getClassification());
201 EXPECT_EQ(beforeCall.getPointerCount(), afterCall.getPointerCount());
202 EXPECT_EQ(beforeCall.getMetaState(), afterCall.getMetaState());
203 EXPECT_EQ(beforeCall.getSource(), afterCall.getSource());
204 EXPECT_EQ(beforeCall.getXPrecision(), afterCall.getXPrecision());
205 EXPECT_EQ(beforeCall.getYPrecision(), afterCall.getYPrecision());
206 EXPECT_EQ(beforeCall.getDownTime(), afterCall.getDownTime());
207 EXPECT_EQ(beforeCall.getDisplayId(), afterCall.getDisplayId());
208}
209
210void ResamplerTest::assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
211 const MotionEvent& resampled,
212 const PointerCoords& expectedCoords) {
213 assertMotionEventMetaDataDidNotMutate(original, resampled);
214 const size_t originalSampleSize = original.getHistorySize() + 1;
215 const size_t resampledSampleSize = resampled.getHistorySize() + 1;
216 EXPECT_EQ(originalSampleSize + 1, resampledSampleSize);
217 const PointerCoords& resampledCoords =
218 resampled.getSamplePointerCoords()[resampled.getHistorySize()];
219 EXPECT_TRUE(resampledCoords.isResampled);
220 EXPECT_NEAR(expectedCoords.getX(), resampledCoords.getX(), EPSILON);
221 EXPECT_NEAR(expectedCoords.getY(), resampledCoords.getY(), EPSILON);
222}
223
224void ResamplerTest::assertMotionEventIsNotResampled(const MotionEvent& original,
225 const MotionEvent& notResampled) {
226 assertMotionEventMetaDataDidNotMutate(original, notResampled);
227 const size_t originalSampleSize = original.getHistorySize() + 1;
228 const size_t notResampledSampleSize = notResampled.getHistorySize() + 1;
229 EXPECT_EQ(originalSampleSize, notResampledSampleSize);
230}
231
Paul Ramirez68ca3d12024-08-12 23:00:50 +0000232TEST_F(ResamplerTest, NonResampledAxesArePreserved) {
233 constexpr float TOUCH_MAJOR_VALUE = 1.0f;
234
235 MotionEvent motionEvent =
236 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
237 AMOTION_EVENT_ACTION_MOVE};
238
239 constexpr std::chrono::nanoseconds eventTime{10ms};
240 PointerCoords pointerCoords{};
241 pointerCoords.isResampled = false;
242 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 2.0f);
243 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 2.0f);
244 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, TOUCH_MAJOR_VALUE);
245
246 motionEvent.addSample(eventTime.count(), &pointerCoords, motionEvent.getId());
247
248 const InputMessage futureSample =
249 InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 4.0f, .isResampled = false}}};
250
251 const MotionEvent originalMotionEvent = motionEvent;
252
253 mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
254
255 EXPECT_EQ(motionEvent.getTouchMajor(0), TOUCH_MAJOR_VALUE);
256
257 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
258 Pointer{.id = 0,
259 .x = 2.2f,
260 .y = 2.4f,
261 .isResampled = true});
262}
263
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000264TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) {
265 MotionEvent motionEvent =
266 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
267 AMOTION_EVENT_ACTION_MOVE,
268 .deviceId = 0};
269 const MotionEvent originalMotionEvent = motionEvent;
270 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
271 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
272}
273
274TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
275 MotionEvent motionFromFirstDevice =
276 InputStream{{{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
277 {8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
278 AMOTION_EVENT_ACTION_MOVE,
279 .deviceId = 0};
280 mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
281 MotionEvent motionFromSecondDevice =
282 InputStream{{{11ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
283 AMOTION_EVENT_ACTION_MOVE,
284 .deviceId = 1};
285 const MotionEvent originalMotionEvent = motionFromSecondDevice;
286 mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
287 // The MotionEvent should not be resampled because the second event came from a different device
288 // than the previous event.
289 assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
290}
291
292// Increments of 16 ms for display refresh rate
293// Increments of 6 ms for input frequency
294// Resampling latency is known to be 5 ms
295// Therefore, first resampling time will be 11 ms
296
297/**
298 * Timeline
299 * ----+----------------------+---------+---------+---------+----------
300 * 0ms 10ms 11ms 15ms 16ms
301 * DOWN MOVE | MSG |
302 * resample frame
303 * Resampling occurs at 11ms. It is possible to interpolate because there is a sample available
304 * after the resample time. It is assumed that the InputMessage frequency is 100Hz, and the frame
305 * frequency is 60Hz. This means the time between InputMessage samples is 10ms, and the time between
306 * frames is ~16ms. Resample time is frameTime - RESAMPLE_LATENCY. The resampled sample must be the
307 * last one in the batch to consume.
308 */
309TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
310 MotionEvent motionEvent =
311 InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
312 AMOTION_EVENT_ACTION_MOVE};
313 const InputMessage futureSample =
314 InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
315
316 const MotionEvent originalMotionEvent = motionEvent;
317
318 mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
319
320 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
321 Pointer{.id = 0,
322 .x = 1.2f,
323 .y = 1.2f,
324 .isResampled = true});
325}
326
327TEST_F(ResamplerTest, SinglePointerDeltaTooSmallInterpolation) {
328 MotionEvent motionEvent =
329 InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
330 AMOTION_EVENT_ACTION_MOVE};
331 const InputMessage futureSample =
332 InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
333
334 const MotionEvent originalMotionEvent = motionEvent;
335
336 mResampler->resampleMotionEvent(10'500'000ns, motionEvent, &futureSample);
337
338 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
339}
340
341/**
342 * Tests extrapolation given two MotionEvents with a single sample.
343 */
344TEST_F(ResamplerTest, SinglePointerSingleSampleExtrapolation) {
345 MotionEvent previousMotionEvent =
346 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
347 AMOTION_EVENT_ACTION_MOVE};
348
349 mResampler->resampleMotionEvent(10ms, previousMotionEvent, nullptr);
350
351 MotionEvent motionEvent =
352 InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
353 AMOTION_EVENT_ACTION_MOVE};
354
355 const MotionEvent originalMotionEvent = motionEvent;
356
357 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
358
359 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
360 Pointer{.id = 0,
361 .x = 1.0f,
362 .y = 1.0f,
363 .isResampled = true});
364 // Integrity of the whole motionEvent
365 // History size should increment by 1
366 // Check if the resampled value is the last one
367 // Check if the resampleTime is correct
368 // Check if the PointerCoords are consistent with the other computations
369}
370
371TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
372 MotionEvent motionEvent =
373 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
374 {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
375 AMOTION_EVENT_ACTION_MOVE};
376 const InputMessage futureSample =
377 InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
378
379 const MotionEvent originalMotionEvent = motionEvent;
380
381 mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
382
383 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
384 Pointer{.id = 0,
385 .x = 2.2f,
386 .y = 2.2f,
387 .isResampled = true});
388}
389
390TEST_F(ResamplerTest, SinglePointerMultipleSampleExtrapolation) {
391 MotionEvent motionEvent =
392 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
393 {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
394 AMOTION_EVENT_ACTION_MOVE};
395
396 const MotionEvent originalMotionEvent = motionEvent;
397
398 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
399
400 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
401 Pointer{.id = 0,
402 .x = 2.2f,
403 .y = 2.2f,
404 .isResampled = true});
405}
406
407TEST_F(ResamplerTest, SinglePointerDeltaTooSmallExtrapolation) {
408 MotionEvent motionEvent =
409 InputStream{{{9ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
410 {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
411 AMOTION_EVENT_ACTION_MOVE};
412
413 const MotionEvent originalMotionEvent = motionEvent;
414
415 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
416
417 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
418}
419
420TEST_F(ResamplerTest, SinglePointerDeltaTooLargeExtrapolation) {
421 MotionEvent motionEvent =
422 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
423 {26ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
424 AMOTION_EVENT_ACTION_MOVE};
425
426 const MotionEvent originalMotionEvent = motionEvent;
427
428 mResampler->resampleMotionEvent(27ms, motionEvent, nullptr);
429
430 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
431}
432
433TEST_F(ResamplerTest, SinglePointerResampleTimeTooFarExtrapolation) {
434 MotionEvent motionEvent =
435 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
436 {25ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
437 AMOTION_EVENT_ACTION_MOVE};
438
439 const MotionEvent originalMotionEvent = motionEvent;
440
441 mResampler->resampleMotionEvent(43ms, motionEvent, nullptr);
442
443 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
444 Pointer{.id = 0,
445 .x = 2.4f,
446 .y = 2.4f,
447 .isResampled = true});
448}
449} // namespace android