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 15 using base::TimeDelta; 16 using base::TimeTicks; 17 using ui::test::MockMotionEvent; 18 19 namespace ui { 20 namespace { 21 22 const TimeDelta kTenMillis = TimeDelta::FromMilliseconds(10); 23 const TimeDelta kOneSecond = TimeDelta::FromSeconds(1); 24 const float kEpsilson = .01f; 25 26 const 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 43 class 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 88 TEST_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 124 TEST_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 143 TEST_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 193 TEST_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