1 /* 2 * Copyright (C) 2008 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.cts; 18 19 import static org.junit.Assert.fail; 20 21 import android.os.SystemClock; 22 import android.util.Log; 23 import android.view.MotionEvent; 24 import android.view.VelocityTracker; 25 26 import androidx.test.filters.SmallTest; 27 import androidx.test.runner.AndroidJUnit4; 28 29 import org.junit.After; 30 import org.junit.Before; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 34 /** 35 * Test {@link VelocityTracker}. 36 */ 37 @SmallTest 38 @RunWith(AndroidJUnit4.class) 39 public class VelocityTrackerTest { 40 private static final String TAG = "VelocityTrackerTest"; 41 42 private static final float TOLERANCE_EXACT = 0.01f; 43 private static final float TOLERANCE_TIGHT = 0.05f; 44 private static final float TOLERANCE_WEAK = 0.1f; 45 private static final float TOLERANCE_VERY_WEAK = 0.2f; 46 47 private VelocityTracker mVelocityTracker; 48 49 // Current position, velocity and acceleration. 50 private long mTime; 51 private long mLastTime; 52 private float mPx, mPy; 53 private float mVx, mVy; 54 private float mAx, mAy; 55 56 @Before setup()57 public void setup() { 58 mVelocityTracker = VelocityTracker.obtain(); 59 mTime = 1000; 60 mLastTime = 0; 61 mPx = 300; 62 mPy = 600; 63 mVx = 0; 64 mVy = 0; 65 mAx = 0; 66 mAy = 0; 67 } 68 69 @After teardown()70 public void teardown() { 71 mVelocityTracker.recycle(); 72 } 73 74 @Test testNoMovement()75 public void testNoMovement() { 76 move(100, 10); 77 assertVelocity(TOLERANCE_EXACT, "Expect exact bound when no movement occurs."); 78 } 79 80 @Test testLinearMovement()81 public void testLinearMovement() { 82 mVx = 2.0f; 83 mVy = -4.0f; 84 move(100, 10); 85 assertVelocity(TOLERANCE_TIGHT, "Expect tight bound for linear motion."); 86 } 87 88 @Test testAcceleratingMovement()89 public void testAcceleratingMovement() { 90 // A very good velocity tracking algorithm will produce a tight bound on 91 // simple acceleration. Certain alternate algorithms will fare less well but 92 // may be more stable in the presence of bad input data. 93 mVx = 2.0f; 94 mVy = -4.0f; 95 mAx = 1.0f; 96 mAy = -0.5f; 97 move(200, 10); 98 assertVelocity(TOLERANCE_WEAK, "Expect weak bound when there is acceleration."); 99 } 100 101 @Test testDeceleratingMovement()102 public void testDeceleratingMovement() { 103 // A very good velocity tracking algorithm will produce a tight bound on 104 // simple acceleration. Certain alternate algorithms will fare less well but 105 // may be more stable in the presence of bad input data. 106 mVx = 2.0f; 107 mVy = -4.0f; 108 mAx = -1.0f; 109 mAy = 0.2f; 110 move(200, 10); 111 assertVelocity(TOLERANCE_WEAK, "Expect weak bound when there is deceleration."); 112 } 113 114 @Test testLinearSharpDirectionChange()115 public void testLinearSharpDirectionChange() { 116 // After a sharp change of direction we expect the velocity to eventually 117 // converge but it might take a moment to get there. 118 mVx = 2.0f; 119 mVy = -4.0f; 120 move(100, 10); 121 assertVelocity(TOLERANCE_TIGHT, "Expect tight bound for linear motion."); 122 mVx = -1.0f; 123 mVy = -3.0f; 124 move(100, 10); 125 assertVelocity(TOLERANCE_WEAK, "Expect weak bound after 100ms of new direction."); 126 move(100, 10); 127 assertVelocity(TOLERANCE_TIGHT, "Expect tight bound after 200ms of new direction."); 128 } 129 130 @Test testLinearSharpDirectionChangeAfterALongPause()131 public void testLinearSharpDirectionChangeAfterALongPause() { 132 // Should be able to get a tighter bound if there is a pause before the 133 // change of direction. 134 mVx = 2.0f; 135 mVy = -4.0f; 136 move(100, 10); 137 assertVelocity(TOLERANCE_TIGHT, "Expect tight bound for linear motion."); 138 pause(100); 139 mVx = -1.0f; 140 mVy = -3.0f; 141 move(100, 10); 142 assertVelocity(TOLERANCE_TIGHT, 143 "Expect tight bound after a 100ms pause and 100ms of new direction."); 144 } 145 146 @Test testChangingAcceleration()147 public void testChangingAcceleration() { 148 // In real circumstances, the acceleration changes continuously throughout a 149 // gesture. Try to model this and see how the algorithm copes. 150 mVx = 2.0f; 151 mVy = -4.0f; 152 for (float change : new float[] { 1, -2, -3, -1, 1 }) { 153 mAx += 1.0f * change; 154 mAy += -0.5f * change; 155 move(30, 10); 156 } 157 assertVelocity(TOLERANCE_VERY_WEAK, 158 "Expect weak bound when there is changing acceleration."); 159 } 160 161 @Test testUsesRawCoordinates()162 public void testUsesRawCoordinates() { 163 VelocityTracker vt = VelocityTracker.obtain(); 164 final int numevents = 5; 165 166 final long downTime = SystemClock.uptimeMillis(); 167 for (int i = 0; i < numevents; i++) { 168 final long eventTime = downTime + i * 10; 169 int action = i == 0 ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_MOVE; 170 MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 0, 0, 0); 171 event.offsetLocation(i * 10, i * 10); 172 vt.addMovement(event); 173 } 174 vt.computeCurrentVelocity(1000); 175 float xVelocity = vt.getXVelocity(); 176 float yVelocity = vt.getYVelocity(); 177 if (xVelocity == 0 || yVelocity == 0) { 178 fail("VelocityTracker is using raw coordinates," 179 + " but it should be using adjusted coordinates"); 180 } 181 } 182 move(long duration, long step)183 private void move(long duration, long step) { 184 addMovement(); 185 while (duration > 0) { 186 duration -= step; 187 mTime += step; 188 mPx += (mAx / 2 * step + mVx) * step; 189 mPy += (mAy / 2 * step + mVy) * step; 190 mVx += mAx * step; 191 mVy += mAy * step; 192 addMovement(); 193 } 194 } 195 pause(long duration)196 private void pause(long duration) { 197 mTime += duration; 198 } 199 addMovement()200 private void addMovement() { 201 if (mTime > mLastTime) { 202 MotionEvent ev = MotionEvent.obtain(0L, mTime, MotionEvent.ACTION_MOVE, mPx, mPy, 0); 203 mVelocityTracker.addMovement(ev); 204 ev.recycle(); 205 mLastTime = mTime; 206 207 mVelocityTracker.computeCurrentVelocity(1); 208 final float estimatedVx = mVelocityTracker.getXVelocity(); 209 final float estimatedVy = mVelocityTracker.getYVelocity(); 210 Log.d(TAG, String.format( 211 "[%d] x=%6.1f, y=%6.1f, vx=%6.1f, vy=%6.1f, ax=%6.1f, ay=%6.1f, " 212 + "evx=%6.1f (%6.1f%%), evy=%6.1f (%6.1f%%)", 213 mTime, mPx, mPy, mVx, mVy, mAx, mAy, 214 estimatedVx, error(mVx, estimatedVx) * 100.0f, 215 estimatedVy, error(mVy, estimatedVy) * 100.0f)); 216 } 217 } 218 assertVelocity(float tolerance, String message)219 private void assertVelocity(float tolerance, String message) { 220 mVelocityTracker.computeCurrentVelocity(1); 221 final float estimatedVx = mVelocityTracker.getXVelocity(); 222 final float estimatedVy = mVelocityTracker.getYVelocity(); 223 float errorVx = error(mVx, estimatedVx); 224 float errorVy = error(mVy, estimatedVy); 225 if (errorVx > tolerance || errorVy > tolerance) { 226 fail(String.format("Velocity exceeds tolerance of %6.1f%%: " 227 + "expected vx=%6.1f, vy=%6.1f. " 228 + "actual vx=%6.1f (%6.1f%%), vy=%6.1f (%6.1f%%). %s", 229 tolerance * 100.0f, mVx, mVy, 230 estimatedVx, errorVx * 100.0f, estimatedVy, errorVy * 100.0f, message)); 231 } 232 } 233 error(float expected, float actual)234 private static float error(float expected, float actual) { 235 float absError = Math.abs(actual - expected); 236 if (absError < 0.001f) { 237 return 0; 238 } 239 if (Math.abs(expected) < 0.001f) { 240 return 1; 241 } 242 return absError / Math.abs(expected); 243 } 244 } 245