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