blob: a1e6e067841cb55e95b564de744dfdc9a6134048 [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#define LOG_TAG "LegacyResampler"
18
19#include <algorithm>
20#include <chrono>
21
22#include <android-base/logging.h>
23#include <android-base/properties.h>
Paul Ramirezcf1b06e2024-08-01 17:11:58 +000024#include <ftl/enum.h>
Paul Ramirezbe9c5442024-07-10 00:12:41 +000025
26#include <input/Resampler.h>
27#include <utils/Timers.h>
28
29using std::chrono::nanoseconds;
30
31namespace android {
32
33namespace {
34
35const bool IS_DEBUGGABLE_BUILD =
36#if defined(__ANDROID__)
37 android::base::GetBoolProperty("ro.debuggable", false);
38#else
39 true;
40#endif
41
42bool debugResampling() {
43 if (!IS_DEBUGGABLE_BUILD) {
44 static const bool DEBUG_TRANSPORT_RESAMPLING =
45 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
46 ANDROID_LOG_INFO);
47 return DEBUG_TRANSPORT_RESAMPLING;
48 }
49 return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
50}
51
52constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5};
53
54constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2};
55
56constexpr std::chrono::milliseconds RESAMPLE_MAX_DELTA{20};
57
58constexpr std::chrono::milliseconds RESAMPLE_MAX_PREDICTION{8};
59
Paul Ramirezcf1b06e2024-08-01 17:11:58 +000060bool canResampleTool(ToolType toolType) {
61 return toolType == ToolType::FINGER || toolType == ToolType::MOUSE ||
62 toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN;
63}
64
Paul Ramirezbe9c5442024-07-10 00:12:41 +000065inline float lerp(float a, float b, float alpha) {
66 return a + alpha * (b - a);
67}
68
Paul Ramirez486ca6d2024-08-12 14:37:02 +000069PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoords& b,
70 float alpha) {
Paul Ramirez68ca3d12024-08-12 23:00:50 +000071 // We use the value of alpha to initialize resampledCoords with the latest sample information.
72 PointerCoords resampledCoords = (alpha < 1.0f) ? a : b;
Paul Ramirezbe9c5442024-07-10 00:12:41 +000073 resampledCoords.isResampled = true;
74 resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, lerp(a.getX(), b.getX(), alpha));
75 resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha));
76 return resampledCoords;
77}
78} // namespace
79
80void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
Paul Ramirez486ca6d2024-08-12 14:37:02 +000081 const size_t numSamples = motionEvent.getHistorySize() + 1;
Paul Ramirezcf1b06e2024-08-01 17:11:58 +000082 const size_t latestIndex = numSamples - 1;
83 const size_t secondToLatestIndex = (latestIndex > 0) ? (latestIndex - 1) : 0;
84 for (size_t sampleIndex = secondToLatestIndex; sampleIndex < numSamples; ++sampleIndex) {
85 std::vector<Pointer> pointers;
86 const size_t numPointers = motionEvent.getPointerCount();
87 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});
94 }
Paul Ramirez486ca6d2024-08-12 14:37:02 +000095 mLatestSamples.pushBack(
Paul Ramirezcf1b06e2024-08-01 17:11:58 +000096 Sample{static_cast<nanoseconds>(motionEvent.getHistoricalEventTime(sampleIndex)),
97 pointers});
Paul Ramirezbe9c5442024-07-10 00:12:41 +000098 }
99}
100
Paul Ramirezcf1b06e2024-08-01 17:11:58 +0000101LegacyResampler::Sample LegacyResampler::messageToSample(const InputMessage& message) {
102 std::vector<Pointer> pointers;
103 for (uint32_t i = 0; i < message.body.motion.pointerCount; ++i) {
104 pointers.push_back(Pointer{message.body.motion.pointers[i].properties,
105 message.body.motion.pointers[i].coords});
106 }
107 return Sample{static_cast<nanoseconds>(message.body.motion.eventTime), pointers};
108}
109
110bool LegacyResampler::pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary) {
111 if (target.pointers.size() > auxiliary.pointers.size()) {
112 LOG_IF(INFO, debugResampling())
113 << "Not resampled. Auxiliary sample has fewer pointers than target sample.";
114 return false;
115 }
116 for (size_t i = 0; i < target.pointers.size(); ++i) {
117 if (target.pointers[i].properties.id != auxiliary.pointers[i].properties.id) {
118 LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ID mismatch.";
119 return false;
120 }
121 if (target.pointers[i].properties.toolType != auxiliary.pointers[i].properties.toolType) {
122 LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ToolType mismatch.";
123 return false;
124 }
125 if (!canResampleTool(target.pointers[i].properties.toolType)) {
126 LOG_IF(INFO, debugResampling())
127 << "Not resampled. Cannot resample "
128 << ftl::enum_string(target.pointers[i].properties.toolType) << " ToolType.";
129 return false;
130 }
131 }
132 return true;
133}
134
135bool LegacyResampler::canInterpolate(const InputMessage& message) const {
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000136 LOG_IF(FATAL, mLatestSamples.empty())
137 << "Not resampled. mLatestSamples must not be empty to interpolate.";
138
139 const Sample& pastSample = *(mLatestSamples.end() - 1);
Paul Ramirezcf1b06e2024-08-01 17:11:58 +0000140 const Sample& futureSample = messageToSample(message);
141
142 if (!pointerPropertiesResampleable(pastSample, futureSample)) {
143 return false;
144 }
145
146 const nanoseconds delta = futureSample.eventTime - pastSample.eventTime;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000147 if (delta < RESAMPLE_MIN_DELTA) {
148 LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000149 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000150 }
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000151 return true;
152}
153
154std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation(
155 nanoseconds resampleTime, const InputMessage& futureSample) const {
156 if (!canInterpolate(futureSample)) {
157 return std::nullopt;
158 }
159 LOG_IF(FATAL, mLatestSamples.empty())
160 << "Not resampled. mLatestSamples must not be empty to interpolate.";
161
162 const Sample& pastSample = *(mLatestSamples.end() - 1);
Paul Ramirezcf1b06e2024-08-01 17:11:58 +0000163
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000164 const nanoseconds delta =
165 static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000166 const float alpha =
167 std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000168
Paul Ramirezcf1b06e2024-08-01 17:11:58 +0000169 std::vector<Pointer> resampledPointers;
170 for (size_t i = 0; i < pastSample.pointers.size(); ++i) {
171 const PointerCoords& resampledCoords =
172 calculateResampledCoords(pastSample.pointers[i].coords,
173 futureSample.body.motion.pointers[i].coords, alpha);
174 resampledPointers.push_back(Pointer{pastSample.pointers[i].properties, resampledCoords});
175 }
176 return Sample{resampleTime, resampledPointers};
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000177}
178
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000179bool LegacyResampler::canExtrapolate() const {
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000180 if (mLatestSamples.size() < 2) {
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000181 LOG_IF(INFO, debugResampling()) << "Not resampled. Not enough data.";
182 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000183 }
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000184
185 const Sample& pastSample = *(mLatestSamples.end() - 2);
186 const Sample& presentSample = *(mLatestSamples.end() - 1);
187
Paul Ramirezcf1b06e2024-08-01 17:11:58 +0000188 if (!pointerPropertiesResampleable(presentSample, pastSample)) {
189 return false;
190 }
191
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000192 const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000193 if (delta < RESAMPLE_MIN_DELTA) {
194 LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000195 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000196 } else if (delta > RESAMPLE_MAX_DELTA) {
197 LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns.";
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000198 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000199 }
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000200 return true;
201}
202
203std::optional<LegacyResampler::Sample> LegacyResampler::attemptExtrapolation(
204 nanoseconds resampleTime) const {
205 if (!canExtrapolate()) {
206 return std::nullopt;
207 }
208 LOG_IF(FATAL, mLatestSamples.size() < 2)
209 << "Not resampled. mLatestSamples must have at least two samples to extrapolate.";
210
211 const Sample& pastSample = *(mLatestSamples.end() - 2);
212 const Sample& presentSample = *(mLatestSamples.end() - 1);
213
214 const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000215 // The farthest future time to which we can extrapolate. If the given resampleTime exceeds this,
216 // we use this value as the resample time target.
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000217 const nanoseconds farthestPrediction =
218 presentSample.eventTime + std::min<nanoseconds>(delta / 2, RESAMPLE_MAX_PREDICTION);
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000219 const nanoseconds newResampleTime =
220 (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
221 LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
222 << "Resample time is too far in the future. Adjusting prediction from "
223 << (resampleTime - presentSample.eventTime) << " to "
224 << (farthestPrediction - presentSample.eventTime) << "ns.";
225 const float alpha =
226 std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
227 delta;
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000228
Paul Ramirezcf1b06e2024-08-01 17:11:58 +0000229 std::vector<Pointer> resampledPointers;
230 for (size_t i = 0; i < presentSample.pointers.size(); ++i) {
231 const PointerCoords& resampledCoords =
232 calculateResampledCoords(pastSample.pointers[i].coords,
233 presentSample.pointers[i].coords, alpha);
234 resampledPointers.push_back(Pointer{presentSample.pointers[i].properties, resampledCoords});
235 }
236 return Sample{newResampleTime, resampledPointers};
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000237}
238
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000239inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
240 MotionEvent& motionEvent) {
Paul Ramirezcf1b06e2024-08-01 17:11:58 +0000241 motionEvent.addSample(sample.eventTime.count(), sample.asPointerCoords().data(),
242 motionEvent.getId());
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000243}
244
245void LegacyResampler::resampleMotionEvent(nanoseconds resampleTime, MotionEvent& motionEvent,
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000246 const InputMessage* futureSample) {
247 if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
248 mLatestSamples.clear();
249 }
250 mPreviousDeviceId = motionEvent.getDeviceId();
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000251
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000252 updateLatestSamples(motionEvent);
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000253
254 const std::optional<Sample> sample = (futureSample != nullptr)
255 ? (attemptInterpolation(resampleTime, *futureSample))
256 : (attemptExtrapolation(resampleTime));
257 if (sample.has_value()) {
258 addSampleToMotionEvent(*sample, motionEvent);
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000259 }
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000260}
261} // namespace android