1 /*
2  * Copyright (C) 2012 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 #pragma once
18 
19 #include <android/os/IInputConstants.h>
20 #include <input/Input.h>
21 #include <input/RingBuffer.h>
22 #include <utils/BitSet.h>
23 #include <utils/Timers.h>
24 #include <map>
25 #include <set>
26 
27 namespace android {
28 
29 class VelocityTrackerStrategy;
30 
31 /*
32  * Calculates the velocity of pointer movements over time.
33  */
34 class VelocityTracker {
35 public:
36     static const size_t MAX_DEGREE = 4;
37 
38     enum class Strategy : int32_t {
39         DEFAULT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_DEFAULT,
40         IMPULSE = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_IMPULSE,
41         LSQ1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ1,
42         LSQ2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ2,
43         LSQ3 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ3,
44         WLSQ2_DELTA = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA,
45         WLSQ2_CENTRAL = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL,
46         WLSQ2_RECENT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT,
47         INT1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT1,
48         INT2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT2,
49         LEGACY = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LEGACY,
50         MIN = IMPULSE,
51         MAX = LEGACY,
52         ftl_last = LEGACY,
53     };
54 
55     /*
56      * Contains all available velocity data from a VelocityTracker.
57      */
58     struct ComputedVelocity {
getVelocityComputedVelocity59         inline std::optional<float> getVelocity(int32_t axis, int32_t id) const {
60             const auto& axisVelocities = mVelocities.find(axis);
61             if (axisVelocities == mVelocities.end()) {
62                 return {};
63             }
64 
65             const auto& axisIdVelocity = axisVelocities->second.find(id);
66             if (axisIdVelocity == axisVelocities->second.end()) {
67                 return {};
68             }
69 
70             return axisIdVelocity->second;
71         }
72 
addVelocityComputedVelocity73         inline void addVelocity(int32_t axis, int32_t id, float velocity) {
74             mVelocities[axis][id] = velocity;
75         }
76 
77     private:
78         std::map<int32_t /*axis*/, std::map<int32_t /*pointerId*/, float /*velocity*/>> mVelocities;
79     };
80 
81     // Creates a velocity tracker using the specified strategy for each supported axis.
82     // If strategy is not provided, uses the default strategy for the platform.
83     // TODO(b/32830165): support axis-specific strategies.
84     VelocityTracker(const Strategy strategy = Strategy::DEFAULT);
85 
86     /** Return true if the axis is supported for velocity tracking, false otherwise. */
87     static bool isAxisSupported(int32_t axis);
88 
89     // Resets the velocity tracker state.
90     void clear();
91 
92     // Resets the velocity tracker state for a specific pointer.
93     // Call this method when some pointers have changed and may be reusing
94     // an id that was assigned to a different pointer earlier.
95     void clearPointer(int32_t pointerId);
96 
97     // Adds movement information for a pointer for a specific axis
98     void addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis, float position);
99 
100     // Adds movement information for all pointers in a MotionEvent, including historical samples.
101     void addMovement(const MotionEvent& event);
102 
103     // Returns the velocity of the specified pointer id and axis in position units per second.
104     // Returns empty optional if there is insufficient movement information for the pointer, or if
105     // the given axis is not supported for velocity tracking.
106     std::optional<float> getVelocity(int32_t axis, int32_t pointerId) const;
107 
108     // Returns a ComputedVelocity instance with all available velocity data, using the given units
109     // (reference: units == 1 means "per millisecond"), and clamping each velocity between
110     // [-maxVelocity, maxVelocity], inclusive.
111     ComputedVelocity getComputedVelocity(int32_t units, float maxVelocity);
112 
113     // Gets the active pointer id, or -1 if none.
getActivePointerId()114     inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); }
115 
116 private:
117     nsecs_t mLastEventTime;
118     BitSet32 mCurrentPointerIdBits;
119     std::optional<int32_t> mActivePointerId;
120 
121     // An override strategy passed in the constructor to be used for all axes.
122     // This strategy will apply to all axes, unless the default strategy is specified here.
123     // When default strategy is specified, then each axis will use a potentially different strategy
124     // based on a hardcoded mapping.
125     const Strategy mOverrideStrategy;
126     // Maps axes to their respective VelocityTrackerStrategy instances.
127     // Note that, only axes that have had MotionEvents (and not all supported axes) will be here.
128     std::map<int32_t /*axis*/, std::unique_ptr<VelocityTrackerStrategy>> mConfiguredStrategies;
129 
130     void configureStrategy(int32_t axis);
131 
132     // Generates a VelocityTrackerStrategy instance for the given Strategy type.
133     // The `deltaValues` parameter indicates whether or not the created strategy should treat motion
134     // values as deltas (and not as absolute values). This the parameter is applicable only for
135     // strategies that support differential axes.
136     static std::unique_ptr<VelocityTrackerStrategy> createStrategy(const Strategy strategy,
137                                                                    bool deltaValues);
138 };
139 
140 
141 /*
142  * Implements a particular velocity tracker algorithm.
143  */
144 class VelocityTrackerStrategy {
145 protected:
VelocityTrackerStrategy()146     VelocityTrackerStrategy() { }
147 
148 public:
~VelocityTrackerStrategy()149     virtual ~VelocityTrackerStrategy() { }
150 
151     virtual void clearPointer(int32_t pointerId) = 0;
152     virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0;
153     virtual std::optional<float> getVelocity(int32_t pointerId) const = 0;
154 };
155 
156 /**
157  * A `VelocityTrackerStrategy` that accumulates added data points and processes the accumulated data
158  * points when getting velocity.
159  */
160 class AccumulatingVelocityTrackerStrategy : public VelocityTrackerStrategy {
161 public:
162     AccumulatingVelocityTrackerStrategy(nsecs_t horizonNanos, bool maintainHorizonDuringAdd);
163 
164     void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
165     void clearPointer(int32_t pointerId) override;
166 
167 protected:
168     struct Movement {
169         nsecs_t eventTime;
170         float position;
171     };
172 
173     // Number of samples to keep.
174     // If different strategies would like to maintain different history size, we can make this a
175     // protected const field.
176     static constexpr uint32_t HISTORY_SIZE = 20;
177 
178     /**
179      * Duration, in nanoseconds, since the latest movement where a movement may be considered for
180      * velocity calculation.
181      */
182     const nsecs_t mHorizonNanos;
183     /**
184      * If true, data points outside of horizon (see `mHorizonNanos`) will be cleared after each
185      * addition of a new movement.
186      */
187     const bool mMaintainHorizonDuringAdd;
188     std::map<int32_t /*pointerId*/, RingBuffer<Movement>> mMovements;
189 };
190 
191 /*
192  * Velocity tracker algorithm based on least-squares linear regression.
193  */
194 class LeastSquaresVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
195 public:
196     enum class Weighting {
197         // No weights applied.  All data points are equally reliable.
198         NONE,
199 
200         // Weight by time delta.  Data points clustered together are weighted less.
201         DELTA,
202 
203         // Weight such that points within a certain horizon are weighed more than those
204         // outside of that horizon.
205         CENTRAL,
206 
207         // Weight such that points older than a certain amount are weighed less.
208         RECENT,
209     };
210 
211     // Degree must be no greater than VelocityTracker::MAX_DEGREE.
212     LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE);
213     ~LeastSquaresVelocityTrackerStrategy() override;
214 
215     std::optional<float> getVelocity(int32_t pointerId) const override;
216 
217 private:
218     // Sample horizon.
219     // We don't use too much history by default since we want to react to quick
220     // changes in direction.
221     static const nsecs_t HORIZON = 100 * 1000000; // 100 ms
222 
223     float chooseWeight(int32_t pointerId, uint32_t index) const;
224     /**
225      * An optimized least-squares solver for degree 2 and no weight (i.e. `Weighting.NONE`).
226      * The provided container of movements shall NOT be empty, and shall have the movements in
227      * chronological order.
228      */
229     std::optional<float> solveUnweightedLeastSquaresDeg2(
230             const RingBuffer<Movement>& movements) const;
231 
232     const uint32_t mDegree;
233     const Weighting mWeighting;
234 };
235 
236 /*
237  * Velocity tracker algorithm that uses an IIR filter.
238  */
239 class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
240 public:
241     // Degree must be 1 or 2.
242     IntegratingVelocityTrackerStrategy(uint32_t degree);
243     ~IntegratingVelocityTrackerStrategy() override;
244 
245     void clearPointer(int32_t pointerId) override;
246     void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override;
247     std::optional<float> getVelocity(int32_t pointerId) const override;
248 
249 private:
250     // Current state estimate for a particular pointer.
251     struct State {
252         nsecs_t updateTime;
253         uint32_t degree;
254 
255         float pos, vel, accel;
256     };
257 
258     const uint32_t mDegree;
259     BitSet32 mPointerIdBits;
260     State mPointerState[MAX_POINTER_ID + 1];
261 
262     void initState(State& state, nsecs_t eventTime, float pos) const;
263     void updateState(State& state, nsecs_t eventTime, float pos) const;
264 };
265 
266 
267 /*
268  * Velocity tracker strategy used prior to ICS.
269  */
270 class LegacyVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
271 public:
272     LegacyVelocityTrackerStrategy();
273     ~LegacyVelocityTrackerStrategy() override;
274 
275     std::optional<float> getVelocity(int32_t pointerId) const override;
276 
277 private:
278     // Oldest sample to consider when calculating the velocity.
279     static const nsecs_t HORIZON = 200 * 1000000; // 100 ms
280 
281     // The minimum duration between samples when estimating velocity.
282     static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
283 };
284 
285 class ImpulseVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
286 public:
287     ImpulseVelocityTrackerStrategy(bool deltaValues);
288     ~ImpulseVelocityTrackerStrategy() override;
289 
290     std::optional<float> getVelocity(int32_t pointerId) const override;
291 
292 private:
293     // Sample horizon.
294     // We don't use too much history by default since we want to react to quick
295     // changes in direction.
296     static constexpr nsecs_t HORIZON = 100 * 1000000; // 100 ms
297 
298     // Whether or not the input movement values for the strategy come in the form of delta values.
299     // If the input values are not deltas, the strategy needs to calculate deltas as part of its
300     // velocity calculation.
301     const bool mDeltaValues;
302 };
303 
304 } // namespace android
305