Skip to content

Commit cfc6759

Browse files
Treehugger RobotAndroid (Google) Code Review
authored andcommitted
Merge changes I41cb79ea,I12eb8eae into main
* changes: Add logic to overwrite pointer coordinates if event time is too old Add logic to overwrite pointer coordinates in motion event
2 parents dce984f + 4679e55 commit cfc6759

3 files changed

Lines changed: 220 additions & 21 deletions

File tree

include/input/Resampler.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ class LegacyResampler final : public Resampler {
9999
*/
100100
RingBuffer<Sample> mLatestSamples{/*capacity=*/2};
101101

102+
/**
103+
* Latest sample in mLatestSamples after resampling motion event. Used to compare if a pointer
104+
* does not move between samples.
105+
*/
106+
std::optional<Sample> mLastRealSample;
107+
108+
/**
109+
* Latest prediction. Used to overwrite motion event samples if a set of conditions is met.
110+
*/
111+
std::optional<Sample> mPreviousPrediction;
112+
102113
/**
103114
* Adds up to mLatestSamples.capacity() of motionEvent's latest samples to mLatestSamples. If
104115
* motionEvent has fewer samples than mLatestSamples.capacity(), then the available samples are
@@ -144,6 +155,23 @@ class LegacyResampler final : public Resampler {
144155
*/
145156
std::optional<Sample> attemptExtrapolation(std::chrono::nanoseconds resampleTime) const;
146157

158+
/**
159+
* Iterates through motion event samples, and calls overwriteStillPointers on each sample.
160+
*/
161+
void overwriteMotionEventSamples(MotionEvent& motionEvent) const;
162+
163+
/**
164+
* Overwrites with resampled data the pointer coordinates that did not move between motion event
165+
* samples, that is, both x and y values are identical to mLastRealSample.
166+
*/
167+
void overwriteStillPointers(MotionEvent& motionEvent, size_t sampleIndex) const;
168+
169+
/**
170+
* Overwrites the pointer coordinates of a sample with event time older than
171+
* that of mPreviousPrediction.
172+
*/
173+
void overwriteOldPointers(MotionEvent& motionEvent, size_t sampleIndex) const;
174+
147175
inline static void addSampleToMotionEvent(const Sample& sample, MotionEvent& motionEvent);
148176
};
149177
} // namespace android

libs/input/Resampler.cpp

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <algorithm>
2020
#include <chrono>
21+
#include <ostream>
2122

2223
#include <android-base/logging.h>
2324
#include <android-base/properties.h>
@@ -26,10 +27,7 @@
2627
#include <input/Resampler.h>
2728
#include <utils/Timers.h>
2829

29-
using std::chrono::nanoseconds;
30-
3130
namespace android {
32-
3331
namespace {
3432

3533
const bool IS_DEBUGGABLE_BUILD =
@@ -49,6 +47,8 @@ bool debugResampling() {
4947
return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
5048
}
5149

50+
using std::chrono::nanoseconds;
51+
5252
constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5};
5353

5454
constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2};
@@ -75,6 +75,31 @@ PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoor
7575
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha));
7676
return resampledCoords;
7777
}
78+
79+
bool equalXY(const PointerCoords& a, const PointerCoords& b) {
80+
return (a.getX() == b.getX()) && (a.getY() == b.getY());
81+
}
82+
83+
void setMotionEventPointerCoords(MotionEvent& motionEvent, size_t sampleIndex, size_t pointerIndex,
84+
const PointerCoords& pointerCoords) {
85+
// Ideally, we should not cast away const. In this particular case, it's safe to cast away const
86+
// and dereference getHistoricalRawPointerCoords returned pointer because motionEvent is a
87+
// nonconst reference to a MotionEvent object, so mutating the object should not be undefined
88+
// behavior; moreover, the invoked method guarantees to return a valid pointer. Otherwise, it
89+
// fatally logs. Alternatively, we could've created a new MotionEvent from scratch, but this
90+
// approach is simpler and more efficient.
91+
PointerCoords& motionEventCoords = const_cast<PointerCoords&>(
92+
*(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex)));
93+
motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_X, pointerCoords.getX());
94+
motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointerCoords.getY());
95+
motionEventCoords.isResampled = pointerCoords.isResampled;
96+
}
97+
98+
std::ostream& operator<<(std::ostream& os, const PointerCoords& pointerCoords) {
99+
os << "(" << pointerCoords.getX() << ", " << pointerCoords.getY() << ")";
100+
return os;
101+
}
102+
78103
} // namespace
79104

