|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/basictypes.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/scoped_ptr.h" | 
|  | #include "base/time/time.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "ui/events/gesture_detection/velocity_tracker_state.h" | 
|  | #include "ui/events/test/mock_motion_event.h" | 
|  | #include "ui/gfx/geometry/point_f.h" | 
|  | #include "ui/gfx/geometry/vector2d_f.h" | 
|  |  | 
|  | using base::TimeDelta; | 
|  | using base::TimeTicks; | 
|  | using ui::test::MockMotionEvent; | 
|  |  | 
|  | namespace ui { | 
|  | namespace { | 
|  |  | 
|  | const TimeDelta kTenMillis = TimeDelta::FromMilliseconds(10); | 
|  | const TimeDelta kOneSecond = TimeDelta::FromSeconds(1); | 
|  | const float kEpsilson = .01f; | 
|  |  | 
|  | const char* GetStrategyName(VelocityTracker::Strategy strategy) { | 
|  | switch (strategy) { | 
|  | case VelocityTracker::LSQ1: return "LSQ1"; | 
|  | case VelocityTracker::LSQ2: return "LSQ2"; | 
|  | case VelocityTracker::LSQ3: return "LSQ3"; | 
|  | case VelocityTracker::WLSQ2_DELTA: return "WLSQ2_DELTA"; | 
|  | case VelocityTracker::WLSQ2_CENTRAL: return "WLSQ2_CENTRAL"; | 
|  | case VelocityTracker::WLSQ2_RECENT: return "WLSQ2_RECENT"; | 
|  | case VelocityTracker::INT1: return "INT1"; | 
|  | case VelocityTracker::INT2: return "INT2"; | 
|  | }; | 
|  | NOTREACHED() << "Invalid strategy"; | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class VelocityTrackerTest : public testing::Test { | 
|  | public: | 
|  | VelocityTrackerTest() {} | 
|  | virtual ~VelocityTrackerTest() {} | 
|  |  | 
|  | protected: | 
|  | static MockMotionEvent Sample(MotionEvent::Action action, | 
|  | gfx::PointF p0, | 
|  | TimeTicks t0, | 
|  | gfx::Vector2dF v, | 
|  | TimeDelta dt) { | 
|  | const gfx::PointF p = p0 + ScaleVector2d(v, dt.InSecondsF()); | 
|  | return MockMotionEvent(action, t0 + dt, p.x(), p.y()); | 
|  | } | 
|  |  | 
|  | static void ApplyMovementSequence(VelocityTrackerState* state, | 
|  | gfx::PointF p0, | 
|  | gfx::Vector2dF v, | 
|  | TimeTicks t0, | 
|  | TimeDelta t, | 
|  | size_t samples) { | 
|  | EXPECT_TRUE(!!samples); | 
|  | if (!samples) | 
|  | return; | 
|  | const base::TimeDelta dt = t / samples; | 
|  | state->AddMovement(Sample(MotionEvent::ACTION_DOWN, p0, t0, v, dt * 0)); | 
|  | ApplyMovement(state, p0, v, t0, t, samples); | 
|  | state->AddMovement(Sample(MotionEvent::ACTION_UP, p0, t0, v, t)); | 
|  | } | 
|  |  | 
|  | static void ApplyMovement(VelocityTrackerState* state, | 
|  | gfx::PointF p0, | 
|  | gfx::Vector2dF v, | 
|  | TimeTicks t0, | 
|  | TimeDelta t, | 
|  | size_t samples) { | 
|  | EXPECT_TRUE(!!samples); | 
|  | if (!samples) | 
|  | return; | 
|  | const base::TimeDelta dt = t / samples; | 
|  | for (size_t i = 0; i < samples; ++i) | 
|  | state->AddMovement(Sample(MotionEvent::ACTION_MOVE, p0, t0, v, dt * i)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_F(VelocityTrackerTest, Basic) { | 
|  | const gfx::PointF p0(0, 0); | 
|  | const gfx::Vector2dF v(0, 500); | 
|  | const size_t samples = 60; | 
|  |  | 
|  | for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) { | 
|  | VelocityTracker::Strategy strategy = | 
|  | static_cast<VelocityTracker::Strategy>(i); | 
|  |  | 
|  | SCOPED_TRACE(GetStrategyName(strategy)); | 
|  | VelocityTrackerState state(strategy); | 
|  |  | 
|  | // Default state should report zero velocity. | 
|  | EXPECT_EQ(0, state.GetXVelocity(0)); | 
|  | EXPECT_EQ(0, state.GetYVelocity(0)); | 
|  |  | 
|  | // Sample a constant velocity sequence. | 
|  | ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), kOneSecond, samples); | 
|  |  | 
|  | // The computed velocity should match that of the input. | 
|  | state.ComputeCurrentVelocity(1000, 20000); | 
|  | EXPECT_NEAR(v.x(), state.GetXVelocity(0), kEpsilson * v.x()); | 
|  | EXPECT_NEAR(v.y(), state.GetYVelocity(0), kEpsilson * v.y()); | 
|  |  | 
|  | // A pointer ID of -1 should report the velocity of the active pointer. | 
|  | EXPECT_NEAR(v.x(), state.GetXVelocity(-1), kEpsilson * v.x()); | 
|  | EXPECT_NEAR(v.y(), state.GetYVelocity(-1), kEpsilson * v.y()); | 
|  |  | 
|  | // Invalid pointer ID's should report zero velocity. | 
|  | EXPECT_EQ(0, state.GetXVelocity(1)); | 
|  | EXPECT_EQ(0, state.GetYVelocity(1)); | 
|  | EXPECT_EQ(0, state.GetXVelocity(7)); | 
|  | EXPECT_EQ(0, state.GetYVelocity(7)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(VelocityTrackerTest, MaxVelocity) { | 
|  | const gfx::PointF p0(0, 0); | 
|  | const gfx::Vector2dF v(-50000, 50000); | 
|  | const size_t samples = 3; | 
|  | const base::TimeDelta dt = kTenMillis * 2; | 
|  |  | 
|  | VelocityTrackerState state; | 
|  | ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), dt, samples); | 
|  |  | 
|  | // The computed velocity should be restricted to the provided maximum. | 
|  | state.ComputeCurrentVelocity(1000, 100); | 
|  | EXPECT_NEAR(-100, state.GetXVelocity(0), kEpsilson); | 
|  | EXPECT_NEAR(100, state.GetYVelocity(0), kEpsilson); | 
|  |  | 
|  | state.ComputeCurrentVelocity(1000, 1000); | 
|  | EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson); | 
|  | EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson); | 
|  | } | 
|  |  | 
|  | TEST_F(VelocityTrackerTest, VaryingVelocity) { | 
|  | const gfx::PointF p0(0, 0); | 
|  | const gfx::Vector2dF vFast(0, 500); | 
|  | const gfx::Vector2dF vSlow = ScaleVector2d(vFast, 0.5f); | 
|  | const size_t samples = 12; | 
|  |  | 
|  | for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) { | 
|  | VelocityTracker::Strategy strategy = | 
|  | static_cast<VelocityTracker::Strategy>(i); | 
|  |  | 
|  | SCOPED_TRACE(GetStrategyName(strategy)); | 
|  | VelocityTrackerState state(strategy); | 
|  |  | 
|  | base::TimeTicks t0 = base::TimeTicks::Now(); | 
|  | base::TimeDelta dt = kTenMillis * 10; | 
|  | state.AddMovement( | 
|  | Sample(MotionEvent::ACTION_DOWN, p0, t0, vFast, base::TimeDelta())); | 
|  |  | 
|  | // Apply some fast movement and compute the velocity. | 
|  | gfx::PointF pCurr = p0; | 
|  | base::TimeTicks tCurr = t0; | 
|  | ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples); | 
|  | state.ComputeCurrentVelocity(1000, 20000); | 
|  | float vOldY = state.GetYVelocity(0); | 
|  |  | 
|  | // Apply some slow movement. | 
|  | pCurr += ScaleVector2d(vFast, dt.InSecondsF()); | 
|  | tCurr += dt; | 
|  | ApplyMovement(&state, pCurr, vSlow, tCurr, dt, samples); | 
|  |  | 
|  | // The computed velocity should have decreased. | 
|  | state.ComputeCurrentVelocity(1000, 20000); | 
|  | float vCurrentY = state.GetYVelocity(0); | 
|  | EXPECT_GT(vFast.y(), vCurrentY); | 
|  | EXPECT_GT(vOldY, vCurrentY); | 
|  | vOldY = vCurrentY; | 
|  |  | 
|  | // Apply some additional fast movement. | 
|  | pCurr += ScaleVector2d(vSlow, dt.InSecondsF()); | 
|  | tCurr += dt; | 
|  | ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples); | 
|  |  | 
|  | // The computed velocity should have increased. | 
|  | state.ComputeCurrentVelocity(1000, 20000); | 
|  | vCurrentY = state.GetYVelocity(0); | 
|  | EXPECT_LT(vSlow.y(), vCurrentY); | 
|  | EXPECT_LT(vOldY, vCurrentY); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(VelocityTrackerTest, DelayedActionUp) { | 
|  | const gfx::PointF p0(0, 0); | 
|  | const gfx::Vector2dF v(-50000, 50000); | 
|  | const size_t samples = 10; | 
|  | const base::TimeTicks t0 = base::TimeTicks::Now(); | 
|  | const base::TimeDelta dt = kTenMillis * 2; | 
|  |  | 
|  | VelocityTrackerState state; | 
|  | state.AddMovement( | 
|  | Sample(MotionEvent::ACTION_DOWN, p0, t0, v, base::TimeDelta())); | 
|  |  | 
|  | // Apply the movement and verify a (non-zero) velocity. | 
|  | ApplyMovement(&state, p0, v, t0, dt, samples); | 
|  | state.ComputeCurrentVelocity(1000, 1000); | 
|  | EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson); | 
|  | EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson); | 
|  |  | 
|  | // Apply the delayed ACTION_UP. | 
|  | const gfx::PointF p1 = p0 + ScaleVector2d(v, dt.InSecondsF()); | 
|  | const base::TimeTicks t1 = t0 + dt + kTenMillis * 10; | 
|  | state.AddMovement(Sample( | 
|  | MotionEvent::ACTION_UP, p1, t1, v, base::TimeDelta())); | 
|  |  | 
|  | // The tracked velocity should have been reset. | 
|  | state.ComputeCurrentVelocity(1000, 1000); | 
|  | EXPECT_EQ(0.f, state.GetXVelocity(0)); | 
|  | EXPECT_EQ(0.f, state.GetYVelocity(0)); | 
|  | } | 
|  |  | 
|  | }  // namespace ui |