/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include namespace android { class VelocityTrackerStrategy; /* * Calculates the velocity of pointer movements over time. */ class VelocityTracker { public: static const size_t MAX_DEGREE = 4; enum class Strategy : int32_t { DEFAULT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_DEFAULT, IMPULSE = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_IMPULSE, LSQ1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ1, LSQ2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ2, LSQ3 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ3, WLSQ2_DELTA = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA, WLSQ2_CENTRAL = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL, WLSQ2_RECENT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT, INT1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT1, INT2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT2, LEGACY = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LEGACY, MIN = IMPULSE, MAX = LEGACY, ftl_last = LEGACY, }; /* * Contains all available velocity data from a VelocityTracker. */ struct ComputedVelocity { inline std::optional getVelocity(int32_t axis, int32_t id) const { const auto& axisVelocities = mVelocities.find(axis); if (axisVelocities == mVelocities.end()) { return {}; } const auto& axisIdVelocity = axisVelocities->second.find(id); if (axisIdVelocity == axisVelocities->second.end()) { return {}; } return axisIdVelocity->second; } inline void addVelocity(int32_t axis, int32_t id, float velocity) { mVelocities[axis][id] = velocity; } private: std::map> mVelocities; }; // Creates a velocity tracker using the specified strategy for each supported axis. // If strategy is not provided, uses the default strategy for the platform. // TODO(b/32830165): support axis-specific strategies. VelocityTracker(const Strategy strategy = Strategy::DEFAULT); /** Return true if the axis is supported for velocity tracking, false otherwise. */ static bool isAxisSupported(int32_t axis); // Resets the velocity tracker state. void clear(); // Resets the velocity tracker state for a specific pointer. // Call this method when some pointers have changed and may be reusing // an id that was assigned to a different pointer earlier. void clearPointer(int32_t pointerId); // Adds movement information for a pointer for a specific axis void addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis, float position); // Adds movement information for all pointers in a MotionEvent, including historical samples. void addMovement(const MotionEvent& event); // Returns the velocity of the specified pointer id and axis in position units per second. // Returns empty optional if there is insufficient movement information for the pointer, or if // the given axis is not supported for velocity tracking. std::optional getVelocity(int32_t axis, int32_t pointerId) const; // Returns a ComputedVelocity instance with all available velocity data, using the given units // (reference: units == 1 means "per millisecond"), and clamping each velocity between // [-maxVelocity, maxVelocity], inclusive. ComputedVelocity getComputedVelocity(int32_t units, float maxVelocity); // Gets the active pointer id, or -1 if none. inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); } private: nsecs_t mLastEventTime; BitSet32 mCurrentPointerIdBits; std::optional mActivePointerId; // An override strategy passed in the constructor to be used for all axes. // This strategy will apply to all axes, unless the default strategy is specified here. // When default strategy is specified, then each axis will use a potentially different strategy // based on a hardcoded mapping. const Strategy mOverrideStrategy; // Maps axes to their respective VelocityTrackerStrategy instances. // Note that, only axes that have had MotionEvents (and not all supported axes) will be here. std::map> mConfiguredStrategies; void configureStrategy(int32_t axis); // Generates a VelocityTrackerStrategy instance for the given Strategy type. // The `deltaValues` parameter indicates whether or not the created strategy should treat motion // values as deltas (and not as absolute values). This the parameter is applicable only for // strategies that support differential axes. static std::unique_ptr createStrategy(const Strategy strategy, bool deltaValues); }; /* * Implements a particular velocity tracker algorithm. */ class VelocityTrackerStrategy { protected: VelocityTrackerStrategy() { } public: virtual ~VelocityTrackerStrategy() { } virtual void clearPointer(int32_t pointerId) = 0; virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0; virtual std::optional getVelocity(int32_t pointerId) const = 0; }; /** * A `VelocityTrackerStrategy` that accumulates added data points and processes the accumulated data * points when getting velocity. */ class AccumulatingVelocityTrackerStrategy : public VelocityTrackerStrategy { public: AccumulatingVelocityTrackerStrategy(nsecs_t horizonNanos, bool maintainHorizonDuringAdd); void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; void clearPointer(int32_t pointerId) override; protected: struct Movement { nsecs_t eventTime; float position; }; // Number of samples to keep. // If different strategies would like to maintain different history size, we can make this a // protected const field. static constexpr uint32_t HISTORY_SIZE = 20; /** * Duration, in nanoseconds, since the latest movement where a movement may be considered for * velocity calculation. */ const nsecs_t mHorizonNanos; /** * If true, data points outside of horizon (see `mHorizonNanos`) will be cleared after each * addition of a new movement. */ const bool mMaintainHorizonDuringAdd; std::map> mMovements; }; /* * Velocity tracker algorithm based on least-squares linear regression. */ class LeastSquaresVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: enum class Weighting { // No weights applied. All data points are equally reliable. NONE, // Weight by time delta. Data points clustered together are weighted less. DELTA, // Weight such that points within a certain horizon are weighed more than those // outside of that horizon. CENTRAL, // Weight such that points older than a certain amount are weighed less. RECENT, }; // Degree must be no greater than VelocityTracker::MAX_DEGREE. LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE); ~LeastSquaresVelocityTrackerStrategy() override; std::optional getVelocity(int32_t pointerId) const override; private: // Sample horizon. // We don't use too much history by default since we want to react to quick // changes in direction. static const nsecs_t HORIZON = 100 * 1000000; // 100 ms float chooseWeight(int32_t pointerId, uint32_t index) const; /** * An optimized least-squares solver for degree 2 and no weight (i.e. `Weighting.NONE`). * The provided container of movements shall NOT be empty, and shall have the movements in * chronological order. */ std::optional solveUnweightedLeastSquaresDeg2( const RingBuffer& movements) const; const uint32_t mDegree; const Weighting mWeighting; }; /* * Velocity tracker algorithm that uses an IIR filter. */ class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy { public: // Degree must be 1 or 2. IntegratingVelocityTrackerStrategy(uint32_t degree); ~IntegratingVelocityTrackerStrategy() override; void clearPointer(int32_t pointerId) override; void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override; std::optional getVelocity(int32_t pointerId) const override; private: // Current state estimate for a particular pointer. struct State { nsecs_t updateTime; uint32_t degree; float pos, vel, accel; }; const uint32_t mDegree; BitSet32 mPointerIdBits; State mPointerState[MAX_POINTER_ID + 1]; void initState(State& state, nsecs_t eventTime, float pos) const; void updateState(State& state, nsecs_t eventTime, float pos) const; }; /* * Velocity tracker strategy used prior to ICS. */ class LegacyVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: LegacyVelocityTrackerStrategy(); ~LegacyVelocityTrackerStrategy() override; std::optional getVelocity(int32_t pointerId) const override; private: // Oldest sample to consider when calculating the velocity. static const nsecs_t HORIZON = 200 * 1000000; // 100 ms // The minimum duration between samples when estimating velocity. static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms }; class ImpulseVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: ImpulseVelocityTrackerStrategy(bool deltaValues); ~ImpulseVelocityTrackerStrategy() override; std::optional getVelocity(int32_t pointerId) const override; private: // Sample horizon. // We don't use too much history by default since we want to react to quick // changes in direction. static constexpr nsecs_t HORIZON = 100 * 1000000; // 100 ms // Whether or not the input movement values for the strategy come in the form of delta values. // If the input values are not deltas, the strategy needs to calculate deltas as part of its // velocity calculation. const bool mDeltaValues; }; } // namespace android