80105
void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
@@ -85,12 +110,9 @@ void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
85110
std::vector<Pointer> pointers;
86111
const size_t numPointers = motionEvent.getPointerCount();
87112
for (size_t pointerIndex = 0; pointerIndex < numPointers; ++pointerIndex) {
88-
// getSamplePointerCoords is the vector representation of a getHistorySize by
89-
// getPointerCount matrix.
90-
const PointerCoords& pointerCoords =
91-
motionEvent.getSamplePointerCoords()[sampleIndex * numPointers + pointerIndex];
92-
pointers.push_back(
93-
Pointer{*motionEvent.getPointerProperties(pointerIndex), pointerCoords});
113+
pointers.push_back(Pointer{*(motionEvent.getPointerProperties(pointerIndex)),
114+
*(motionEvent.getHistoricalRawPointerCoords(pointerIndex,
115+
sampleIndex))});
94116
}
95117
mLatestSamples.pushBack(
96118
Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointers});
@@ -245,6 +267,47 @@ nanoseconds LegacyResampler::getResampleLatency() const {
245267
return RESAMPLE_LATENCY;
246268
}
247269

270+
void LegacyResampler::overwriteMotionEventSamples(MotionEvent& motionEvent) const {
271+
const size_t numSamples = motionEvent.getHistorySize() + 1;
272+
for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) {
273+
overwriteStillPointers(motionEvent, sampleIndex);
274+
overwriteOldPointers(motionEvent, sampleIndex);
275+
}
276+
}
277+
278+
void LegacyResampler::overwriteStillPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
279+
for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); ++pointerIndex) {
280+
const PointerCoords& pointerCoords =
281+
*(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex));
282+
if (equalXY(mLastRealSample->pointers[pointerIndex].coords, pointerCoords)) {
283+
LOG_IF(INFO, debugResampling())
284+
<< "Pointer ID: " << motionEvent.getPointerId(pointerIndex)
285+
<< " did not move. Overwriting its coordinates from " << pointerCoords << " to "
286+
<< mLastRealSample->pointers[pointerIndex].coords;
287+
setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
288+
mPreviousPrediction->pointers[pointerIndex].coords);
289+
}
290+
}
291+
}
292+
293+
void LegacyResampler::overwriteOldPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
294+
if (!mPreviousPrediction.has_value()) {
295+
return;
296+
}
297+
if (nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)} <
298+
mPreviousPrediction->eventTime) {
299+
LOG_IF(INFO, debugResampling())
300+
<< "Motion event sample older than predicted sample. Overwriting event time from "
301+
<< motionEvent.getHistoricalEventTime(sampleIndex) << "ns to "
302+
<< mPreviousPrediction->eventTime.count() << "ns.";
303+
for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
304+
++pointerIndex) {
305+
setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
306+
mPreviousPrediction->pointers[pointerIndex].coords);
307+
}
308+
}
309+
}
310+
248311
void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
249312
const InputMessage* futureSample) {
250313
const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
@@ -261,6 +324,16 @@ void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& mo
261324
: (attemptExtrapolation(resampleTime));
262325
if (sample.has_value()) {
263326
addSampleToMotionEvent(*sample, motionEvent);
327+
if (mPreviousPrediction.has_value()) {
328+
overwriteMotionEventSamples(motionEvent);
329+
}
330+
// mPreviousPrediction is only updated whenever extrapolation occurs because extrapolation
331+
// is about predicting upcoming scenarios.
332+
if (futureSample == nullptr) {
333+
mPreviousPrediction = sample;
334+
}
264335
}
336+
mLastRealSample = *(mLatestSamples.end() - 1);
265337
}
338+
266339
} // namespace android

