• 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.sysprop.InputProperties;
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 = InputProperties.velocitytracker_strategy().orElse(null);
283             if (strategyProperty == null || strategyProperty.isEmpty()) {
284                 mStrategy = strategy;
285             } else {
286                 mStrategy = toStrategyId(strategyProperty);
287             }
288         } else {
289             // User specified strategy
290             mStrategy = strategy;
291         }
292         mPtr = nativeInitialize(mStrategy);
293     }
294 
295     @Override
finalize()296     protected void finalize() throws Throwable {
297         try {
298             if (mPtr != 0) {
299                 nativeDispose(mPtr);
300                 mPtr = 0;
301             }
302         } finally {
303             super.finalize();
304         }
305     }
306 
307     /**
308      * Reset the velocity tracker back to its initial state.
309      */
clear()310     public void clear() {
311         nativeClear(mPtr);
312     }
313 
314     /**
315      * Add a user's movement to the tracker.  You should call this for the
316      * initial {@link MotionEvent#ACTION_DOWN}, the following
317      * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
318      * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
319      * for whichever events you desire.
320      *
321      * @param event The MotionEvent you received and would like to track.
322      */
addMovement(MotionEvent event)323     public void addMovement(MotionEvent event) {
324         if (event == null) {
325             throw new IllegalArgumentException("event must not be null");
326         }
327         nativeAddMovement(mPtr, event);
328     }
329 
330     /**
331      * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
332      * velocity of Float.MAX_VALUE.
333      *
334      * @see #computeCurrentVelocity(int, float)
335      */
computeCurrentVelocity(int units)336     public void computeCurrentVelocity(int units) {
337         nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
338     }
339 
340     /**
341      * Compute the current velocity based on the points that have been
342      * collected.  Only call this when you actually want to retrieve velocity
343      * information, as it is relatively expensive.  You can then retrieve
344      * the velocity with {@link #getXVelocity()} and
345      * {@link #getYVelocity()}.
346      *
347      * @param units The units you would like the velocity in.  A value of 1
348      * provides pixels per millisecond, 1000 provides pixels per second, etc.
349      * @param maxVelocity The maximum velocity that can be computed by this method.
350      * This value must be declared in the same unit as the units parameter. This value
351      * must be positive.
352      */
computeCurrentVelocity(int units, float maxVelocity)353     public void computeCurrentVelocity(int units, float maxVelocity) {
354         nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
355     }
356 
357     /**
358      * Retrieve the last computed X velocity.  You must first call
359      * {@link #computeCurrentVelocity(int)} before calling this function.
360      *
361      * @return The previously computed X velocity.
362      */
getXVelocity()363     public float getXVelocity() {
364         return nativeGetXVelocity(mPtr, ACTIVE_POINTER_ID);
365     }
366 
367     /**
368      * Retrieve the last computed Y velocity.  You must first call
369      * {@link #computeCurrentVelocity(int)} before calling this function.
370      *
371      * @return The previously computed Y velocity.
372      */
getYVelocity()373     public float getYVelocity() {
374         return nativeGetYVelocity(mPtr, ACTIVE_POINTER_ID);
375     }
376 
377     /**
378      * Retrieve the last computed X velocity.  You must first call
379      * {@link #computeCurrentVelocity(int)} before calling this function.
380      *
381      * @param id Which pointer's velocity to return.
382      * @return The previously computed X velocity.
383      */
getXVelocity(int id)384     public float getXVelocity(int id) {
385         return nativeGetXVelocity(mPtr, id);
386     }
387 
388     /**
389      * Retrieve the last computed Y velocity.  You must first call
390      * {@link #computeCurrentVelocity(int)} before calling this function.
391      *
392      * @param id Which pointer's velocity to return.
393      * @return The previously computed Y velocity.
394      */
getYVelocity(int id)395     public float getYVelocity(int id) {
396         return nativeGetYVelocity(mPtr, id);
397     }
398 
399     /**
400      * Get an estimator for the movements of a pointer using past movements of the
401      * pointer to predict future movements.
402      *
403      * It is not necessary to call {@link #computeCurrentVelocity(int)} before calling
404      * this method.
405      *
406      * @param id Which pointer's velocity to return.
407      * @param outEstimator The estimator to populate.
408      * @return True if an estimator was obtained, false if there is no information
409      * available about the pointer.
410      *
411      * @hide For internal use only.  Not a final API.
412      */
getEstimator(int id, Estimator outEstimator)413     public boolean getEstimator(int id, Estimator outEstimator) {
414         if (outEstimator == null) {
415             throw new IllegalArgumentException("outEstimator must not be null");
416         }
417         return nativeGetEstimator(mPtr, id, outEstimator);
418     }
419 
420     /**
421      * An estimator for the movements of a pointer based on a polynomial model.
422      *
423      * The last recorded position of the pointer is at time zero seconds.
424      * Past estimated positions are at negative times and future estimated positions
425      * are at positive times.
426      *
427      * First coefficient is position (in pixels), second is velocity (in pixels per second),
428      * third is acceleration (in pixels per second squared).
429      *
430      * @hide For internal use only.  Not a final API.
431      */
432     public static final class Estimator {
433         // Must match VelocityTracker::Estimator::MAX_DEGREE
434         private static final int MAX_DEGREE = 4;
435 
436         /**
437          * Polynomial coefficients describing motion in X.
438          */
439         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
440         public final float[] xCoeff = new float[MAX_DEGREE + 1];
441 
442         /**
443          * Polynomial coefficients describing motion in Y.
444          */
445         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
446         public final float[] yCoeff = new float[MAX_DEGREE + 1];
447 
448         /**
449          * Polynomial degree, or zero if only position information is available.
450          */
451         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
452         public int degree;
453 
454         /**
455          * Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
456          */
457         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
458         public float confidence;
459 
460         /**
461          * Gets an estimate of the X position of the pointer at the specified time point.
462          * @param time The time point in seconds, 0 is the last recorded time.
463          * @return The estimated X coordinate.
464          */
estimateX(float time)465         public float estimateX(float time) {
466             return estimate(time, xCoeff);
467         }
468 
469         /**
470          * Gets an estimate of the Y position of the pointer at the specified time point.
471          * @param time The time point in seconds, 0 is the last recorded time.
472          * @return The estimated Y coordinate.
473          */
estimateY(float time)474         public float estimateY(float time) {
475             return estimate(time, yCoeff);
476         }
477 
478         /**
479          * Gets the X coefficient with the specified index.
480          * @param index The index of the coefficient to return.
481          * @return The X coefficient, or 0 if the index is greater than the degree.
482          */
getXCoeff(int index)483         public float getXCoeff(int index) {
484             return index <= degree ? xCoeff[index] : 0;
485         }
486 
487         /**
488          * Gets the Y coefficient with the specified index.
489          * @param index The index of the coefficient to return.
490          * @return The Y coefficient, or 0 if the index is greater than the degree.
491          */
getYCoeff(int index)492         public float getYCoeff(int index) {
493             return index <= degree ? yCoeff[index] : 0;
494         }
495 
estimate(float time, float[] c)496         private float estimate(float time, float[] c) {
497             float a = 0;
498             float scale = 1;
499             for (int i = 0; i <= degree; i++) {
500                 a += c[i] * scale;
501                 scale *= time;
502             }
503             return a;
504         }
505     }
506 }
507