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