Skip to content

Commit b59ab9f

Browse files
author
Jeff Brown
committed
Velocity Tracker II: The Revenge of Velocity Tracker
Bug: 5265529 Rewrote the velocity tracker to fit a polynomial curve to pointer movements using least squares linear regression. The velocity is simply the first derivative of this polynomial. Clients can also obtain an Estimator that describes the complete terms of the estimating polynomial including the coefficient of determination which provides a measure of the quality of the fit (confidence). Enhanced PointerLocation to display the movement curve predicted by the estimator in addition to the velocity vector. By default, the algorithm computes a 2nd degree (quadratic) polynomial based on a 100ms recent history horizon. Change-Id: Id377bef44117fce68fee2c41f90134ce3224d3a1
1 parent aab55bf commit b59ab9f

File tree

5 files changed

+496
-56
lines changed

5 files changed

+496
-56
lines changed

core/java/android/view/VelocityTracker.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public void onReleased(VelocityTracker element) {
5959
private static native void nativeComputeCurrentVelocity(int ptr, int units, float maxVelocity);
6060
private static native float nativeGetXVelocity(int ptr, int id);
6161
private static native float nativeGetYVelocity(int ptr, int id);
62+
private static native boolean nativeGetEstimator(int ptr, int id,
63+
int degree, int horizonMillis, Estimator outEstimator);
6264

6365
/**
6466
* Retrieve a new VelocityTracker object to watch the velocity of a
@@ -215,4 +217,94 @@ public float getXVelocity(int id) {
215217
public float getYVelocity(int id) {
216218
return nativeGetYVelocity(mPtr, id);
217219
}
220+
221+
/**
222+
* Get an estimator for the movements of a pointer using past movements of the
223+
* pointer to predict future movements.
224+
*
225+
* It is not necessary to call {@link #computeCurrentVelocity(int)} before calling
226+
* this method.
227+
*
228+
* @param id Which pointer's velocity to return.
229+
* @param degree The desired polynomial degree. The actual estimator may have
230+
* a lower degree than what is requested here. If -1, uses the default degree.
231+
* @param horizonMillis The maximum age of the oldest sample to consider, in milliseconds.
232+
* If -1, uses the default horizon.
233+
* @param outEstimator The estimator to populate.
234+
* @return True if an estimator was obtained, false if there is no information
235+
* available about the pointer.
236+
*
237+
* @hide For internal use only. Not a final API.
238+
*/
239+
public boolean getEstimator(int id, int degree, int horizonMillis, Estimator outEstimator) {
240+
if (outEstimator == null) {
241+
throw new IllegalArgumentException("outEstimator must not be null");
242+
}
243+
return nativeGetEstimator(mPtr, id, degree, horizonMillis, outEstimator);
244+
}
245+
246+
/**
247+
* An estimator for the movements of a pointer based on a polynomial model.
248+
*
249+
* The last recorded position of the pointer is at time zero seconds.
250+
* Past estimated positions are at negative times and future estimated positions
251+
* are at positive times.
252+
*
253+
* First coefficient is position (in pixels), second is velocity (in pixels per second),
254+
* third is acceleration (in pixels per second squared).
255+
*
256+
* @hide For internal use only. Not a final API.
257+
*/
258+
public static final class Estimator {
259+
// Must match VelocityTracker::Estimator::MAX_DEGREE
260+
private static final int MAX_DEGREE = 2;
261+
262+
/**
263+
* Polynomial coefficients describing motion in X.
264+
*/
265+
public final float[] xCoeff = new float[MAX_DEGREE + 1];
266+
267+
/**
268+
* Polynomial coefficients describing motion in Y.
269+
*/
270+
public final float[] yCoeff = new float[MAX_DEGREE + 1];
271+
272+
/**
273+
* Polynomial degree, or zero if only position information is available.
274+
*/
275+
public int degree;
276+
277+
/**
278+
* Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
279+
*/
280+
public float confidence;
281+
282+
/**
283+
* Gets an estimate of the X position of the pointer at the specified time point.
284+
* @param time The time point in seconds, 0 is the last recorded time.
285+
* @return The estimated X coordinate.
286+
*/
287+
public float estimateX(float time) {
288+
return estimate(time, xCoeff);
289+
}
290+
291+
/**
292+
* Gets an estimate of the Y position of the pointer at the specified time point.
293+
* @param time The time point in seconds, 0 is the last recorded time.
294+
* @return The estimated Y coordinate.
295+
*/
296+
public float estimateY(float time) {
297+
return estimate(time, yCoeff);
298+
}
299+
300+
private float estimate(float time, float[] c) {
301+
float a = 0;
302+
float scale = 1;
303+
for (int i = 0; i <= degree; i++) {
304+
a += c[i] * scale;
305+
scale *= time;
306+
}
307+
return a;
308+
}
309+
}
218310
}

