xref: /aosp_15_r20/frameworks/native/libs/input/Resampler.cpp (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
1 /**
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 #define LOG_TAG "LegacyResampler"
18 
19 #include <algorithm>
20 #include <chrono>
21 #include <iomanip>
22 #include <ostream>
23 
24 #include <android-base/logging.h>
25 #include <android-base/properties.h>
26 #include <ftl/enum.h>
27 
28 #include <input/Resampler.h>
29 #include <utils/Timers.h>
30 
31 namespace android {
32 namespace {
33 
34 const bool IS_DEBUGGABLE_BUILD =
35 #if defined(__ANDROID__)
36         android::base::GetBoolProperty("ro.debuggable", false);
37 #else
38         true;
39 #endif
40 
41 /**
42  * Log debug messages about timestamp and coordinates of event resampling.
43  * Enable this via "adb shell setprop log.tag.LegacyResamplerResampling DEBUG"
44  * (requires restart)
45  */
debugResampling()46 bool debugResampling() {
47     if (!IS_DEBUGGABLE_BUILD) {
48         static const bool DEBUG_TRANSPORT_RESAMPLING =
49                 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
50                                           ANDROID_LOG_INFO);
51         return DEBUG_TRANSPORT_RESAMPLING;
52     }
53     return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
54 }
55 
56 using std::chrono::nanoseconds;
57 
58 constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5};
59 
60 constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2};
61 
62 constexpr std::chrono::milliseconds RESAMPLE_MAX_DELTA{20};
63 
64 constexpr std::chrono::milliseconds RESAMPLE_MAX_PREDICTION{8};
65 
canResampleTool(ToolType toolType)66 bool canResampleTool(ToolType toolType) {
67     return toolType == ToolType::FINGER || toolType == ToolType::MOUSE ||
68             toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN;
69 }
70 
lerp(float a,float b,float alpha)71 inline float lerp(float a, float b, float alpha) {
72     return a + alpha * (b - a);
73 }
74 
calculateResampledCoords(const PointerCoords & a,const PointerCoords & b,float alpha)75 PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoords& b,
76                                        float alpha) {
77     // We use the value of alpha to initialize resampledCoords with the latest sample information.
78     PointerCoords resampledCoords = (alpha < 1.0f) ? a : b;
79     resampledCoords.isResampled = true;
80     resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, lerp(a.getX(), b.getX(), alpha));
81     resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha));
82     return resampledCoords;
83 }
84 
equalXY(const PointerCoords & a,const PointerCoords & b)85 bool equalXY(const PointerCoords& a, const PointerCoords& b) {
86     return (a.getX() == b.getX()) && (a.getY() == b.getY());
87 }
88 
setMotionEventPointerCoords(MotionEvent & motionEvent,size_t sampleIndex,size_t pointerIndex,const PointerCoords & pointerCoords)89 void setMotionEventPointerCoords(MotionEvent& motionEvent, size_t sampleIndex, size_t pointerIndex,
90                                  const PointerCoords& pointerCoords) {
91     // Ideally, we should not cast away const. In this particular case, it's safe to cast away const
92     // and dereference getHistoricalRawPointerCoords returned pointer because motionEvent is a
93     // nonconst reference to a MotionEvent object, so mutating the object should not be undefined
94     // behavior; moreover, the invoked method guarantees to return a valid pointer. Otherwise, it
95     // fatally logs. Alternatively, we could've created a new MotionEvent from scratch, but this
96     // approach is simpler and more efficient.
97     PointerCoords& motionEventCoords = const_cast<PointerCoords&>(
98             *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex)));
99     motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_X, pointerCoords.getX());
100     motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointerCoords.getY());
101     motionEventCoords.isResampled = pointerCoords.isResampled;
102 }
103 
operator <<(std::ostream & os,const PointerCoords & pointerCoords)104 std::ostream& operator<<(std::ostream& os, const PointerCoords& pointerCoords) {
105     os << "(" << pointerCoords.getX() << ", " << pointerCoords.getY() << ")";
106     return os;
107 }
108 
109 } // namespace
110 
updateLatestSamples(const MotionEvent & motionEvent)111 void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
112     const size_t numSamples = motionEvent.getHistorySize() + 1;
113     const size_t latestIndex = numSamples - 1;
114     const size_t secondToLatestIndex = (latestIndex > 0) ? (latestIndex - 1) : 0;
115     for (size_t sampleIndex = secondToLatestIndex; sampleIndex < numSamples; ++sampleIndex) {
116         PointerMap pointerMap;
117         for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
118              ++pointerIndex) {
119             pointerMap.insert(Pointer{*(motionEvent.getPointerProperties(pointerIndex)),
120                                       *(motionEvent.getHistoricalRawPointerCoords(pointerIndex,
121                                                                                   sampleIndex))});
122         }
123         mLatestSamples.pushBack(
124                 Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointerMap});
125     }
126 }
127 
messageToSample(const InputMessage & message)128 LegacyResampler::Sample LegacyResampler::messageToSample(const InputMessage& message) {
129     PointerMap pointerMap;
130     for (uint32_t i = 0; i < message.body.motion.pointerCount; ++i) {
131         pointerMap.insert(Pointer{message.body.motion.pointers[i].properties,
132                                   message.body.motion.pointers[i].coords});
133     }
134     return Sample{nanoseconds{message.body.motion.eventTime}, pointerMap};
135 }
136 
pointerPropertiesResampleable(const Sample & target,const Sample & auxiliary)137 bool LegacyResampler::pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary) {
138     for (const Pointer& pointer : target.pointerMap) {
139         const std::optional<Pointer> auxiliaryPointer =
140                 auxiliary.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
141         if (!auxiliaryPointer.has_value()) {
142             LOG_IF(INFO, debugResampling())
143                     << "Not resampled. Auxiliary sample does not contain all pointers from target.";
144             return false;
145         }
146         if (pointer.properties.toolType != auxiliaryPointer->properties.toolType) {
147             LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ToolType mismatch.";
148             return false;
149         }
150         if (!canResampleTool(pointer.properties.toolType)) {
151             LOG_IF(INFO, debugResampling())
152                     << "Not resampled. Cannot resample "
153                     << ftl::enum_string(pointer.properties.toolType) << " ToolType.";
154             return false;
155         }
156     }
157     return true;
158 }
159 
canInterpolate(const InputMessage & message) const160 bool LegacyResampler::canInterpolate(const InputMessage& message) const {
161     LOG_IF(FATAL, mLatestSamples.empty())
162             << "Not resampled. mLatestSamples must not be empty to interpolate.";
163 
164     const Sample& pastSample = *(mLatestSamples.end() - 1);
165     const Sample& futureSample = messageToSample(message);
166 
167     if (!pointerPropertiesResampleable(pastSample, futureSample)) {
168         return false;
169     }
170 
171     const nanoseconds delta = futureSample.eventTime - pastSample.eventTime;
172     if (delta < RESAMPLE_MIN_DELTA) {
173         LOG_IF(INFO, debugResampling())
174                 << "Not resampled. Delta is too small: " << std::setprecision(3)
175                 << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
176         return false;
177     }
178     return true;
179 }
180 
attemptInterpolation(nanoseconds resampleTime,const InputMessage & futureMessage) const181 std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation(
182         nanoseconds resampleTime, const InputMessage& futureMessage) const {
183     if (!canInterpolate(futureMessage)) {
184         return std::nullopt;
185     }
186     LOG_IF(FATAL, mLatestSamples.empty())
187             << "Not resampled. mLatestSamples must not be empty to interpolate.";
188 
189     const Sample& pastSample = *(mLatestSamples.end() - 1);
190     const Sample& futureSample = messageToSample(futureMessage);
191 
192     const nanoseconds delta = nanoseconds{futureSample.eventTime} - pastSample.eventTime;
193     const float alpha =
194             std::chrono::duration<float, std::nano>(resampleTime - pastSample.eventTime) / delta;
195 
196     PointerMap resampledPointerMap;
197     for (const Pointer& pointer : pastSample.pointerMap) {
198         if (std::optional<Pointer> futureSamplePointer =
199                     futureSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
200             futureSamplePointer.has_value()) {
201             const PointerCoords& resampledCoords =
202                     calculateResampledCoords(pointer.coords, futureSamplePointer->coords, alpha);
203             resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords});
204         }
205     }
206     return Sample{resampleTime, resampledPointerMap};
207 }
208 
canExtrapolate() const209 bool LegacyResampler::canExtrapolate() const {
210     if (mLatestSamples.size() < 2) {
211         LOG_IF(INFO, debugResampling()) << "Not resampled. Not enough data.";
212         return false;
213     }
214 
215     const Sample& pastSample = *(mLatestSamples.end() - 2);
216     const Sample& presentSample = *(mLatestSamples.end() - 1);
217 
218     if (!pointerPropertiesResampleable(presentSample, pastSample)) {
219         return false;
220     }
221 
222     const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
223     if (delta < RESAMPLE_MIN_DELTA) {
224         LOG_IF(INFO, debugResampling())
225                 << "Not resampled. Delta is too small: " << std::setprecision(3)
226                 << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
227         return false;
228     } else if (delta > RESAMPLE_MAX_DELTA) {
229         LOG_IF(INFO, debugResampling())
230                 << "Not resampled. Delta is too large: " << std::setprecision(3)
231                 << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
232         return false;
233     }
234     return true;
235 }
236 
attemptExtrapolation(nanoseconds resampleTime) const237 std::optional<LegacyResampler::Sample> LegacyResampler::attemptExtrapolation(
238         nanoseconds resampleTime) const {
239     if (!canExtrapolate()) {
240         return std::nullopt;
241     }
242     LOG_IF(FATAL, mLatestSamples.size() < 2)
243             << "Not resampled. mLatestSamples must have at least two samples to extrapolate.";
244 
245     const Sample& pastSample = *(mLatestSamples.end() - 2);
246     const Sample& presentSample = *(mLatestSamples.end() - 1);
247 
248     const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
249     // The farthest future time to which we can extrapolate. If the given resampleTime exceeds this,
250     // we use this value as the resample time target.
251     const nanoseconds farthestPrediction =
252             presentSample.eventTime + std::min<nanoseconds>(delta / 2, RESAMPLE_MAX_PREDICTION);
253     const nanoseconds newResampleTime =
254             (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
255     LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
256             << "Resample time is too far in the future. Adjusting prediction from "
257             << std::setprecision(3)
258             << std::chrono::duration<double, std::milli>{resampleTime - presentSample.eventTime}
259                        .count()
260             << "ms to "
261             << std::chrono::duration<double, std::milli>{farthestPrediction -
262                                                          presentSample.eventTime}
263                        .count()
264             << "ms";
265     const float alpha =
266             std::chrono::duration<float, std::nano>(newResampleTime - pastSample.eventTime) / delta;
267 
268     PointerMap resampledPointerMap;
269     for (const Pointer& pointer : presentSample.pointerMap) {
270         if (std::optional<Pointer> pastSamplePointer =
271                     pastSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
272             pastSamplePointer.has_value()) {
273             const PointerCoords& resampledCoords =
274                     calculateResampledCoords(pastSamplePointer->coords, pointer.coords, alpha);
275             resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords});
276         }
277     }
278     return Sample{newResampleTime, resampledPointerMap};
279 }
280 
addSampleToMotionEvent(const Sample & sample,MotionEvent & motionEvent)281 inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
282                                                     MotionEvent& motionEvent) {
283     motionEvent.addSample(sample.eventTime.count(), sample.asPointerCoords().data(),
284                           motionEvent.getId());
285 }
286 
getResampleLatency() const287 nanoseconds LegacyResampler::getResampleLatency() const {
288     return RESAMPLE_LATENCY;
289 }
290 
291 /**
292  * The resampler is unaware of ACTION_DOWN. Thus, it needs to constantly check for pointer IDs
293  * occurrences. This problem could be fixed if the resampler has access to the entire stream of
294  * MotionEvent actions. That way, both ACTION_DOWN and ACTION_UP will be visible; therefore,
295  * facilitating pointer tracking between samples.
296  */
overwriteMotionEventSamples(MotionEvent & motionEvent) const297 void LegacyResampler::overwriteMotionEventSamples(MotionEvent& motionEvent) const {
298     const size_t numSamples = motionEvent.getHistorySize() + 1;
299     for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) {
300         overwriteStillPointers(motionEvent, sampleIndex);
301         overwriteOldPointers(motionEvent, sampleIndex);
302     }
303 }
304 
overwriteStillPointers(MotionEvent & motionEvent,size_t sampleIndex) const305 void LegacyResampler::overwriteStillPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
306     if (!mLastRealSample.has_value() || !mPreviousPrediction.has_value()) {
307         LOG_IF(INFO, debugResampling()) << "Still pointers not overwritten. Not enough data.";
308         return;
309     }
310     for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); ++pointerIndex) {
311         const std::optional<Pointer> lastRealPointer = mLastRealSample->pointerMap.find(
312                 PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
313         const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find(
314                 PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
315         // This could happen because resampler only receives ACTION_MOVE events.
316         if (!lastRealPointer.has_value() || !previousPointer.has_value()) {
317             continue;
318         }
319         const PointerCoords& pointerCoords =
320                 *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex));
321         if (equalXY(pointerCoords, lastRealPointer->coords)) {
322             LOG_IF(INFO, debugResampling())
323                     << "Pointer ID: " << motionEvent.getPointerId(pointerIndex)
324                     << " did not move. Overwriting its coordinates from " << pointerCoords << " to "
325                     << previousPointer->coords;
326             setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
327                                         previousPointer->coords);
328         }
329     }
330 }
331 
overwriteOldPointers(MotionEvent & motionEvent,size_t sampleIndex) const332 void LegacyResampler::overwriteOldPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
333     if (!mPreviousPrediction.has_value()) {
334         LOG_IF(INFO, debugResampling()) << "Old sample not overwritten. Not enough data.";
335         return;
336     }
337     if (nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)} <
338         mPreviousPrediction->eventTime) {
339         LOG_IF(INFO, debugResampling())
340                 << "Motion event sample older than predicted sample. Overwriting event time from "
341                 << std::setprecision(3)
342                 << std::chrono::duration<double,
343                                          std::milli>{nanoseconds{motionEvent.getHistoricalEventTime(
344                                                              sampleIndex)}}
345                            .count()
346                 << "ms to "
347                 << std::chrono::duration<double, std::milli>{mPreviousPrediction->eventTime}.count()
348                 << "ms";
349         for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
350              ++pointerIndex) {
351             const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find(
352                     PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
353             // This could happen because resampler only receives ACTION_MOVE events.
354             if (!previousPointer.has_value()) {
355                 continue;
356             }
357             setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
358                                         previousPointer->coords);
359         }
360     }
361 }
362 
resampleMotionEvent(nanoseconds frameTime,MotionEvent & motionEvent,const InputMessage * futureSample)363 void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
364                                           const InputMessage* futureSample) {
365     const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
366 
367     if (resampleTime.count() == motionEvent.getEventTime()) {
368         LOG_IF(INFO, debugResampling()) << "Not resampled. Resample time equals motion event time.";
369         return;
370     }
371 
372     updateLatestSamples(motionEvent);
373 
374     const std::optional<Sample> sample = (futureSample != nullptr)
375             ? (attemptInterpolation(resampleTime, *futureSample))
376             : (attemptExtrapolation(resampleTime));
377     if (sample.has_value()) {
378         addSampleToMotionEvent(*sample, motionEvent);
379         if (mPreviousPrediction.has_value()) {
380             overwriteMotionEventSamples(motionEvent);
381         }
382         // mPreviousPrediction is only updated whenever extrapolation occurs because extrapolation
383         // is about predicting upcoming scenarios.
384         if (futureSample == nullptr) {
385             mPreviousPrediction = sample;
386         }
387     }
388     LOG_IF(FATAL, mLatestSamples.empty()) << "mLatestSamples must contain at least one sample.";
389     mLastRealSample = *(mLatestSamples.end() - 1);
390 }
391 
392 // --- FilteredLegacyResampler ---
393 
FilteredLegacyResampler(float minCutoffFreq,float beta)394 FilteredLegacyResampler::FilteredLegacyResampler(float minCutoffFreq, float beta)
395       : mResampler{}, mMinCutoffFreq{minCutoffFreq}, mBeta{beta} {}
396 
resampleMotionEvent(std::chrono::nanoseconds requestedFrameTime,MotionEvent & motionEvent,const InputMessage * futureSample)397 void FilteredLegacyResampler::resampleMotionEvent(std::chrono::nanoseconds requestedFrameTime,
398                                                   MotionEvent& motionEvent,
399                                                   const InputMessage* futureSample) {
400     mResampler.resampleMotionEvent(requestedFrameTime, motionEvent, futureSample);
401     const size_t numSamples = motionEvent.getHistorySize() + 1;
402     for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) {
403         for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
404              ++pointerIndex) {
405             const int32_t pointerId = motionEvent.getPointerProperties(pointerIndex)->id;
406             const nanoseconds eventTime =
407                     nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)};
408             // Refer to the static function `setMotionEventPointerCoords` for a justification of
409             // casting away const.
410             PointerCoords& pointerCoords = const_cast<PointerCoords&>(
411                     *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex)));
412             const auto& [iter, _] = mFilteredPointers.try_emplace(pointerId, mMinCutoffFreq, mBeta);
413             iter->second.filter(eventTime, pointerCoords);
414         }
415     }
416 }
417 
getResampleLatency() const418 std::chrono::nanoseconds FilteredLegacyResampler::getResampleLatency() const {
419     return mResampler.getResampleLatency();
420 }
421 
422 } // namespace android
423