Make VelocityTracker 1D
Currently, VelocityTracker is strictly tied to X and Y axes. It's APIs
act on both axes, and its component structs (e.g. Position, Estimator)
are tied to both X and Y axes. As a step towards supporting more axes
for velocity tracking, this change:
- removes the Position struct: stores/processes data as pure floats, one
axis at a time
- makes Estimator and Strategy specific to a single axis, instead of
dealing with both/only X and Y at the same time
Furthermore, we have pulled into VelocityTracker the logic to compute
all velocity. This helps making the immediate JNI layer light-weight in
addition to allowing us to test the logic (which is non-trivial and
benefits from tests).
Bug: 32830165
Test: VelocityTracker_test unaffected (atest libinput_tests)
Change-Id: I181af7a033eb647e9cb97db9b86a36ae972290a5
diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h
index 1acc2ae..f4c7061 100644
--- a/include/input/VelocityControl.h
+++ b/include/input/VelocityControl.h
@@ -98,7 +98,7 @@
VelocityControlParameters mParameters;
nsecs_t mLastMovementTime;
- VelocityTracker::Position mRawPosition;
+ float mRawPositionX, mRawPositionY;
VelocityTracker mVelocityTracker;
};
diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h
index 886f1f7..6f2fcf4 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -20,6 +20,8 @@
#include <input/Input.h>
#include <utils/BitSet.h>
#include <utils/Timers.h>
+#include <map>
+#include <set>
namespace android {
@@ -46,18 +48,14 @@
MAX = LEGACY,
};
- struct Position {
- float x, y;
- };
-
struct Estimator {
static const size_t MAX_DEGREE = 4;
// Estimator time base.
nsecs_t time;
- // Polynomial coefficients describing motion in X and Y.
- float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1];
+ // Polynomial coefficients describing motion.
+ float coeff[MAX_DEGREE + 1];
// Polynomial degree (number of coefficients), or zero if no information is
// available.
@@ -71,14 +69,40 @@
degree = 0;
confidence = 0;
for (size_t i = 0; i <= MAX_DEGREE; i++) {
- xCoeff[i] = 0;
- yCoeff[i] = 0;
+ coeff[i] = 0;
}
}
};
- // Creates a velocity tracker using the specified strategy.
+ /*
+ * Contains all available velocity data from a VelocityTracker.
+ */
+ struct ComputedVelocity {
+ inline std::optional<float> getVelocity(int32_t axis, uint32_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, uint32_t id, float velocity) {
+ mVelocities[axis][id] = velocity;
+ }
+
+ private:
+ std::map<int32_t /*axis*/, std::map<int32_t /*pointerId*/, float /*velocity*/>> 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);
~VelocityTracker();
@@ -92,45 +116,57 @@
void clearPointers(BitSet32 idBits);
// Adds movement information for a set of pointers.
- // The idBits bitfield specifies the pointer ids of the pointers whose positions
+ // The idBits bitfield specifies the pointer ids of the pointers whose data points
// are included in the movement.
- // The positions array contains position information for each pointer in order by
- // increasing id. Its size should be equal to the number of one bits in idBits.
- void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector<Position>& positions);
+ // The positions map contains a mapping of an axis to positions array.
+ // The positions arrays contain information for each pointer in order by increasing id.
+ // Each array's size should be equal to the number of one bits in idBits.
+ void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const std::map<int32_t, std::vector<float>>& positions);
// Adds movement information for all pointers in a MotionEvent, including historical samples.
void addMovement(const MotionEvent* event);
- // Gets the velocity of the specified pointer id in position units per second.
- // Returns false and sets the velocity components to zero if there is
- // insufficient movement information for the pointer.
- bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
+ // 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<float> getVelocity(int32_t axis, uint32_t id) const;
- // Gets an estimator for the recent movements of the specified pointer id.
+ // Populates 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.
+ void populateComputedVelocity(ComputedVelocity& computedVelocity, int32_t units,
+ float maxVelocity);
+
+ // Gets an estimator for the recent movements of the specified pointer id for the given axis.
// Returns false and clears the estimator if there is no information available
// about the pointer.
- bool getEstimator(uint32_t id, Estimator* outEstimator) const;
+ bool getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const;
// Gets the active pointer id, or -1 if none.
inline int32_t getActivePointerId() const { return mActivePointerId; }
- // Gets a bitset containing all pointer ids from the most recent movement.
- inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; }
-
private:
// The default velocity tracker strategy.
// Although other strategies are available for testing and comparison purposes,
// this is the strategy that applications will actually use. Be very careful
// when adjusting the default strategy because it can dramatically affect
// (often in a bad way) the user experience.
+ // TODO(b/32830165): define default strategy per axis.
static const Strategy DEFAULT_STRATEGY = Strategy::LSQ2;
+ // Set of all axes supported for velocity tracking.
+ static const std::set<int32_t> SUPPORTED_AXES;
+
+ // Axes specifying location on a 2D plane (i.e. X and Y).
+ static const std::set<int32_t> PLANAR_AXES;
+
nsecs_t mLastEventTime;
BitSet32 mCurrentPointerIdBits;
int32_t mActivePointerId;
- std::unique_ptr<VelocityTrackerStrategy> mStrategy;
+ std::map<int32_t /*axis*/, std::unique_ptr<VelocityTrackerStrategy>> mStrategies;
- bool configureStrategy(const Strategy strategy);
+ void configureStrategy(int32_t axis, const Strategy strategy);
static std::unique_ptr<VelocityTrackerStrategy> createStrategy(const Strategy strategy);
};
@@ -149,7 +185,7 @@
virtual void clear() = 0;
virtual void clearPointers(BitSet32 idBits) = 0;
virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) = 0;
+ const std::vector<float>& positions) = 0;
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0;
};
@@ -181,7 +217,7 @@
virtual void clear();
virtual void clearPointers(BitSet32 idBits);
void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) override;
+ const std::vector<float>& positions) override;
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
private:
@@ -196,11 +232,9 @@
struct Movement {
nsecs_t eventTime;
BitSet32 idBits;
- VelocityTracker::Position positions[MAX_POINTERS];
+ float positions[MAX_POINTERS];
- inline const VelocityTracker::Position& getPosition(uint32_t id) const {
- return positions[idBits.getIndexOfBit(id)];
- }
+ inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
};
float chooseWeight(uint32_t index) const;
@@ -224,7 +258,7 @@
virtual void clear();
virtual void clearPointers(BitSet32 idBits);
void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) override;
+ const std::vector<float>& positions) override;
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
private:
@@ -233,16 +267,15 @@
nsecs_t updateTime;
uint32_t degree;
- float xpos, xvel, xaccel;
- float ypos, yvel, yaccel;
+ float pos, vel, accel;
};
const uint32_t mDegree;
BitSet32 mPointerIdBits;
State mPointerState[MAX_POINTER_ID + 1];
- void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
- void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
+ void initState(State& state, nsecs_t eventTime, float pos) const;
+ void updateState(State& state, nsecs_t eventTime, float pos) const;
void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const;
};
@@ -258,7 +291,7 @@
virtual void clear();
virtual void clearPointers(BitSet32 idBits);
void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) override;
+ const std::vector<float>& positions) override;
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
private:
@@ -274,11 +307,9 @@
struct Movement {
nsecs_t eventTime;
BitSet32 idBits;
- VelocityTracker::Position positions[MAX_POINTERS];
+ float positions[MAX_POINTERS];
- inline const VelocityTracker::Position& getPosition(uint32_t id) const {
- return positions[idBits.getIndexOfBit(id)];
- }
+ inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
};
uint32_t mIndex;
@@ -293,7 +324,7 @@
virtual void clear();
virtual void clearPointers(BitSet32 idBits);
void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) override;
+ const std::vector<float>& positions) override;
virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
private:
@@ -308,11 +339,9 @@
struct Movement {
nsecs_t eventTime;
BitSet32 idBits;
- VelocityTracker::Position positions[MAX_POINTERS];
+ float positions[MAX_POINTERS];
- inline const VelocityTracker::Position& getPosition(uint32_t id) const {
- return positions[idBits.getIndexOfBit(id)];
- }
+ inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
};
size_t mIndex;
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index 6e991e9..e2bfb50 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -44,8 +44,8 @@
void VelocityControl::reset() {
mLastMovementTime = LLONG_MIN;
- mRawPosition.x = 0;
- mRawPosition.y = 0;
+ mRawPositionX = 0;
+ mRawPositionY = 0;
mVelocityTracker.clear();
}
@@ -61,17 +61,20 @@
mLastMovementTime = eventTime;
if (deltaX) {
- mRawPosition.x += *deltaX;
+ mRawPositionX += *deltaX;
}
if (deltaY) {
- mRawPosition.y += *deltaY;
+ mRawPositionY += *deltaY;
}
- mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), {mRawPosition});
+ mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)),
+ {{AMOTION_EVENT_AXIS_X, {mRawPositionX}},
+ {AMOTION_EVENT_AXIS_Y, {mRawPositionY}}});
- float vx, vy;
+ std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
+ std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
float scale = mParameters.scale;
- if (mVelocityTracker.getVelocity(0, &vx, &vy)) {
- float speed = hypotf(vx, vy) * scale;
+ if (vx && vy) {
+ float speed = hypotf(*vx, *vy) * scale;
if (speed >= mParameters.highThreshold) {
// Apply full acceleration above the high speed threshold.
scale *= mParameters.acceleration;
@@ -85,10 +88,9 @@
if (DEBUG_ACCELERATION) {
ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
- "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
- mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
- mParameters.acceleration,
- vx, vy, speed, scale / mParameters.scale);
+ "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
+ mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+ mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale);
}
} else {
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 76aaf61..4f91af1 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -125,29 +125,39 @@
// --- VelocityTracker ---
+const std::set<int32_t> VelocityTracker::SUPPORTED_AXES = {AMOTION_EVENT_AXIS_X,
+ AMOTION_EVENT_AXIS_Y};
+
+const std::set<int32_t> VelocityTracker::PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y};
+
VelocityTracker::VelocityTracker(const Strategy strategy)
: mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) {
- // Configure the strategy.
- if (!configureStrategy(strategy)) {
- ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy);
- if (!configureStrategy(VelocityTracker::DEFAULT_STRATEGY)) {
- LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%" PRId32
- "'!",
- strategy);
- }
+ // Configure the strategy for each axis.
+ for (int32_t axis : SUPPORTED_AXES) {
+ configureStrategy(axis, strategy);
}
}
VelocityTracker::~VelocityTracker() {
}
-bool VelocityTracker::configureStrategy(Strategy strategy) {
+void VelocityTracker::configureStrategy(int32_t axis, const Strategy strategy) {
+ std::unique_ptr<VelocityTrackerStrategy> createdStrategy;
+
if (strategy == VelocityTracker::Strategy::DEFAULT) {
- mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY);
+ createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY);
} else {
- mStrategy = createStrategy(strategy);
+ createdStrategy = createStrategy(strategy);
}
- return mStrategy != nullptr;
+
+ if (createdStrategy == nullptr) {
+ ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy);
+ createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY);
+ LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr,
+ "Could not create the default velocity tracker strategy '%" PRId32 "'!",
+ strategy);
+ }
+ mStrategies[axis] = std::move(createdStrategy);
}
std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy(
@@ -201,8 +211,9 @@
void VelocityTracker::clear() {
mCurrentPointerIdBits.clear();
mActivePointerId = -1;
-
- mStrategy->clear();
+ for (int32_t axis : SUPPORTED_AXES) {
+ mStrategies[axis]->clear();
+ }
}
void VelocityTracker::clearPointers(BitSet32 idBits) {
@@ -213,14 +224,13 @@
mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
}
- mStrategy->clearPointers(idBits);
+ for (int32_t axis : SUPPORTED_AXES) {
+ mStrategies[axis]->clearPointers(idBits);
+ }
}
void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) {
- LOG_ALWAYS_FATAL_IF(idBits.count() != positions.size(),
- "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu",
- idBits.count(), positions.size());
+ const std::map<int32_t /*axis*/, std::vector<float>>& positions) {
while (idBits.count() > MAX_POINTERS) {
idBits.clearLastMarkedBit();
}
@@ -232,7 +242,9 @@
// We have not received any movements for too long. Assume that all pointers
// have stopped.
- mStrategy->clear();
+ for (const auto& [_, strategy] : mStrategies) {
+ strategy->clear();
+ }
}
mLastEventTime = eventTime;
@@ -241,29 +253,37 @@
mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
}
- mStrategy->addMovement(eventTime, idBits, positions);
+ for (const auto& [axis, positionValues] : positions) {
+ LOG_ALWAYS_FATAL_IF(idBits.count() != positionValues.size(),
+ "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu",
+ idBits.count(), positionValues.size());
+ mStrategies[axis]->addMovement(eventTime, idBits, positionValues);
+ }
if (DEBUG_VELOCITY) {
ALOGD("VelocityTracker: addMovement eventTime=%" PRId64
", idBits=0x%08x, activePointerId=%d",
eventTime, idBits.value, mActivePointerId);
- for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) {
- uint32_t id = iterBits.firstMarkedBit();
- uint32_t index = idBits.getIndexOfBit(id);
- iterBits.clearBit(id);
- Estimator estimator;
- getEstimator(id, &estimator);
- ALOGD(" %d: position (%0.3f, %0.3f), "
- "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
- id, positions[index].x, positions[index].y, int(estimator.degree),
- vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(),
- vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(),
- estimator.confidence);
+ for (const auto& positionsEntry : positions) {
+ for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) {
+ uint32_t id = iterBits.firstMarkedBit();
+ uint32_t index = idBits.getIndexOfBit(id);
+ iterBits.clearBit(id);
+ Estimator estimator;
+ getEstimator(positionsEntry.first, id, &estimator);
+ ALOGD(" %d: axis=%d, position=%0.3f, "
+ "estimator (degree=%d, coeff=%s, confidence=%f)",
+ id, positionsEntry.first, positionsEntry.second[index], int(estimator.degree),
+ vectorToString(estimator.coeff, estimator.degree + 1).c_str(),
+ estimator.confidence);
+ }
}
}
}
void VelocityTracker::addMovement(const MotionEvent* event) {
+ // Stores data about which axes to process based on the incoming motion event.
+ std::set<int32_t> axesToProcess;
int32_t actionMasked = event->getActionMasked();
switch (actionMasked) {
@@ -271,6 +291,9 @@
case AMOTION_EVENT_ACTION_HOVER_ENTER:
// Clear all pointers on down before adding the new movement.
clear();
+ for (int32_t axis : PLANAR_AXES) {
+ axesToProcess.insert(axis);
+ }
break;
case AMOTION_EVENT_ACTION_POINTER_DOWN: {
// Start a new movement trace for a pointer that just went down.
@@ -279,10 +302,16 @@
BitSet32 downIdBits;
downIdBits.markBit(event->getPointerId(event->getActionIndex()));
clearPointers(downIdBits);
+ for (int32_t axis : PLANAR_AXES) {
+ axesToProcess.insert(axis);
+ }
break;
}
case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ for (int32_t axis : PLANAR_AXES) {
+ axesToProcess.insert(axis);
+ }
break;
case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_UP: {
@@ -293,7 +322,9 @@
toString(delaySinceLastEvent).c_str());
// We have not received any movements for too long. Assume that all pointers
// have stopped.
- mStrategy->clear();
+ for (int32_t axis : PLANAR_AXES) {
+ mStrategies[axis]->clear();
+ }
}
// These actions because they do not convey any new information about
// pointer movement. We also want to preserve the last known velocity of the pointers.
@@ -325,37 +356,54 @@
pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
}
- std::vector<Position> positions;
- positions.resize(pointerCount);
+ std::map<int32_t, std::vector<float>> positions;
+ for (int32_t axis : axesToProcess) {
+ positions[axis].resize(pointerCount);
+ }
size_t historySize = event->getHistorySize();
for (size_t h = 0; h <= historySize; h++) {
nsecs_t eventTime = event->getHistoricalEventTime(h);
- for (size_t i = 0; i < pointerCount; i++) {
- uint32_t index = pointerIndex[i];
- positions[index].x = event->getHistoricalX(i, h);
- positions[index].y = event->getHistoricalY(i, h);
+ for (int32_t axis : axesToProcess) {
+ for (size_t i = 0; i < pointerCount; i++) {
+ positions[axis][pointerIndex[i]] = event->getHistoricalAxisValue(axis, i, h);
+ }
}
addMovement(eventTime, idBits, positions);
}
}
-bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
+std::optional<float> VelocityTracker::getVelocity(int32_t axis, uint32_t id) const {
Estimator estimator;
- if (getEstimator(id, &estimator) && estimator.degree >= 1) {
- *outVx = estimator.xCoeff[1];
- *outVy = estimator.yCoeff[1];
- return true;
+ bool validVelocity = getEstimator(axis, id, &estimator) && estimator.degree >= 1;
+ if (validVelocity) {
+ return estimator.coeff[1];
}
- *outVx = 0;
- *outVy = 0;
- return false;
+ return {};
}
-bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
- return mStrategy->getEstimator(id, outEstimator);
+void VelocityTracker::populateComputedVelocity(ComputedVelocity& computedVelocity, int32_t units,
+ float maxVelocity) {
+ for (int32_t axis : SUPPORTED_AXES) {
+ BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits);
+ while (!copyIdBits.isEmpty()) {
+ uint32_t id = copyIdBits.clearFirstMarkedBit();
+ std::optional<float> velocity = getVelocity(axis, id);
+ if (velocity) {
+ float adjustedVelocity =
+ std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity);
+ computedVelocity.addVelocity(axis, id, adjustedVelocity);
+ }
+ }
+ }
}
+bool VelocityTracker::getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const {
+ if (SUPPORTED_AXES.find(axis) == SUPPORTED_AXES.end()) {
+ return false;
+ }
+ return mStrategies.at(axis)->getEstimator(id, outEstimator);
+}
// --- LeastSquaresVelocityTrackerStrategy ---
@@ -378,9 +426,8 @@
mMovements[mIndex].idBits = remainingIdBits;
}
-void LeastSquaresVelocityTrackerStrategy::addMovement(
- nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) {
+void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const std::vector<float>& positions) {
if (mMovements[mIndex].eventTime != eventTime) {
// When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
// of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
@@ -627,8 +674,7 @@
outEstimator->clear();
// Iterate over movement samples in reverse time order and collect samples.
- std::vector<float> x;
- std::vector<float> y;
+ std::vector<float> positions;
std::vector<float> w;
std::vector<float> time;
@@ -645,15 +691,13 @@
break;
}
- const VelocityTracker::Position& position = movement.getPosition(id);
- x.push_back(position.x);
- y.push_back(position.y);
+ positions.push_back(movement.getPosition(id));
w.push_back(chooseWeight(index));
time.push_back(-age * 0.000000001f);
index = (index == 0 ? HISTORY_SIZE : index) - 1;
- } while (x.size() < HISTORY_SIZE);
+ } while (positions.size() < HISTORY_SIZE);
- const size_t m = x.size();
+ const size_t m = positions.size();
if (m == 0) {
return false; // no data
}
@@ -666,39 +710,36 @@
if (degree == 2 && mWeighting == WEIGHTING_NONE) {
// Optimize unweighted, quadratic polynomial fit
- std::optional<std::array<float, 3>> xCoeff = solveUnweightedLeastSquaresDeg2(time, x);
- std::optional<std::array<float, 3>> yCoeff = solveUnweightedLeastSquaresDeg2(time, y);
- if (xCoeff && yCoeff) {
+ std::optional<std::array<float, 3>> coeff =
+ solveUnweightedLeastSquaresDeg2(time, positions);
+ if (coeff) {
outEstimator->time = newestMovement.eventTime;
outEstimator->degree = 2;
outEstimator->confidence = 1;
for (size_t i = 0; i <= outEstimator->degree; i++) {
- outEstimator->xCoeff[i] = (*xCoeff)[i];
- outEstimator->yCoeff[i] = (*yCoeff)[i];
+ outEstimator->coeff[i] = (*coeff)[i];
}
return true;
}
} else if (degree >= 1) {
// General case for an Nth degree polynomial fit
- float xdet, ydet;
+ float det;
uint32_t n = degree + 1;
- if (solveLeastSquares(time, x, w, n, outEstimator->xCoeff, &xdet) &&
- solveLeastSquares(time, y, w, n, outEstimator->yCoeff, &ydet)) {
+ if (solveLeastSquares(time, positions, w, n, outEstimator->coeff, &det)) {
outEstimator->time = newestMovement.eventTime;
outEstimator->degree = degree;
- outEstimator->confidence = xdet * ydet;
+ outEstimator->confidence = det;
- ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
- int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(),
- vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence);
+ ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f",
+ int(outEstimator->degree), vectorToString(outEstimator->coeff, n).c_str(),
+ outEstimator->confidence);
return true;
}
}
// No velocity data available for this pointer, but we do have its current position.
- outEstimator->xCoeff[0] = x[0];
- outEstimator->yCoeff[0] = y[0];
+ outEstimator->coeff[0] = positions[0];
outEstimator->time = newestMovement.eventTime;
outEstimator->degree = 0;
outEstimator->confidence = 1;
@@ -790,18 +831,17 @@
mPointerIdBits.value &= ~idBits.value;
}
-void IntegratingVelocityTrackerStrategy::addMovement(
- nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) {
+void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const std::vector<float>& positions) {
uint32_t index = 0;
for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
uint32_t id = iterIdBits.clearFirstMarkedBit();
State& state = mPointerState[id];
- const VelocityTracker::Position& position = positions[index++];
+ const float position = positions[index++];
if (mPointerIdBits.hasBit(id)) {
- updateState(state, eventTime, position.x, position.y);
+ updateState(state, eventTime, position);
} else {
- initState(state, eventTime, position.x, position.y);
+ initState(state, eventTime, position);
}
}
@@ -821,21 +861,18 @@
return false;
}
-void IntegratingVelocityTrackerStrategy::initState(State& state,
- nsecs_t eventTime, float xpos, float ypos) const {
+void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime,
+ float pos) const {
state.updateTime = eventTime;
state.degree = 0;
- state.xpos = xpos;
- state.xvel = 0;
- state.xaccel = 0;
- state.ypos = ypos;
- state.yvel = 0;
- state.yaccel = 0;
+ state.pos = pos;
+ state.accel = 0;
+ state.vel = 0;
}
-void IntegratingVelocityTrackerStrategy::updateState(State& state,
- nsecs_t eventTime, float xpos, float ypos) const {
+void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime,
+ float pos) const {
const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
@@ -846,34 +883,26 @@
float dt = (eventTime - state.updateTime) * 0.000000001f;
state.updateTime = eventTime;
- float xvel = (xpos - state.xpos) / dt;
- float yvel = (ypos - state.ypos) / dt;
+ float vel = (pos - state.pos) / dt;
if (state.degree == 0) {
- state.xvel = xvel;
- state.yvel = yvel;
+ state.vel = vel;
state.degree = 1;
} else {
float alpha = dt / (FILTER_TIME_CONSTANT + dt);
if (mDegree == 1) {
- state.xvel += (xvel - state.xvel) * alpha;
- state.yvel += (yvel - state.yvel) * alpha;
+ state.vel += (vel - state.vel) * alpha;
} else {
- float xaccel = (xvel - state.xvel) / dt;
- float yaccel = (yvel - state.yvel) / dt;
+ float accel = (vel - state.vel) / dt;
if (state.degree == 1) {
- state.xaccel = xaccel;
- state.yaccel = yaccel;
+ state.accel = accel;
state.degree = 2;
} else {
- state.xaccel += (xaccel - state.xaccel) * alpha;
- state.yaccel += (yaccel - state.yaccel) * alpha;
+ state.accel += (accel - state.accel) * alpha;
}
- state.xvel += (state.xaccel * dt) * alpha;
- state.yvel += (state.yaccel * dt) * alpha;
+ state.vel += (state.accel * dt) * alpha;
}
}
- state.xpos = xpos;
- state.ypos = ypos;
+ state.pos = pos;
}
void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
@@ -881,12 +910,9 @@
outEstimator->time = state.updateTime;
outEstimator->confidence = 1.0f;
outEstimator->degree = state.degree;
- outEstimator->xCoeff[0] = state.xpos;
- outEstimator->xCoeff[1] = state.xvel;
- outEstimator->xCoeff[2] = state.xaccel / 2;
- outEstimator->yCoeff[0] = state.ypos;
- outEstimator->yCoeff[1] = state.yvel;
- outEstimator->yCoeff[2] = state.yaccel / 2;
+ outEstimator->coeff[0] = state.pos;
+ outEstimator->coeff[1] = state.vel;
+ outEstimator->coeff[2] = state.accel / 2;
}
@@ -909,9 +935,8 @@
mMovements[mIndex].idBits = remainingIdBits;
}
-void LegacyVelocityTrackerStrategy::addMovement(
- nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) {
+void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const std::vector<float>& positions) {
if (++mIndex == HISTORY_SIZE) {
mIndex = 0;
}
@@ -959,12 +984,11 @@
// overestimate the velocity at that time point. Most samples might be measured
// 16ms apart but some consecutive samples could be only 0.5sm apart because
// the hardware or driver reports them irregularly or in bursts.
- float accumVx = 0;
- float accumVy = 0;
+ float accumV = 0;
uint32_t index = oldestIndex;
uint32_t samplesUsed = 0;
const Movement& oldestMovement = mMovements[oldestIndex];
- const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id);
+ float oldestPosition = oldestMovement.getPosition(id);
nsecs_t lastDuration = 0;
while (numTouches-- > 1) {
@@ -978,26 +1002,22 @@
// the velocity. Consequently, we impose a minimum duration constraint on the
// samples that we include in the calculation.
if (duration >= MIN_DURATION) {
- const VelocityTracker::Position& position = movement.getPosition(id);
+ float position = movement.getPosition(id);
float scale = 1000000000.0f / duration; // one over time delta in seconds
- float vx = (position.x - oldestPosition.x) * scale;
- float vy = (position.y - oldestPosition.y) * scale;
- accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
- accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
+ float v = (position - oldestPosition) * scale;
+ accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration);
lastDuration = duration;
samplesUsed += 1;
}
}
// Report velocity.
- const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id);
+ float newestPosition = newestMovement.getPosition(id);
outEstimator->time = newestMovement.eventTime;
outEstimator->confidence = 1;
- outEstimator->xCoeff[0] = newestPosition.x;
- outEstimator->yCoeff[0] = newestPosition.y;
+ outEstimator->coeff[0] = newestPosition;
if (samplesUsed) {
- outEstimator->xCoeff[1] = accumVx;
- outEstimator->yCoeff[1] = accumVy;
+ outEstimator->coeff[1] = accumV;
outEstimator->degree = 1;
} else {
outEstimator->degree = 0;
@@ -1024,9 +1044,8 @@
mMovements[mIndex].idBits = remainingIdBits;
}
-void ImpulseVelocityTrackerStrategy::addMovement(
- nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) {
+void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const std::vector<float>& positions) {
if (mMovements[mIndex].eventTime != eventTime) {
// When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
// of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
@@ -1163,8 +1182,7 @@
outEstimator->clear();
// Iterate over movement samples in reverse time order and collect samples.
- float x[HISTORY_SIZE];
- float y[HISTORY_SIZE];
+ float positions[HISTORY_SIZE];
nsecs_t time[HISTORY_SIZE];
size_t m = 0; // number of points that will be used for fitting
size_t index = mIndex;
@@ -1180,9 +1198,7 @@
break;
}
- const VelocityTracker::Position& position = movement.getPosition(id);
- x[m] = position.x;
- y[m] = position.y;
+ positions[m] = movement.getPosition(id);
time[m] = movement.eventTime;
index = (index == 0 ? HISTORY_SIZE : index) - 1;
} while (++m < HISTORY_SIZE);
@@ -1190,33 +1206,30 @@
if (m == 0) {
return false; // no data
}
- outEstimator->xCoeff[0] = 0;
- outEstimator->yCoeff[0] = 0;
- outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m);
- outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m);
- outEstimator->xCoeff[2] = 0;
- outEstimator->yCoeff[2] = 0;
+ outEstimator->coeff[0] = 0;
+ outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m);
+ outEstimator->coeff[2] = 0;
+
outEstimator->time = newestMovement.eventTime;
outEstimator->degree = 2; // similar results to 2nd degree fit
outEstimator->confidence = 1;
- ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1],
- outEstimator->yCoeff[1]);
+ ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", outEstimator->coeff[1]);
if (DEBUG_IMPULSE) {
// TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
- // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons
+ // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
+ // X axis chosen arbitrarily for velocity comparisons.
VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
BitSet32 idBits;
const uint32_t pointerId = 0;
idBits.markBit(pointerId);
for (ssize_t i = m - 1; i >= 0; i--) {
- lsq2.addMovement(time[i], idBits, {{x[i], y[i]}});
+ lsq2.addMovement(time[i], idBits, {{AMOTION_EVENT_AXIS_X, {positions[i]}}});
}
- float outVx = 0, outVy = 0;
- const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy);
- if (computed) {
- ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy);
+ std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
+ if (v) {
+ ALOGD("lsq2 velocity: %.1f", *v);
} else {
ALOGD("lsq2 velocity: could not compute velocity");
}
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 4a445de..0a37318 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -16,9 +16,10 @@
#define LOG_TAG "VelocityTracker_test"
+#include <math.h>
#include <array>
#include <chrono>
-#include <math.h>
+#include <limits>
#include <android-base/stringprintf.h>
#include <attestation/HmacKeyManager.h>
@@ -198,25 +199,13 @@
const std::vector<MotionEventEntry>& motions, int32_t axis,
float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) {
VelocityTracker vt(strategy);
- float Vx, Vy;
std::vector<MotionEvent> events = createMotionEventStream(motions);
for (MotionEvent event : events) {
vt.addMovement(&event);
}
- vt.getVelocity(pointerId, &Vx, &Vy);
-
- switch (axis) {
- case AMOTION_EVENT_AXIS_X:
- checkVelocity(Vx, targetVelocity);
- break;
- case AMOTION_EVENT_AXIS_Y:
- checkVelocity(Vy, targetVelocity);
- break;
- default:
- FAIL() << "Axis must be either AMOTION_EVENT_AXIS_X or AMOTION_EVENT_AXIS_Y";
- }
+ checkVelocity(vt.getVelocity(axis, pointerId).value_or(0), targetVelocity);
}
static void computeAndCheckQuadraticEstimate(const std::vector<MotionEventEntry>& motions,
@@ -226,17 +215,99 @@
for (MotionEvent event : events) {
vt.addMovement(&event);
}
- VelocityTracker::Estimator estimator;
- EXPECT_TRUE(vt.getEstimator(0, &estimator));
+ VelocityTracker::Estimator estimatorX;
+ VelocityTracker::Estimator estimatorY;
+ EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_X, 0, &estimatorX));
+ EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0, &estimatorY));
for (size_t i = 0; i< coefficients.size(); i++) {
- checkCoefficient(estimator.xCoeff[i], coefficients[i]);
- checkCoefficient(estimator.yCoeff[i], coefficients[i]);
+ checkCoefficient(estimatorX.coeff[i], coefficients[i]);
+ checkCoefficient(estimatorY.coeff[i], coefficients[i]);
}
}
/*
* ================== VelocityTracker tests generated manually =====================================
*/
+TEST_F(VelocityTrackerTest, TestComputedVelocity) {
+ VelocityTracker::ComputedVelocity computedVelocity;
+
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 0 /*id*/, 200 /*velocity*/);
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/, 400 /*velocity*/);
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/, 650 /*velocity*/);
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, 750 /*velocity*/);
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 0 /*id*/, 1000 /*velocity*/);
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/, 2000 /*velocity*/);
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/, 3000 /*velocity*/);
+ computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, 4000 /*velocity*/);
+
+ // Check the axes/indices with velocity.
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 0U /*id*/)), 200);
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/)), 400);
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/)), 650);
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID)), 750);
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 0U /*id*/)), 1000);
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/)), 2000);
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/)), 3000);
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID)), 4000);
+ for (uint32_t id = 0; id < 32; id++) {
+ // Since no data was added for AXIS_SCROLL, expect empty value for the axis for any id.
+ EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, id))
+ << "Empty scroll data expected at id=" << id;
+ if (id == 0 || id == 26U || id == 27U || id == MAX_POINTER_ID) {
+ // Already checked above; continue.
+ continue;
+ }
+ // No data was added to X/Y for this id, expect empty value.
+ EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id))
+ << "Empty X data expected at id=" << id;
+ EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, id))
+ << "Empty Y data expected at id=" << id;
+ }
+ // Out-of-bounds ids should given empty values.
+ EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, -1));
+ EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID + 1));
+}
+
+TEST_F(VelocityTrackerTest, TestPopulateComputedVelocity) {
+ std::vector<MotionEventEntry> motions = {
+ {235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}},
+ {235089093349000ns, {{527.00, 0}}}, {235089095677625ns, {{527.00, 0}}},
+ {235089101859000ns, {{527.00, 0}}}, {235089110378000ns, {{528.00, 0}}},
+ {235089112497111ns, {{528.25, 0}}}, {235089118760000ns, {{531.00, 0}}},
+ {235089126686000ns, {{535.00, 0}}}, {235089129316820ns, {{536.33, 0}}},
+ {235089135199000ns, {{540.00, 0}}}, {235089144297000ns, {{546.00, 0}}},
+ {235089146136443ns, {{547.21, 0}}}, {235089152923000ns, {{553.00, 0}}},
+ {235089160784000ns, {{559.00, 0}}}, {235089162955851ns, {{560.66, 0}}},
+ {235089162955851ns, {{560.66, 0}}}, // ACTION_UP
+ };
+ VelocityTracker vt(VelocityTracker::Strategy::IMPULSE);
+ std::vector<MotionEvent> events = createMotionEventStream(motions);
+ for (const MotionEvent& event : events) {
+ vt.addMovement(&event);
+ }
+
+ float maxFloat = std::numeric_limits<float>::max();
+ VelocityTracker::ComputedVelocity computedVelocity;
+ vt.populateComputedVelocity(computedVelocity, 1000 /* units */, maxFloat);
+ checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)),
+ 764.345703);
+
+ // Expect X velocity to be scaled with respective to provided units.
+ vt.populateComputedVelocity(computedVelocity, 1000000 /* units */, maxFloat);
+ checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)),
+ 764345.703);
+
+ // Expect X velocity to be clamped by provided max velocity.
+ vt.populateComputedVelocity(computedVelocity, 1000000 /* units */, 1000);
+ checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 1000);
+
+ // All 0 data for Y; expect 0 velocity.
+ EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID)), 0);
+
+ // No data for scroll-axis; expect empty velocity.
+ EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID));
+}
+
TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) {
// Same coordinate is reported 2 times in a row
// It is difficult to determine the correct answer here, but at least the direction
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 539e24a..8c241f2 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -2712,17 +2712,18 @@
// Update the velocity tracker.
{
- std::vector<VelocityTracker::Position> positions;
+ std::vector<float> positionsX;
+ std::vector<float> positionsY;
for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
const RawPointerData::Pointer& pointer =
mCurrentRawState.rawPointerData.pointerForId(id);
- float x = pointer.x * mPointerXMovementScale;
- float y = pointer.y * mPointerYMovementScale;
- positions.push_back({x, y});
+ positionsX.push_back(pointer.x * mPointerXMovementScale);
+ positionsY.push_back(pointer.y * mPointerYMovementScale);
}
mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits,
- positions);
+ {{AMOTION_EVENT_AXIS_X, positionsX},
+ {AMOTION_EVENT_AXIS_Y, positionsY}});
}
// If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning
@@ -2829,9 +2830,12 @@
float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
- float vx, vy;
- if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
- float speed = hypotf(vx, vy);
+ std::optional<float> vx =
+ mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
+ std::optional<float> vy =
+ mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id);
+ if (vx && vy) {
+ float speed = hypotf(*vx, *vy);
if (speed > bestSpeed) {
bestId = id;
bestSpeed = speed;