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