blob: 342f7f557ff8e171fe0b37ac54d1c9ea2630ff5d [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>
24
25#include <input/Resampler.h>
26#include <utils/Timers.h>
27
28using std::chrono::nanoseconds;
29
30namespace android {
31
32namespace {
33
34const bool IS_DEBUGGABLE_BUILD =
35#if defined(__ANDROID__)
36 android::base::GetBoolProperty("ro.debuggable", false);
37#else
38 true;
39#endif
40
41bool debugResampling() {
42 if (!IS_DEBUGGABLE_BUILD) {
43 static const bool DEBUG_TRANSPORT_RESAMPLING =
44 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
45 ANDROID_LOG_INFO);
46 return DEBUG_TRANSPORT_RESAMPLING;
47 }
48 return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
49}
50
51constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5};
52
53constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2};
54
55constexpr std::chrono::milliseconds RESAMPLE_MAX_DELTA{20};
56
57constexpr std::chrono::milliseconds RESAMPLE_MAX_PREDICTION{8};
58
59inline float lerp(float a, float b, float alpha) {
60 return a + alpha * (b - a);
61}
62
Paul Ramirez486ca6d2024-08-12 14:37:02 +000063PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoords& b,
64 float alpha) {
Paul Ramirez68ca3d12024-08-12 23:00:50 +000065 // We use the value of alpha to initialize resampledCoords with the latest sample information.
66 PointerCoords resampledCoords = (alpha < 1.0f) ? a : b;
Paul Ramirezbe9c5442024-07-10 00:12:41 +000067 resampledCoords.isResampled = true;
68 resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, lerp(a.getX(), b.getX(), alpha));
69 resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha));
70 return resampledCoords;
71}
72} // namespace
73
74void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
Paul Ramirez486ca6d2024-08-12 14:37:02 +000075 const size_t numSamples = motionEvent.getHistorySize() + 1;
76 for (size_t i = 0; i < numSamples; ++i) {
77 mLatestSamples.pushBack(
78 Sample{static_cast<nanoseconds>(motionEvent.getHistoricalEventTime(i)),
79 Pointer{*motionEvent.getPointerProperties(0),
80 motionEvent.getSamplePointerCoords()[i]}});
Paul Ramirezbe9c5442024-07-10 00:12:41 +000081 }
82}
83
Paul Ramirez486ca6d2024-08-12 14:37:02 +000084bool LegacyResampler::canInterpolate(const InputMessage& futureSample) const {
85 LOG_IF(FATAL, mLatestSamples.empty())
86 << "Not resampled. mLatestSamples must not be empty to interpolate.";
87
88 const Sample& pastSample = *(mLatestSamples.end() - 1);
Paul Ramirezbe9c5442024-07-10 00:12:41 +000089 const nanoseconds delta =
90 static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
91 if (delta < RESAMPLE_MIN_DELTA) {
92 LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
Paul Ramirez486ca6d2024-08-12 14:37:02 +000093 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +000094 }
Paul Ramirez486ca6d2024-08-12 14:37:02 +000095 return true;
96}
97
98std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation(
99 nanoseconds resampleTime, const InputMessage& futureSample) const {
100 if (!canInterpolate(futureSample)) {
101 return std::nullopt;
102 }
103 LOG_IF(FATAL, mLatestSamples.empty())
104 << "Not resampled. mLatestSamples must not be empty to interpolate.";
105
106 const Sample& pastSample = *(mLatestSamples.end() - 1);
107 const nanoseconds delta =
108 static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000109 const float alpha =
110 std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000111 const PointerCoords resampledCoords =
112 calculateResampledCoords(pastSample.pointer.coords,
113 futureSample.body.motion.pointers[0].coords, alpha);
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000114
115 return Sample{resampleTime, Pointer{pastSample.pointer.properties, resampledCoords}};
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000116}
117
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000118bool LegacyResampler::canExtrapolate() const {
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000119 if (mLatestSamples.size() < 2) {
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000120 LOG_IF(INFO, debugResampling()) << "Not resampled. Not enough data.";
121 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000122 }
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000123
124 const Sample& pastSample = *(mLatestSamples.end() - 2);
125 const Sample& presentSample = *(mLatestSamples.end() - 1);
126
127 const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000128 if (delta < RESAMPLE_MIN_DELTA) {
129 LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000130 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000131 } else if (delta > RESAMPLE_MAX_DELTA) {
132 LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns.";
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000133 return false;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000134 }
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000135 return true;
136}
137
138std::optional<LegacyResampler::Sample> LegacyResampler::attemptExtrapolation(
139 nanoseconds resampleTime) const {
140 if (!canExtrapolate()) {
141 return std::nullopt;
142 }
143 LOG_IF(FATAL, mLatestSamples.size() < 2)
144 << "Not resampled. mLatestSamples must have at least two samples to extrapolate.";
145
146 const Sample& pastSample = *(mLatestSamples.end() - 2);
147 const Sample& presentSample = *(mLatestSamples.end() - 1);
148
149 const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000150 // The farthest future time to which we can extrapolate. If the given resampleTime exceeds this,
151 // we use this value as the resample time target.
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000152 const nanoseconds farthestPrediction =
153 presentSample.eventTime + std::min<nanoseconds>(delta / 2, RESAMPLE_MAX_PREDICTION);
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000154 const nanoseconds newResampleTime =
155 (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
156 LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
157 << "Resample time is too far in the future. Adjusting prediction from "
158 << (resampleTime - presentSample.eventTime) << " to "
159 << (farthestPrediction - presentSample.eventTime) << "ns.";
160 const float alpha =
161 std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
162 delta;
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000163 const PointerCoords resampledCoords =
164 calculateResampledCoords(pastSample.pointer.coords, presentSample.pointer.coords,
165 alpha);
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000166
167 return Sample{newResampleTime, Pointer{presentSample.pointer.properties, resampledCoords}};
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000168}
169
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000170inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
171 MotionEvent& motionEvent) {
172 motionEvent.addSample(sample.eventTime.count(), &sample.pointer.coords, motionEvent.getId());
173}
174
175void LegacyResampler::resampleMotionEvent(nanoseconds resampleTime, MotionEvent& motionEvent,
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000176 const InputMessage* futureSample) {
177 if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
178 mLatestSamples.clear();
179 }
180 mPreviousDeviceId = motionEvent.getDeviceId();
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000181
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000182 updateLatestSamples(motionEvent);
Paul Ramirez486ca6d2024-08-12 14:37:02 +0000183
184 const std::optional<Sample> sample = (futureSample != nullptr)
185 ? (attemptInterpolation(resampleTime, *futureSample))
186 : (attemptExtrapolation(resampleTime));
187 if (sample.has_value()) {
188 addSampleToMotionEvent(*sample, motionEvent);
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000189 }
Paul Ramirezbe9c5442024-07-10 00:12:41 +0000190}
191} // namespace android