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