core/java/com/android/internal/widget/PointerLocationView.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ public static class PointerState {
5151
// Most recent velocity.
5252
private float mXVelocity;
5353
private float mYVelocity;
54-
54+
55+
// Position estimator.
56+
private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator();
57+
5558
public void clearTrace() {
5659
mTraceCount = 0;
5760
}
@@ -75,6 +78,10 @@ public void addTrace(float x, float y) {
7578
}
7679
}
7780

81+
private final int ESTIMATE_PAST_POINTS = 4;
82+
private final int ESTIMATE_FUTURE_POINTS = 2;
83+
private final float ESTIMATE_INTERVAL = 0.02f;
84+
7885
private final ViewConfiguration mVC;
7986
private final Paint mTextPaint;
8087
private final Paint mTextBackgroundPaint;
@@ -278,8 +285,20 @@ protected void onDraw(Canvas canvas) {
278285
haveLast = true;
279286
}
280287

281-
// Draw velocity vector.
282288
if (drawn) {
289+
// Draw movement estimate curve.
290+
mPaint.setARGB(128, 128, 0, 128);
291+
float lx = ps.mEstimator.estimateX(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
292+
float ly = ps.mEstimator.estimateY(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
293+
for (int i = -ESTIMATE_PAST_POINTS + 1; i <= ESTIMATE_FUTURE_POINTS; i++) {
294+
float x = ps.mEstimator.estimateX(i * ESTIMATE_INTERVAL);
295+
float y = ps.mEstimator.estimateY(i * ESTIMATE_INTERVAL);
296+
canvas.drawLine(lx, ly, x, y, mPaint);
297+
lx = x;
298+
ly = y;
299+
}
300+
301+
// Draw velocity vector.
283302
mPaint.setARGB(255, 255, 64, 128);
284303
float xVel = ps.mXVelocity * (1000 / 60);
285304
float yVel = ps.mYVelocity * (1000 / 60);
@@ -517,6 +536,7 @@ public void addPointerEvent(MotionEvent event) {
517536
ps.addTrace(coords.x, coords.y);
518537
ps.mXVelocity = mVelocity.getXVelocity(id);
519538
ps.mYVelocity = mVelocity.getYVelocity(id);
539+
mVelocity.getEstimator(id, -1, -1, ps.mEstimator);
520540
ps.mToolType = event.getToolType(i);
521541
}
522542
}

core/jni/android_view_VelocityTracker.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ namespace android {
2929
// Special constant to request the velocity of the active pointer.
3030
static const int ACTIVE_POINTER_ID = -1;
3131

32+
static struct {
33+
jfieldID xCoeff;
34+
jfieldID yCoeff;
35+
jfieldID degree;
36+
jfieldID confidence;
37+
} gEstimatorClassInfo;
38+
39+
3240
// --- VelocityTrackerState ---
3341

3442
class VelocityTrackerState {
@@ -39,6 +47,8 @@ class VelocityTrackerState {
3947
void addMovement(const MotionEvent* event);
4048
void computeCurrentVelocity(int32_t units, float maxVelocity);
4149
void getVelocity(int32_t id, float* outVx, float* outVy);
50+
bool getEstimator(int32_t id, uint32_t degree, nsecs_t horizon,
51+
VelocityTracker::Estimator* outEstimator);
4252

4353
private:
4454
struct Velocity {
@@ -118,6 +128,11 @@ void VelocityTrackerState::getVelocity(int32_t id, float* outVx, float* outVy) {
118128
}
119129
}
120130

131+
bool VelocityTrackerState::getEstimator(int32_t id, uint32_t degree, nsecs_t horizon,
132+
VelocityTracker::Estimator* outEstimator) {
133+
return mVelocityTracker.getEstimator(id, degree, horizon, outEstimator);
134+
}
135+
121136

122137
// --- JNI Methods ---
123138

@@ -169,6 +184,30 @@ static jfloat android_view_VelocityTracker_nativeGetYVelocity(JNIEnv* env, jclas
169184
return vy;
170185
}
171186

187+
static jboolean android_view_VelocityTracker_nativeGetEstimator(JNIEnv* env, jclass clazz,
188+
jint ptr, jint id, jint degree, jint horizonMillis, jobject outEstimatorObj) {
189+
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
190+
VelocityTracker::Estimator estimator;
191+
bool result = state->getEstimator(id,
192+
degree < 0 ? VelocityTracker::DEFAULT_DEGREE : uint32_t(degree),
193+
horizonMillis < 0 ? VelocityTracker::DEFAULT_HORIZON :
194+
nsecs_t(horizonMillis) * 1000000L,
195+
&estimator);
196+
197+
jfloatArray xCoeffObj = jfloatArray(env->GetObjectField(outEstimatorObj,
198+
gEstimatorClassInfo.xCoeff));
199+
jfloatArray yCoeffObj = jfloatArray(env->GetObjectField(outEstimatorObj,
200+
gEstimatorClassInfo.yCoeff));
201+
202+
env->SetFloatArrayRegion(xCoeffObj, 0, VelocityTracker::Estimator::MAX_DEGREE + 1,
203+
estimator.xCoeff);
204+
env->SetFloatArrayRegion(yCoeffObj, 0, VelocityTracker::Estimator::MAX_DEGREE + 1,
205+
estimator.yCoeff);
206+
env->SetIntField(outEstimatorObj, gEstimatorClassInfo.degree, estimator.degree);
207+
env->SetFloatField(outEstimatorObj, gEstimatorClassInfo.confidence, estimator.confidence);
208+
return result;
209+
}
210+
172211

173212
// --- JNI Registration ---
174213

@@ -195,12 +234,35 @@ static JNINativeMethod gVelocityTrackerMethods[] = {
195234
{ "nativeGetYVelocity",
196235
"(II)F",
197236
(void*)android_view_VelocityTracker_nativeGetYVelocity },
237+
{ "nativeGetEstimator",
238+
"(IIIILandroid/view/VelocityTracker$Estimator;)Z",
239+
(void*)android_view_VelocityTracker_nativeGetEstimator },
198240
};
199241

242+
#define FIND_CLASS(var, className) \
243+
var = env->FindClass(className); \
244+
LOG_FATAL_IF(! var, "Unable to find class " className);
245+
246+
#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
247+
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
248+
LOG_FATAL_IF(! var, "Unable to find field " fieldName);
249+
200250
int register_android_view_VelocityTracker(JNIEnv* env) {
201251
int res = jniRegisterNativeMethods(env, "android/view/VelocityTracker",
202252
gVelocityTrackerMethods, NELEM(gVelocityTrackerMethods));
203253
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
254+
255+
jclass clazz;
256+
FIND_CLASS(clazz, "android/view/VelocityTracker$Estimator");
257+
258+
GET_FIELD_ID(gEstimatorClassInfo.xCoeff, clazz,
259+
"xCoeff", "[F");
260+
GET_FIELD_ID(gEstimatorClassInfo.yCoeff, clazz,
261+
"yCoeff", "[F");
262+
GET_FIELD_ID(gEstimatorClassInfo.degree, clazz,
263+
"degree", "I");
264+
GET_FIELD_ID(gEstimatorClassInfo.confidence, clazz,
265+
"confidence", "F");
204266
return 0;
205267
}
206268

include/ui/Input.h

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,41 @@ class PreallocatedInputEventFactory : public InputEventFactoryInterface {
620620
*/
621621
class VelocityTracker {
622622
public:
623+
// Default polynomial degree. (used by getVelocity)
624+
static const uint32_t DEFAULT_DEGREE = 2;
625+
626+
// Default sample horizon. (used by getVelocity)
627+
// We don't use too much history by default since we want to react to quick
628+
// changes in direction.
629+
static const nsecs_t DEFAULT_HORIZON = 100 * 1000000; // 100 ms
630+
623631
struct Position {
624632
float x, y;
625633
};
626634

635+
struct Estimator {
636+
static const size_t MAX_DEGREE = 2;
637+
638+
// Polynomial coefficients describing motion in X and Y.
639+
float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1];
640+
641+
// Polynomial degree (number of coefficients), or zero if no information is
642+
// available.
643+
uint32_t degree;
644+
645+
// Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
646+
float confidence;
647+
648+
inline void clear() {
649+
degree = 0;
650+
confidence = 0;
651+
for (size_t i = 0; i <= MAX_DEGREE; i++) {
652+
xCoeff[i] = 0;
653+
yCoeff[i] = 0;
654+
}
655+
}
656+
};
657+
627658
VelocityTracker();
628659

629660
// Resets the velocity tracker state.
@@ -645,10 +676,16 @@ class VelocityTracker {
645676
void addMovement(const MotionEvent* event);
646677

647678
// Gets the velocity of the specified pointer id in position units per second.
648-
// Returns false and sets the velocity components to zero if there is no movement
649-
// information for the pointer.
679+
// Returns false and sets the velocity components to zero if there is
680+
// insufficient movement information for the pointer.
650681
bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
651682

683+
// Gets a quadratic estimator for the movements of the specified pointer id.
684+
// Returns false and clears the estimator if there is no information available
685+
// about the pointer.
686+
bool getEstimator(uint32_t id, uint32_t degree, nsecs_t horizon,
687+
Estimator* outEstimator) const;
688+
652689
// Gets the active pointer id, or -1 if none.
653690
inline int32_t getActivePointerId() const { return mActivePointerId; }
654691

@@ -657,13 +694,7 @@ class VelocityTracker {
657694

658695
private:
659696
// Number of samples to keep.
660-
static const uint32_t HISTORY_SIZE = 10;
661-
662-
// Oldest sample to consider when calculating the velocity.
663-
static const nsecs_t MAX_AGE = 100 * 1000000; // 100 ms
664-
665-
// The minimum duration between samples when estimating velocity.
666-
static const nsecs_t MIN_DURATION = 5 * 1000000; // 5 ms
697+
static const uint32_t HISTORY_SIZE = 20;
667698

668699
struct Movement {
669700
nsecs_t eventTime;

0 commit comments

Comments
 (0)