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