libs/input/tests/InputConsumerResampling_test.cpp

Lines changed: 110 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,6 @@ TEST_F(InputConsumerResamplingTest, EventIsResampled) {
197197
mClientTestChannel->enqueueMessage(nextPointerMessage(
198198
{0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
199199

200-
mClientTestChannel->assertNoSentMessages();
201-
202200
invokeLooperCallback();
203201
assertReceivedMotionEvent({InputEventEntry{0ms,
204202
{Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
@@ -238,8 +236,6 @@ TEST_F(InputConsumerResamplingTest, EventIsResampledWithDifferentId) {
238236
mClientTestChannel->enqueueMessage(nextPointerMessage(
239237
{0ms, {Pointer{.id = 1, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
240238

241-
mClientTestChannel->assertNoSentMessages();
242-
243239
invokeLooperCallback();
244240
assertReceivedMotionEvent({InputEventEntry{0ms,
245241
{Pointer{.id = 1, .x = 10.0f, .y = 20.0f}},
@@ -280,8 +276,6 @@ TEST_F(InputConsumerResamplingTest, StylusEventIsResampled) {
280276
{Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::STYLUS}},
281277
AMOTION_EVENT_ACTION_DOWN}));
282278

283-
mClientTestChannel->assertNoSentMessages();
284-
285279
invokeLooperCallback();
286280
assertReceivedMotionEvent({InputEventEntry{0ms,
287281
{Pointer{.id = 0,
@@ -338,8 +332,6 @@ TEST_F(InputConsumerResamplingTest, MouseEventIsResampled) {
338332
{Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::MOUSE}},
339333
AMOTION_EVENT_ACTION_DOWN}));
340334

341-
mClientTestChannel->assertNoSentMessages();
342-
343335
invokeLooperCallback();
344336
assertReceivedMotionEvent({InputEventEntry{0ms,
345337
{Pointer{.id = 0,
@@ -396,8 +388,6 @@ TEST_F(InputConsumerResamplingTest, PalmEventIsNotResampled) {
396388
{Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::PALM}},
397389
AMOTION_EVENT_ACTION_DOWN}));
398390

399-
mClientTestChannel->assertNoSentMessages();
400-
401391
invokeLooperCallback();
402392
assertReceivedMotionEvent(
403393
{InputEventEntry{0ms,
@@ -438,8 +428,6 @@ TEST_F(InputConsumerResamplingTest, SampleTimeEqualsEventTime) {
438428
mClientTestChannel->enqueueMessage(nextPointerMessage(
439429
{0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
440430

441-
mClientTestChannel->assertNoSentMessages();
442-
443431
invokeLooperCallback();
444432
assertReceivedMotionEvent({InputEventEntry{0ms,
445433
{Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
@@ -468,4 +456,114 @@ TEST_F(InputConsumerResamplingTest, SampleTimeEqualsEventTime) {
468456
mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
469457
}
470458

459+
/**
460+
* Once we send a resampled value to the app, we should continue to send the last predicted value if
461+
* a pointer does not move. Only real values are used to determine if a pointer does not move.
462+
*/
463+
TEST_F(InputConsumerResamplingTest, ResampledValueIsUsedForIdenticalCoordinates) {
464+
// Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
465+
// InputEvent with a single action.
466+
mClientTestChannel->enqueueMessage(nextPointerMessage(
467+
{0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
468+
469+
invokeLooperCallback();
470+
assertReceivedMotionEvent({InputEventEntry{0ms,
471+
{Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
472+
AMOTION_EVENT_ACTION_DOWN}});
473+
474+
// Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
475+
mClientTestChannel->enqueueMessage(nextPointerMessage(
476+
{10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
477+
mClientTestChannel->enqueueMessage(nextPointerMessage(
478+
{20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
479+
480+
invokeLooperCallback();
481+
mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
482+
assertReceivedMotionEvent(
483+
{InputEventEntry{10ms,
484+
{Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
485+
AMOTION_EVENT_ACTION_MOVE},
486+
InputEventEntry{20ms,
487+
{Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
488+
AMOTION_EVENT_ACTION_MOVE},
489+
InputEventEntry{25ms,
490+
{Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
491+
AMOTION_EVENT_ACTION_MOVE}});
492+
493+
// Coordinate value 30 has been resampled to 35. When a new event comes in with value 30 again,
494+
// the system should still report 35.
495+
mClientTestChannel->enqueueMessage(nextPointerMessage(
496+
{40ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
497+
498+
invokeLooperCallback();
499+
mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + 5ms /*RESAMPLE_LATENCY*/}.count());
500+
assertReceivedMotionEvent(
501+
{InputEventEntry{40ms,
502+
{Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
503+
AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
504+
InputEventEntry{45ms,
505+
{Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
506+
AMOTION_EVENT_ACTION_MOVE}}); // resampled event, rewritten
507+
508+
mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
509+
mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
510+
mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
511+
mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
512+
}
513+
514+
TEST_F(InputConsumerResamplingTest, OldEventReceivedAfterResampleOccurs) {
515+
// Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
516+
// InputEvent with a single action.
517+
mClientTestChannel->enqueueMessage(nextPointerMessage(
518+
{0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
519+
520+
invokeLooperCallback();
521+
assertReceivedMotionEvent({InputEventEntry{0ms,
522+
{Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
523+
AMOTION_EVENT_ACTION_DOWN}});
524+
525+
// Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
526+
mClientTestChannel->enqueueMessage(nextPointerMessage(
527+
{10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
528+
mClientTestChannel->enqueueMessage(nextPointerMessage(
529+
{20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
530+
531+
invokeLooperCallback();
532+
mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
533+
assertReceivedMotionEvent(
534+
{InputEventEntry{10ms,
535+
{Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
536+
AMOTION_EVENT_ACTION_MOVE},
537+
InputEventEntry{20ms,
538+
{Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
539+
AMOTION_EVENT_ACTION_MOVE},
540+
InputEventEntry{25ms,
541+
{Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
542+
AMOTION_EVENT_ACTION_MOVE}});
543+
544+
// Above, the resampled event is at 25ms rather than at 30 ms = 35ms - RESAMPLE_LATENCY
545+
// because we are further bound by how far we can extrapolate by the "last time delta".
546+
// That's 50% of (20 ms - 10ms) => 5ms. So we can't predict more than 5 ms into the future
547+
// from the event at 20ms, which is why the resampled event is at t = 25 ms.
548+
549+
// We resampled the event to 25 ms. Now, an older 'real' event comes in.
550+
mClientTestChannel->enqueueMessage(nextPointerMessage(
551+
{24ms, {Pointer{.id = 0, .x = 40.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
552+
553+
invokeLooperCallback();
554+
mConsumer->consumeBatchedInputEvents(nanoseconds{50ms}.count());
555+
assertReceivedMotionEvent(
556+
{InputEventEntry{24ms,
557+
{Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
558+
AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
559+
InputEventEntry{26ms,
560+
{Pointer{.id = 0, .x = 45.0f, .y = 30.0f, .isResampled = true}},
561+
AMOTION_EVENT_ACTION_MOVE}}); // resampled event, rewritten
562+
563+
mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
564+
mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
565+
mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
566+
mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
567+
}
568+
471569
} // namespace android

0 commit comments

Comments
 (0)