1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/basictypes.h"
6 #include "base/logging.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/time/time.h"
9 #include "testing/gtest/include/gtest/gtest.h"
10 #include "ui/events/gesture_detection/velocity_tracker_state.h"
11 #include "ui/events/test/motion_event_test_utils.h"
12 #include "ui/gfx/geometry/point_f.h"
13 #include "ui/gfx/geometry/vector2d_f.h"
15 using base::TimeDelta
;
16 using base::TimeTicks
;
17 using ui::test::MockMotionEvent
;
22 const TimeDelta kTenMillis
= TimeDelta::FromMilliseconds(10);
23 const TimeDelta kOneSecond
= TimeDelta::FromSeconds(1);
24 const float kEpsilson
= .01f
;
26 const char* GetStrategyName(VelocityTracker::Strategy strategy
) {
28 case VelocityTracker::LSQ1
: return "LSQ1";
29 case VelocityTracker::LSQ2
: return "LSQ2";
30 case VelocityTracker::LSQ2_RESTRICTED
: return "LSQ2_RESTRICTED";
31 case VelocityTracker::LSQ3
: return "LSQ3";
32 case VelocityTracker::WLSQ2_DELTA
: return "WLSQ2_DELTA";
33 case VelocityTracker::WLSQ2_CENTRAL
: return "WLSQ2_CENTRAL";
34 case VelocityTracker::WLSQ2_RECENT
: return "WLSQ2_RECENT";
35 case VelocityTracker::INT1
: return "INT1";
36 case VelocityTracker::INT2
: return "INT2";
38 NOTREACHED() << "Invalid strategy";
44 class VelocityTrackerTest
: public testing::Test
{
46 VelocityTrackerTest() {}
47 ~VelocityTrackerTest() override
{}
50 static MockMotionEvent
Sample(MotionEvent::Action action
,
55 const gfx::PointF p
= p0
+ ScaleVector2d(v
, dt
.InSecondsF());
56 return MockMotionEvent(action
, t0
+ dt
, p
.x(), p
.y());
59 static void ApplyMovementSequence(VelocityTrackerState
* state
,
65 EXPECT_TRUE(!!samples
);
68 const base::TimeDelta dt
= t
/ samples
;
69 state
->AddMovement(Sample(MotionEvent::ACTION_DOWN
, p0
, t0
, v
, dt
* 0));
70 ApplyMovement(state
, p0
, v
, t0
, t
, samples
);
71 state
->AddMovement(Sample(MotionEvent::ACTION_UP
, p0
, t0
, v
, t
));
74 static void ApplyMovement(VelocityTrackerState
* state
,
80 EXPECT_TRUE(!!samples
);
83 const base::TimeDelta dt
= t
/ samples
;
84 for (size_t i
= 0; i
< samples
; ++i
)
85 state
->AddMovement(Sample(MotionEvent::ACTION_MOVE
, p0
, t0
, v
, dt
* i
));
89 TEST_F(VelocityTrackerTest
, Basic
) {
90 const gfx::PointF
p0(0, 0);
91 const gfx::Vector2dF
v(0, 500);
92 const size_t samples
= 60;
94 for (int i
= 0; i
<= VelocityTracker::STRATEGY_MAX
; ++i
) {
95 VelocityTracker::Strategy strategy
=
96 static_cast<VelocityTracker::Strategy
>(i
);
98 SCOPED_TRACE(GetStrategyName(strategy
));
99 VelocityTrackerState
state(strategy
);
101 // Default state should report zero velocity.
102 EXPECT_EQ(0, state
.GetXVelocity(0));
103 EXPECT_EQ(0, state
.GetYVelocity(0));
105 // Sample a constant velocity sequence.
106 ApplyMovementSequence(&state
, p0
, v
, TimeTicks::Now(), kOneSecond
, samples
);
108 // The computed velocity should match that of the input.
109 state
.ComputeCurrentVelocity(1000, 20000);
110 EXPECT_NEAR(v
.x(), state
.GetXVelocity(0), kEpsilson
* v
.x());
111 EXPECT_NEAR(v
.y(), state
.GetYVelocity(0), kEpsilson
* v
.y());
113 // A pointer ID of -1 should report the velocity of the active pointer.
114 EXPECT_NEAR(v
.x(), state
.GetXVelocity(-1), kEpsilson
* v
.x());
115 EXPECT_NEAR(v
.y(), state
.GetYVelocity(-1), kEpsilson
* v
.y());
117 // Invalid pointer ID's should report zero velocity.
118 EXPECT_EQ(0, state
.GetXVelocity(1));
119 EXPECT_EQ(0, state
.GetYVelocity(1));
120 EXPECT_EQ(0, state
.GetXVelocity(7));
121 EXPECT_EQ(0, state
.GetYVelocity(7));
125 TEST_F(VelocityTrackerTest
, MaxVelocity
) {
126 const gfx::PointF
p0(0, 0);
127 const gfx::Vector2dF
v(-50000, 50000);
128 const size_t samples
= 3;
129 const base::TimeDelta dt
= kTenMillis
* 2;
131 VelocityTrackerState
state(VelocityTracker::Strategy::LSQ2
);
132 ApplyMovementSequence(&state
, p0
, v
, TimeTicks::Now(), dt
, samples
);
134 // The computed velocity should be restricted to the provided maximum.
135 state
.ComputeCurrentVelocity(1000, 100);
136 EXPECT_NEAR(-100, state
.GetXVelocity(0), kEpsilson
);
137 EXPECT_NEAR(100, state
.GetYVelocity(0), kEpsilson
);
139 state
.ComputeCurrentVelocity(1000, 1000);
140 EXPECT_NEAR(-1000, state
.GetXVelocity(0), kEpsilson
);
141 EXPECT_NEAR(1000, state
.GetYVelocity(0), kEpsilson
);
144 TEST_F(VelocityTrackerTest
, VaryingVelocity
) {
145 const gfx::PointF
p0(0, 0);
146 const gfx::Vector2dF
vFast(0, 500);
147 const gfx::Vector2dF vSlow
= ScaleVector2d(vFast
, 0.5f
);
148 const size_t samples
= 12;
150 for (int i
= 0; i
<= VelocityTracker::STRATEGY_MAX
; ++i
) {
151 VelocityTracker::Strategy strategy
=
152 static_cast<VelocityTracker::Strategy
>(i
);
154 SCOPED_TRACE(GetStrategyName(strategy
));
155 VelocityTrackerState
state(strategy
);
157 base::TimeTicks t0
= base::TimeTicks::Now();
158 base::TimeDelta dt
= kTenMillis
* 10;
160 Sample(MotionEvent::ACTION_DOWN
, p0
, t0
, vFast
, base::TimeDelta()));
162 // Apply some fast movement and compute the velocity.
163 gfx::PointF pCurr
= p0
;
164 base::TimeTicks tCurr
= t0
;
165 ApplyMovement(&state
, pCurr
, vFast
, tCurr
, dt
, samples
);
166 state
.ComputeCurrentVelocity(1000, 20000);
167 float vOldY
= state
.GetYVelocity(0);
169 // Apply some slow movement.
170 pCurr
+= ScaleVector2d(vFast
, dt
.InSecondsF());
172 ApplyMovement(&state
, pCurr
, vSlow
, tCurr
, dt
, samples
);
174 // The computed velocity should have decreased.
175 state
.ComputeCurrentVelocity(1000, 20000);
176 float vCurrentY
= state
.GetYVelocity(0);
177 EXPECT_GT(vFast
.y(), vCurrentY
);
178 EXPECT_GT(vOldY
, vCurrentY
);
181 // Apply some additional fast movement.
182 pCurr
+= ScaleVector2d(vSlow
, dt
.InSecondsF());
184 ApplyMovement(&state
, pCurr
, vFast
, tCurr
, dt
, samples
);
186 // The computed velocity should have increased.
187 state
.ComputeCurrentVelocity(1000, 20000);
188 vCurrentY
= state
.GetYVelocity(0);
189 EXPECT_LT(vSlow
.y(), vCurrentY
);
190 EXPECT_LT(vOldY
, vCurrentY
);
194 TEST_F(VelocityTrackerTest
, DelayedActionUp
) {
195 const gfx::PointF
p0(0, 0);
196 const gfx::Vector2dF
v(-50000, 50000);
197 const size_t samples
= 10;
198 const base::TimeTicks t0
= base::TimeTicks::Now();
199 const base::TimeDelta dt
= kTenMillis
* 2;
201 VelocityTrackerState
state(VelocityTracker::Strategy::LSQ2
);
203 Sample(MotionEvent::ACTION_DOWN
, p0
, t0
, v
, base::TimeDelta()));
205 // Apply the movement and verify a (non-zero) velocity.
206 ApplyMovement(&state
, p0
, v
, t0
, dt
, samples
);
207 state
.ComputeCurrentVelocity(1000, 1000);
208 EXPECT_NEAR(-1000, state
.GetXVelocity(0), kEpsilson
);
209 EXPECT_NEAR(1000, state
.GetYVelocity(0), kEpsilson
);
211 // Apply the delayed ACTION_UP.
212 const gfx::PointF p1
= p0
+ ScaleVector2d(v
, dt
.InSecondsF());
213 const base::TimeTicks t1
= t0
+ dt
+ kTenMillis
* 10;
214 state
.AddMovement(Sample(
215 MotionEvent::ACTION_UP
, p1
, t1
, v
, base::TimeDelta()));
217 // The tracked velocity should have been reset.
218 state
.ComputeCurrentVelocity(1000, 1000);
219 EXPECT_EQ(0.f
, state
.GetXVelocity(0));
220 EXPECT_EQ(0.f
, state
.GetYVelocity(0));
223 // Tests that a rapid deceleration won't result in a velocity going in the
224 // opposite direction to the pointers primary movement, with the LSQ_RESTRICTED
225 // strategy. See crbug.com/417855.
226 TEST_F(VelocityTrackerTest
, NoDirectionReversal
) {
227 VelocityTrackerState
state_unrestricted(VelocityTracker::LSQ2
);
228 VelocityTrackerState
state_restricted(VelocityTracker::LSQ2_RESTRICTED
);
229 const base::TimeTicks t0
= base::TimeTicks::Now();
230 const base::TimeDelta dt
= base::TimeDelta::FromMilliseconds(1);
231 const size_t samples
= 60;
235 MockMotionEvent
m1(MotionEvent::ACTION_DOWN
, t0
, p
.x(), p
.y());
236 state_unrestricted
.AddMovement(m1
);
237 state_restricted
.AddMovement(m1
);
239 for (size_t i
= 0; i
< samples
; ++i
) {
242 MockMotionEvent
mi(MotionEvent::ACTION_MOVE
, t0
+ dt
* i
, p
.x(), p
.y());
243 state_unrestricted
.AddMovement(mi
);
244 state_restricted
.AddMovement(mi
);
247 // The computed velocity be zero, as we stopped at the end of the gesture. In
248 // particular, it should not be negative, as all movement was in the positive
250 state_restricted
.ComputeCurrentVelocity(1000, 20000);
251 EXPECT_EQ(0, state_restricted
.GetXVelocity(0));
252 EXPECT_EQ(0, state_restricted
.GetYVelocity(0));
254 // This does not hold for the unrestricted LSQ2 strategy.
255 state_unrestricted
.ComputeCurrentVelocity(1000, 20000);
256 EXPECT_EQ(0, state_unrestricted
.GetXVelocity(0));
257 // Y velocity is negative, despite the fact that the finger only moved in the
258 // positive y direction.
259 EXPECT_GT(0, state_unrestricted
.GetYVelocity(0));