• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 package android.view;
18 
19 import android.annotation.IntDef;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.os.Build;
22 import android.os.SystemProperties;
23 import android.util.ArrayMap;
24 import android.util.Pools.SynchronizedPool;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.Map;
29 
30 /**
31  * Helper for tracking the velocity of touch events, for implementing
32  * flinging and other such gestures.
33  *
34  * Use {@link #obtain} to retrieve a new instance of the class when you are going
35  * to begin tracking.  Put the motion events you receive into it with
36  * {@link #addMovement(MotionEvent)}.  When you want to determine the velocity call
37  * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
38  * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
39  */
40 public final class VelocityTracker {
41     private static final SynchronizedPool<VelocityTracker> sPool =
42             new SynchronizedPool<VelocityTracker>(2);
43 
44     private static final int ACTIVE_POINTER_ID = -1;
45 
46     /**
47      * Velocity Tracker Strategy: Invalid.
48      *
49      * @hide
50      */
51     public static final int VELOCITY_TRACKER_STRATEGY_DEFAULT = -1;
52 
53     /**
54      * Velocity Tracker Strategy: Impulse.
55      * Physical model of pushing an object.  Quality: VERY GOOD.
56      * Works with duplicate coordinates, unclean finger liftoff.
57      *
58      * @hide
59      */
60     public static final int VELOCITY_TRACKER_STRATEGY_IMPULSE = 0;
61 
62     /**
63      * Velocity Tracker Strategy: LSQ1.
64      * 1st order least squares.  Quality: POOR.
65      * Frequently underfits the touch data especially when the finger accelerates
66      * or changes direction.  Often underestimates velocity.  The direction
67      * is overly influenced by historical touch points.
68      *
69      * @hide
70      */
71     public static final int VELOCITY_TRACKER_STRATEGY_LSQ1 = 1;
72 
73     /**
74      * Velocity Tracker Strategy: LSQ2.
75      * 2nd order least squares.  Quality: VERY GOOD.
76      * Pretty much ideal, but can be confused by certain kinds of touch data,
77      * particularly if the panel has a tendency to generate delayed,
78      * duplicate or jittery touch coordinates when the finger is released.
79      *
80      * @hide
81      */
82     public static final int VELOCITY_TRACKER_STRATEGY_LSQ2 = 2;
83 
84     /**
85      * Velocity Tracker Strategy: LSQ3.
86      * 3rd order least squares.  Quality: UNUSABLE.
87      * Frequently overfits the touch data yielding wildly divergent estimates
88      * of the velocity when the finger is released.
89      *
90      * @hide
91      */
92     public static final int VELOCITY_TRACKER_STRATEGY_LSQ3 = 3;
93 
94     /**
95      * Velocity Tracker Strategy: WLSQ2_DELTA.
96      * 2nd order weighted least squares, delta weighting.  Quality: EXPERIMENTAL
97      *
98      * @hide
99      */
100     public static final int VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA = 4;
101 
102     /**
103      * Velocity Tracker Strategy: WLSQ2_CENTRAL.
104      * 2nd order weighted least squares, central weighting.  Quality: EXPERIMENTAL
105      *
106      * @hide
107      */
108     public static final int VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL = 5;
109 
110     /**
111      * Velocity Tracker Strategy: WLSQ2_RECENT.
112      * 2nd order weighted least squares, recent weighting.  Quality: EXPERIMENTAL
113      *
114      * @hide
115      */
116     public static final int VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT = 6;
117 
118     /**
119      * Velocity Tracker Strategy: INT1.
120      * 1st order integrating filter.  Quality: GOOD.
121      * Not as good as 'lsq2' because it cannot estimate acceleration but it is
122      * more tolerant of errors.  Like 'lsq1', this strategy tends to underestimate
123      * the velocity of a fling but this strategy tends to respond to changes in
124      * direction more quickly and accurately.
125      *
126      * @hide
127      */
128     public static final int VELOCITY_TRACKER_STRATEGY_INT1 = 7;
129 
130     /**
131      * Velocity Tracker Strategy: INT2.
132      * 2nd order integrating filter.  Quality: EXPERIMENTAL.
133      * For comparison purposes only.  Unlike 'int1' this strategy can compensate
134      * for acceleration but it typically overestimates the effect.
135      *
136      * @hide
137      */
138     public static final int VELOCITY_TRACKER_STRATEGY_INT2 = 8;
139 
140     /**
141      * Velocity Tracker Strategy: Legacy.
142      * Legacy velocity tracker algorithm.  Quality: POOR.
143      * For comparison purposes only.  This algorithm is strongly influenced by
144      * old data points, consistently underestimates velocity and takes a very long
145      * time to adjust to changes in direction.
146      *
147      * @hide
148      */
149     public static final int VELOCITY_TRACKER_STRATEGY_LEGACY = 9;
150 
151 
152     /**
153      * Velocity Tracker Strategy look up table.
154      */
155     private static final Map<String, Integer> STRATEGIES = new ArrayMap<>();
156 
157     /** @hide */
158     @Retention(RetentionPolicy.SOURCE)
159     @IntDef(prefix = {"VELOCITY_TRACKER_STRATEGY_"}, value = {
160             VELOCITY_TRACKER_STRATEGY_DEFAULT,
161             VELOCITY_TRACKER_STRATEGY_IMPULSE,
162             VELOCITY_TRACKER_STRATEGY_LSQ1,
163             VELOCITY_TRACKER_STRATEGY_LSQ2,
164             VELOCITY_TRACKER_STRATEGY_LSQ3,
165             VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA,
166             VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL,
167             VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT,
168             VELOCITY_TRACKER_STRATEGY_INT1,
169             VELOCITY_TRACKER_STRATEGY_INT2,
170             VELOCITY_TRACKER_STRATEGY_LEGACY
171     })
172     public @interface VelocityTrackerStrategy {}
173 
174     private long mPtr;
175     @VelocityTrackerStrategy
176     private final int mStrategy;
177 
nativeInitialize(int strategy)178     private static native long nativeInitialize(int strategy);
nativeDispose(long ptr)179     private static native void nativeDispose(long ptr);
nativeClear(long ptr)180     private static native void nativeClear(long ptr);
nativeAddMovement(long ptr, MotionEvent event)181     private static native void nativeAddMovement(long ptr, MotionEvent event);
nativeComputeCurrentVelocity(long ptr, int units, float maxVelocity)182     private static native void nativeComputeCurrentVelocity(long ptr, int units, float maxVelocity);
nativeGetXVelocity(long ptr, int id)183     private static native float nativeGetXVelocity(long ptr, int id);
nativeGetYVelocity(long ptr, int id)184     private static native float nativeGetYVelocity(long ptr, int id);
nativeGetEstimator(long ptr, int id, Estimator outEstimator)185     private static native boolean nativeGetEstimator(long ptr, int id, Estimator outEstimator);
186 
187     static {
188         // Strategy string and IDs mapping lookup.
189         STRATEGIES.put("impulse", VELOCITY_TRACKER_STRATEGY_IMPULSE);
190         STRATEGIES.put("lsq1", VELOCITY_TRACKER_STRATEGY_LSQ1);
191         STRATEGIES.put("lsq2", VELOCITY_TRACKER_STRATEGY_LSQ2);
192         STRATEGIES.put("lsq3", VELOCITY_TRACKER_STRATEGY_LSQ3);
193         STRATEGIES.put("wlsq2-delta", VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA);
194         STRATEGIES.put("wlsq2-central", VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL);
195         STRATEGIES.put("wlsq2-recent", VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT);
196         STRATEGIES.put("int1", VELOCITY_TRACKER_STRATEGY_INT1);
197         STRATEGIES.put("int2", VELOCITY_TRACKER_STRATEGY_INT2);
198         STRATEGIES.put("legacy", VELOCITY_TRACKER_STRATEGY_LEGACY);
199     }
200 
201     /**
202      * Return a strategy ID from string.
203      */
toStrategyId(String strStrategy)204     private static int toStrategyId(String strStrategy) {
205         if (STRATEGIES.containsKey(strStrategy)) {
206             return STRATEGIES.get(strStrategy);
207         }
208         return VELOCITY_TRACKER_STRATEGY_DEFAULT;
209     }
210 
211     /**
212      * Retrieve a new VelocityTracker object to watch the velocity of a
213      * motion.  Be sure to call {@link #recycle} when done.  You should
214      * generally only maintain an active object while tracking a movement,
215      * so that the VelocityTracker can be re-used elsewhere.
216      *
217      * @return Returns a new VelocityTracker.
218      */
obtain()219     static public VelocityTracker obtain() {
220         VelocityTracker instance = sPool.acquire();
221         return (instance != null) ? instance
222                 : new VelocityTracker(VELOCITY_TRACKER_STRATEGY_DEFAULT);
223     }
224 
225     /**
226      * Obtains a velocity tracker with the specified strategy as string.
227      * For testing and comparison purposes only.
228      * @deprecated Use {@link obtain(int strategy)} instead.
229      *
230      * @param strategy The strategy, or null to use the default.
231      * @return The velocity tracker.
232      *
233      * @hide
234      */
235     @UnsupportedAppUsage
236     @Deprecated
obtain(String strategy)237     public static VelocityTracker obtain(String strategy) {
238         if (strategy == null) {
239             return obtain();
240         }
241         return new VelocityTracker(toStrategyId(strategy));
242     }
243 
244     /**
245      * Obtains a velocity tracker with the specified strategy.
246      * For testing and comparison purposes only.
247      *
248      * @param strategy The strategy Id, VELOCITY_TRACKER_STRATEGY_DEFAULT to use the default.
249      * @return The velocity tracker.
250      *
251      * @hide
252      */
obtain(int strategy)253     public static VelocityTracker obtain(int strategy) {
254         return new VelocityTracker(strategy);
255     }
256 
257     /**
258      * Return a VelocityTracker object back to be re-used by others.  You must
259      * not touch the object after calling this function.
260      */
recycle()261     public void recycle() {
262         if (mStrategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) {
263             clear();
264             sPool.release(this);
265         }
266     }
267 
268     /**
269      * Return strategy Id of VelocityTracker object.
270      * @return The velocity tracker strategy Id.
271      *
272      * @hide
273      */
getStrategyId()274     public int getStrategyId() {
275         return mStrategy;
276     }
277 
VelocityTracker(@elocityTrackerStrategy int strategy)278     private VelocityTracker(@VelocityTrackerStrategy int strategy) {
279         // If user has not selected a specific strategy
280         if (strategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) {
281             // Check if user specified strategy by overriding system property.
282             String strategyProperty =
283                                     SystemProperties.get("persist.input.velocitytracker.strategy");
284             if (strategyProperty == null || strategyProperty.isEmpty()) {
285                 mStrategy = strategy;
286             } else {
287                 mStrategy = toStrategyId(strategyProperty);
288             }
289         } else {
290             // User specified strategy
291             mStrategy = strategy;
292         }
293         mPtr = nativeInitialize(mStrategy);
294     }
295 
296     @Override
finalize()297     protected void finalize() throws Throwable {
298         try {
299             if (mPtr != 0) {
300                 nativeDispose(mPtr);
301                 mPtr = 0;
302             }
303         } finally {
304             super.finalize();
305         }
306     }
307 
308     /**
309      * Reset the velocity tracker back to its initial state.
310      */
clear()311     public void clear() {
312         nativeClear(mPtr);
313     }
314 
315     /**
316      * Add a user's movement to the tracker.  You should call this for the
317      * initial {@link MotionEvent#ACTION_DOWN}, the following
318      * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
319      * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
320      * for whichever events you desire.
321      *
322      * @param event The MotionEvent you received and would like to track.
323      */
addMovement(MotionEvent event)324     public void addMovement(MotionEvent event) {
325         if (event == null) {
326             throw new IllegalArgumentException("event must not be null");
327         }
328         nativeAddMovement(mPtr, event);
329     }
330 
331     /**
332      * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
333      * velocity of Float.MAX_VALUE.
334      *
335      * @see #computeCurrentVelocity(int, float)
336      */
computeCurrentVelocity(int units)337     public void computeCurrentVelocity(int units) {
338         nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
339     }
340 
341     /**
342      * Compute the current velocity based on the points that have been
343      * collected.  Only call this when you actually want to retrieve velocity
344      * information, as it is relatively expensive.  You can then retrieve
345      * the velocity with {@link #getXVelocity()} and
346      * {@link #getYVelocity()}.
347      *
348      * @param units The units you would like the velocity in.  A value of 1
349      * provides pixels per millisecond, 1000 provides pixels per second, etc.
350      * @param maxVelocity The maximum velocity that can be computed by this method.
351      * This value must be declared in the same unit as the units parameter. This value
352      * must be positive.
353      */
computeCurrentVelocity(int units, float maxVelocity)354     public void computeCurrentVelocity(int units, float maxVelocity) {
355         nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
356     }
357 
358     /**
359      * Retrieve the last computed X velocity.  You must first call
360      * {@link #computeCurrentVelocity(int)} before calling this function.
361      *
362      * @return The previously computed X velocity.
363      */
getXVelocity()364     public float getXVelocity() {
365         return nativeGetXVelocity(mPtr, ACTIVE_POINTER_ID);
366     }
367 
368     /**
369      * Retrieve the last computed Y velocity.  You must first call
370      * {@link #computeCurrentVelocity(int)} before calling this function.
371      *
372      * @return The previously computed Y velocity.
373      */
getYVelocity()374     public float getYVelocity() {
375         return nativeGetYVelocity(mPtr, ACTIVE_POINTER_ID);
376     }
377 
378     /**
379      * Retrieve the last computed X velocity.  You must first call
380      * {@link #computeCurrentVelocity(int)} before calling this function.
381      *
382      * @param id Which pointer's velocity to return.
383      * @return The previously computed X velocity.
384      */
getXVelocity(int id)385     public float getXVelocity(int id) {
386         return nativeGetXVelocity(mPtr, id);
387     }
388 
389     /**
390      * Retrieve the last computed Y velocity.  You must first call
391      * {@link #computeCurrentVelocity(int)} before calling this function.
392      *
393      * @param id Which pointer's velocity to return.
394      * @return The previously computed Y velocity.
395      */
getYVelocity(int id)396     public float getYVelocity(int id) {
397         return nativeGetYVelocity(mPtr, id);
398     }
399 
400     /**
401      * Get an estimator for the movements of a pointer using past movements of the
402      * pointer to predict future movements.
403      *
404      * It is not necessary to call {@link #computeCurrentVelocity(int)} before calling
405      * this method.
406      *
407      * @param id Which pointer's velocity to return.
408      * @param outEstimator The estimator to populate.
409      * @return True if an estimator was obtained, false if there is no information
410      * available about the pointer.
411      *
412      * @hide For internal use only.  Not a final API.
413      */
getEstimator(int id, Estimator outEstimator)414     public boolean getEstimator(int id, Estimator outEstimator) {
415         if (outEstimator == null) {
416             throw new IllegalArgumentException("outEstimator must not be null");
417         }
418         return nativeGetEstimator(mPtr, id, outEstimator);
419     }
420 
421     /**
422      * An estimator for the movements of a pointer based on a polynomial model.
423      *
424      * The last recorded position of the pointer is at time zero seconds.
425      * Past estimated positions are at negative times and future estimated positions
426      * are at positive times.
427      *
428      * First coefficient is position (in pixels), second is velocity (in pixels per second),
429      * third is acceleration (in pixels per second squared).
430      *
431      * @hide For internal use only.  Not a final API.
432      */
433     public static final class Estimator {
434         // Must match VelocityTracker::Estimator::MAX_DEGREE
435         private static final int MAX_DEGREE = 4;
436 
437         /**
438          * Polynomial coefficients describing motion in X.
439          */
440         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
441         public final float[] xCoeff = new float[MAX_DEGREE + 1];
442 
443         /**
444          * Polynomial coefficients describing motion in Y.
445          */
446         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
447         public final float[] yCoeff = new float[MAX_DEGREE + 1];
448 
449         /**
450          * Polynomial degree, or zero if only position information is available.
451          */
452         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
453         public int degree;
454 
455         /**
456          * Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
457          */
458         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
459         public float confidence;
460 
461         /**
462          * Gets an estimate of the X position of the pointer at the specified time point.
463          * @param time The time point in seconds, 0 is the last recorded time.
464          * @return The estimated X coordinate.
465          */
estimateX(float time)466         public float estimateX(float time) {
467             return estimate(time, xCoeff);
468         }
469 
470         /**
471          * Gets an estimate of the Y position of the pointer at the specified time point.
472          * @param time The time point in seconds, 0 is the last recorded time.
473          * @return The estimated Y coordinate.
474          */
estimateY(float time)475         public float estimateY(float time) {
476             return estimate(time, yCoeff);
477         }
478 
479         /**
480          * Gets the X coefficient with the specified index.
481          * @param index The index of the coefficient to return.
482          * @return The X coefficient, or 0 if the index is greater than the degree.
483          */
getXCoeff(int index)484         public float getXCoeff(int index) {
485             return index <= degree ? xCoeff[index] : 0;
486         }
487 
488         /**
489          * Gets the Y coefficient with the specified index.
490          * @param index The index of the coefficient to return.
491          * @return The Y coefficient, or 0 if the index is greater than the degree.
492          */
getYCoeff(int index)493         public float getYCoeff(int index) {
494             return index <= degree ? yCoeff[index] : 0;
495         }
496 
estimate(float time, float[] c)497         private float estimate(float time, float[] c) {
498             float a = 0;
499             float scale = 1;
500             for (int i = 0; i <= degree; i++) {
501                 a += c[i] * scale;
502                 scale *= time;
503             }
504             return a;
505         }
506     }
507 }
508