blob: e160ca06d0316fb0601f7ca0c4b622c1c3bf7d6a [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
232TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) {
233 MotionEvent motionEvent =
234 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
235 AMOTION_EVENT_ACTION_MOVE,
236 .deviceId = 0};
237 const MotionEvent originalMotionEvent = motionEvent;
238 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
239 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
240}
241
242TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
243 MotionEvent motionFromFirstDevice =
244 InputStream{{{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
245 {8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
246 AMOTION_EVENT_ACTION_MOVE,
247 .deviceId = 0};
248 mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
249 MotionEvent motionFromSecondDevice =
250 InputStream{{{11ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
251 AMOTION_EVENT_ACTION_MOVE,
252 .deviceId = 1};
253 const MotionEvent originalMotionEvent = motionFromSecondDevice;
254 mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
255 // The MotionEvent should not be resampled because the second event came from a different device
256 // than the previous event.
257 assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
258}
259
260// Increments of 16 ms for display refresh rate
261// Increments of 6 ms for input frequency
262// Resampling latency is known to be 5 ms
263// Therefore, first resampling time will be 11 ms
264
265/**
266 * Timeline
267 * ----+----------------------+---------+---------+---------+----------
268 * 0ms 10ms 11ms 15ms 16ms
269 * DOWN MOVE | MSG |
270 * resample frame
271 * Resampling occurs at 11ms. It is possible to interpolate because there is a sample available
272 * after the resample time. It is assumed that the InputMessage frequency is 100Hz, and the frame
273 * frequency is 60Hz. This means the time between InputMessage samples is 10ms, and the time between
274 * frames is ~16ms. Resample time is frameTime - RESAMPLE_LATENCY. The resampled sample must be the
275 * last one in the batch to consume.
276 */
277TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
278 MotionEvent motionEvent =
279 InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
280 AMOTION_EVENT_ACTION_MOVE};
281 const InputMessage futureSample =
282 InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
283
284 const MotionEvent originalMotionEvent = motionEvent;
285
286 mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
287
288 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
289 Pointer{.id = 0,
290 .x = 1.2f,
291 .y = 1.2f,
292 .isResampled = true});
293}
294
295TEST_F(ResamplerTest, SinglePointerDeltaTooSmallInterpolation) {
296 MotionEvent motionEvent =
297 InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
298 AMOTION_EVENT_ACTION_MOVE};
299 const InputMessage futureSample =
300 InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
301
302 const MotionEvent originalMotionEvent = motionEvent;
303
304 mResampler->resampleMotionEvent(10'500'000ns, motionEvent, &futureSample);
305
306 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
307}
308
309/**
310 * Tests extrapolation given two MotionEvents with a single sample.
311 */
312TEST_F(ResamplerTest, SinglePointerSingleSampleExtrapolation) {
313 MotionEvent previousMotionEvent =
314 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
315 AMOTION_EVENT_ACTION_MOVE};
316
317 mResampler->resampleMotionEvent(10ms, previousMotionEvent, nullptr);
318
319 MotionEvent motionEvent =
320 InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
321 AMOTION_EVENT_ACTION_MOVE};
322
323 const MotionEvent originalMotionEvent = motionEvent;
324
325 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
326
327 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
328 Pointer{.id = 0,
329 .x = 1.0f,
330 .y = 1.0f,
331 .isResampled = true});
332 // Integrity of the whole motionEvent
333 // History size should increment by 1
334 // Check if the resampled value is the last one
335 // Check if the resampleTime is correct
336 // Check if the PointerCoords are consistent with the other computations
337}
338
339TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
340 MotionEvent motionEvent =
341 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
342 {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
343 AMOTION_EVENT_ACTION_MOVE};
344 const InputMessage futureSample =
345 InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
346
347 const MotionEvent originalMotionEvent = motionEvent;
348
349 mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
350
351 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
352 Pointer{.id = 0,
353 .x = 2.2f,
354 .y = 2.2f,
355 .isResampled = true});
356}
357
358TEST_F(ResamplerTest, SinglePointerMultipleSampleExtrapolation) {
359 MotionEvent motionEvent =
360 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
361 {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
362 AMOTION_EVENT_ACTION_MOVE};
363
364 const MotionEvent originalMotionEvent = motionEvent;
365
366 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
367
368 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
369 Pointer{.id = 0,
370 .x = 2.2f,
371 .y = 2.2f,
372 .isResampled = true});
373}
374
375TEST_F(ResamplerTest, SinglePointerDeltaTooSmallExtrapolation) {
376 MotionEvent motionEvent =
377 InputStream{{{9ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
378 {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
379 AMOTION_EVENT_ACTION_MOVE};
380
381 const MotionEvent originalMotionEvent = motionEvent;
382
383 mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
384
385 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
386}
387
388TEST_F(ResamplerTest, SinglePointerDeltaTooLargeExtrapolation) {
389 MotionEvent motionEvent =
390 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
391 {26ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
392 AMOTION_EVENT_ACTION_MOVE};
393
394 const MotionEvent originalMotionEvent = motionEvent;
395
396 mResampler->resampleMotionEvent(27ms, motionEvent, nullptr);
397
398 assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
399}
400
401TEST_F(ResamplerTest, SinglePointerResampleTimeTooFarExtrapolation) {
402 MotionEvent motionEvent =
403 InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
404 {25ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
405 AMOTION_EVENT_ACTION_MOVE};
406
407 const MotionEvent originalMotionEvent = motionEvent;
408
409 mResampler->resampleMotionEvent(43ms, motionEvent, nullptr);
410
411 assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
412 Pointer{.id = 0,
413 .x = 2.4f,
414 .y = 2.4f,
415 .isResampled = true});
416}
417} // namespace android