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