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.
4
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/mock_motion_event.h"
12#include "ui/gfx/geometry/point_f.h"
13#include "ui/gfx/geometry/vector2d_f.h"
14
15using base::TimeDelta;
16using base::TimeTicks;
17using ui::test::MockMotionEvent;
18
19namespace ui {
20namespace {
21
22const TimeDelta kTenMillis = TimeDelta::FromMilliseconds(10);
23const TimeDelta kOneSecond = TimeDelta::FromSeconds(1);
24const float kEpsilson = .01f;
25
26const char* GetStrategyName(VelocityTracker::Strategy strategy) {
27  switch (strategy) {
28    case VelocityTracker::LSQ1: return "LSQ1";
29    case VelocityTracker::LSQ2: return "LSQ2";
30    case VelocityTracker::LSQ3: return "LSQ3";
31    case VelocityTracker::WLSQ2_DELTA: return "WLSQ2_DELTA";
32    case VelocityTracker::WLSQ2_CENTRAL: return "WLSQ2_CENTRAL";
33    case VelocityTracker::WLSQ2_RECENT: return "WLSQ2_RECENT";
34    case VelocityTracker::INT1: return "INT1";
35    case VelocityTracker::INT2: return "INT2";
36  };
37  NOTREACHED() << "Invalid strategy";
38  return "";
39}
40
41}  // namespace
42
43class VelocityTrackerTest : public testing::Test {
44 public:
45  VelocityTrackerTest() {}
46  virtual ~VelocityTrackerTest() {}
47
48 protected:
49  static MockMotionEvent Sample(MotionEvent::Action action,
50                                gfx::PointF p0,
51                                TimeTicks t0,
52                                gfx::Vector2dF v,
53                                TimeDelta dt) {
54    const gfx::PointF p = p0 + ScaleVector2d(v, dt.InSecondsF());
55    return MockMotionEvent(action, t0 + dt, p.x(), p.y());
56  }
57
58  static void ApplyMovementSequence(VelocityTrackerState* state,
59                                    gfx::PointF p0,
60                                    gfx::Vector2dF v,
61                                    TimeTicks t0,
62                                    TimeDelta t,
63                                    size_t samples) {
64    EXPECT_TRUE(!!samples);
65    if (!samples)
66      return;
67    const base::TimeDelta dt = t / samples;
68    state->AddMovement(Sample(MotionEvent::ACTION_DOWN, p0, t0, v, dt * 0));
69    ApplyMovement(state, p0, v, t0, t, samples);
70    state->AddMovement(Sample(MotionEvent::ACTION_UP, p0, t0, v, t));
71  }
72
73  static void ApplyMovement(VelocityTrackerState* state,
74                            gfx::PointF p0,
75                            gfx::Vector2dF v,
76                            TimeTicks t0,
77                            TimeDelta t,
78                            size_t samples) {
79    EXPECT_TRUE(!!samples);
80    if (!samples)
81      return;
82    const base::TimeDelta dt = t / samples;
83    for (size_t i = 0; i < samples; ++i)
84      state->AddMovement(Sample(MotionEvent::ACTION_MOVE, p0, t0, v, dt * i));
85  }
86};
87
88TEST_F(VelocityTrackerTest, Basic) {
89  const gfx::PointF p0(0, 0);
90  const gfx::Vector2dF v(0, 500);
91  const size_t samples = 60;
92
93  for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) {
94    VelocityTracker::Strategy strategy =
95        static_cast<VelocityTracker::Strategy>(i);
96
97    SCOPED_TRACE(GetStrategyName(strategy));
98    VelocityTrackerState state(strategy);
99
100    // Default state should report zero velocity.
101    EXPECT_EQ(0, state.GetXVelocity(0));
102    EXPECT_EQ(0, state.GetYVelocity(0));
103
104    // Sample a constant velocity sequence.
105    ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), kOneSecond, samples);
106
107    // The computed velocity should match that of the input.
108    state.ComputeCurrentVelocity(1000, 20000);
109    EXPECT_NEAR(v.x(), state.GetXVelocity(0), kEpsilson * v.x());
110    EXPECT_NEAR(v.y(), state.GetYVelocity(0), kEpsilson * v.y());
111
112    // A pointer ID of -1 should report the velocity of the active pointer.
113    EXPECT_NEAR(v.x(), state.GetXVelocity(-1), kEpsilson * v.x());
114    EXPECT_NEAR(v.y(), state.GetYVelocity(-1), kEpsilson * v.y());
115
116    // Invalid pointer ID's should report zero velocity.
117    EXPECT_EQ(0, state.GetXVelocity(1));
118    EXPECT_EQ(0, state.GetYVelocity(1));
119    EXPECT_EQ(0, state.GetXVelocity(7));
120    EXPECT_EQ(0, state.GetYVelocity(7));
121  }
122}
123
124TEST_F(VelocityTrackerTest, MaxVelocity) {
125  const gfx::PointF p0(0, 0);
126  const gfx::Vector2dF v(-50000, 50000);
127  const size_t samples = 3;
128  const base::TimeDelta dt = kTenMillis * 2;
129
130  VelocityTrackerState state;
131  ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), dt, samples);
132
133  // The computed velocity should be restricted to the provided maximum.
134  state.ComputeCurrentVelocity(1000, 100);
135  EXPECT_NEAR(-100, state.GetXVelocity(0), kEpsilson);
136  EXPECT_NEAR(100, state.GetYVelocity(0), kEpsilson);
137
138  state.ComputeCurrentVelocity(1000, 1000);
139  EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson);
140  EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson);
141}
142
143TEST_F(VelocityTrackerTest, VaryingVelocity) {
144  const gfx::PointF p0(0, 0);
145  const gfx::Vector2dF vFast(0, 500);
146  const gfx::Vector2dF vSlow = ScaleVector2d(vFast, 0.5f);
147  const size_t samples = 12;
148
149  for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) {
150    VelocityTracker::Strategy strategy =
151        static_cast<VelocityTracker::Strategy>(i);
152
153    SCOPED_TRACE(GetStrategyName(strategy));
154    VelocityTrackerState state(strategy);
155
156    base::TimeTicks t0 = base::TimeTicks::Now();
157    base::TimeDelta dt = kTenMillis * 10;
158    state.AddMovement(
159        Sample(MotionEvent::ACTION_DOWN, p0, t0, vFast, base::TimeDelta()));
160
161    // Apply some fast movement and compute the velocity.
162    gfx::PointF pCurr = p0;
163    base::TimeTicks tCurr = t0;
164    ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples);
165    state.ComputeCurrentVelocity(1000, 20000);
166    float vOldY = state.GetYVelocity(0);
167
168    // Apply some slow movement.
169    pCurr += ScaleVector2d(vFast, dt.InSecondsF());
170    tCurr += dt;
171    ApplyMovement(&state, pCurr, vSlow, tCurr, dt, samples);
172
173    // The computed velocity should have decreased.
174    state.ComputeCurrentVelocity(1000, 20000);
175    float vCurrentY = state.GetYVelocity(0);
176    EXPECT_GT(vFast.y(), vCurrentY);
177    EXPECT_GT(vOldY, vCurrentY);
178    vOldY = vCurrentY;
179
180    // Apply some additional fast movement.
181    pCurr += ScaleVector2d(vSlow, dt.InSecondsF());
182    tCurr += dt;
183    ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples);
184
185    // The computed velocity should have increased.
186    state.ComputeCurrentVelocity(1000, 20000);
187    vCurrentY = state.GetYVelocity(0);
188    EXPECT_LT(vSlow.y(), vCurrentY);
189    EXPECT_LT(vOldY, vCurrentY);
190  }
191}
192
193TEST_F(VelocityTrackerTest, DelayedActionUp) {
194  const gfx::PointF p0(0, 0);
195  const gfx::Vector2dF v(-50000, 50000);
196  const size_t samples = 10;
197  const base::TimeTicks t0 = base::TimeTicks::Now();
198  const base::TimeDelta dt = kTenMillis * 2;
199
200  VelocityTrackerState state;
201  state.AddMovement(
202      Sample(MotionEvent::ACTION_DOWN, p0, t0, v, base::TimeDelta()));
203
204  // Apply the movement and verify a (non-zero) velocity.
205  ApplyMovement(&state, p0, v, t0, dt, samples);
206  state.ComputeCurrentVelocity(1000, 1000);
207  EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson);
208  EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson);
209
210  // Apply the delayed ACTION_UP.
211  const gfx::PointF p1 = p0 + ScaleVector2d(v, dt.InSecondsF());
212  const base::TimeTicks t1 = t0 + dt + kTenMillis * 10;
213  state.AddMovement(Sample(
214      MotionEvent::ACTION_UP, p1, t1, v, base::TimeDelta()));
215
216  // The tracked velocity should have been reset.
217  state.ComputeCurrentVelocity(1000, 1000);
218  EXPECT_EQ(0.f, state.GetXVelocity(0));
219  EXPECT_EQ(0.f, state.GetYVelocity(0));
220}
221
222}  // namespace ui
223