// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/graphics/gestures/side_swipe_detector.h"
#include <memory>
#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/timer/timer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/aura/window.h"
#include "ui/events/test/event_generator.h"
// Gmock matchers and actions that we use below.
using testing::_;
using testing::AnyOf;
using testing::Eq;
using testing::Return;
namespace chromecast {
namespace test {
namespace {
constexpr base::TimeDelta kTimeDelay = base::Milliseconds(100);
constexpr int kSwipeDistance = 50;
constexpr int kNumSteps = 5;
// constexpr gfx::Point kZeroPoint{0, 0};
} // namespace
class MockCastGestureHandler : public CastGestureHandler {
public:
~MockCastGestureHandler() override = default;
MOCK_METHOD0(GetPriority, Priority());
MOCK_METHOD1(CanHandleSwipe, bool(CastSideSwipeOrigin origin));
MOCK_METHOD3(HandleSideSwipe,
void(CastSideSwipeEvent event,
CastSideSwipeOrigin swipe_origin,
const gfx::Point& touch_location));
MOCK_METHOD1(HandleTapDownGesture, void(const gfx::Point& touch_location));
MOCK_METHOD1(HandleTapGesture, void(const gfx::Point& touch_location));
};
// Event sink to check for events that get through (or don't get through) after
// the system gesture handler handles them.
class TestEventHandler : public ui::EventHandler {
public:
TestEventHandler() : EventHandler(), num_touch_events_received_(0) {}
void OnTouchEvent(ui::TouchEvent* event) override {
num_touch_events_received_++;
}
int NumTouchEventsReceived() const { return num_touch_events_received_; }
private:
int num_touch_events_received_;
};
class SideSwipeDetectorTest : public aura::test::AuraTestBase {
public:
~SideSwipeDetectorTest() override = default;
void SetUp() override {
aura::test::AuraTestBase::SetUp();
gesture_handler_ = std::make_unique<MockCastGestureHandler>();
side_swipe_detector_ = std::make_unique<SideSwipeDetector>(
gesture_handler_.get(), root_window());
test_event_handler_ = std::make_unique<TestEventHandler>();
root_window()->AddPostTargetHandler(test_event_handler_.get());
mock_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::Time::Now(), base::TimeTicks::Now());
auto mock_timer = std::make_unique<base::OneShotTimer>(
mock_task_runner_->GetMockTickClock());
mock_timer->SetTaskRunner(mock_task_runner_);
}
void TearDown() override {
side_swipe_detector_.reset();
gesture_handler_.reset();
aura::test::AuraTestBase::TearDown();
}
void Drag(const gfx::Point& start_point,
const base::TimeDelta& start_hold_time,
const base::TimeDelta& drag_time,
const gfx::Point& end_point,
ui::PointerId pointer_id,
bool end_release = true) {
ui::TouchEvent press(
ui::EventType::kTouchPressed, start_point, mock_clock()->NowTicks(),
ui::PointerDetails(ui::EventPointerType::kTouch, pointer_id));
GetEventGenerator().Dispatch(&press);
mock_task_runner()->AdvanceMockTickClock(start_hold_time);
mock_task_runner()->FastForwardBy(start_hold_time);
ui::TouchEvent move(
ui::EventType::kTouchMoved, end_point, mock_clock()->NowTicks(),
ui::PointerDetails(ui::EventPointerType::kTouch, pointer_id));
GetEventGenerator().Dispatch(&move);
mock_task_runner()->AdvanceMockTickClock(drag_time);
mock_task_runner()->FastForwardBy(drag_time);
if (end_release) {
ui::TouchEvent release(
ui::EventType::kTouchReleased, end_point, mock_clock()->NowTicks(),
ui::PointerDetails(ui::EventPointerType::kTouch, pointer_id));
GetEventGenerator().Dispatch(&release);
}
}
ui::test::EventGenerator& GetEventGenerator() {
if (!event_generator_) {
event_generator_ =
std::make_unique<ui::test::EventGenerator>(root_window());
}
return *event_generator_.get();
}
MockCastGestureHandler& mock_gesture_handler() { return *gesture_handler_; }
base::TestMockTimeTaskRunner* mock_task_runner() const {
return mock_task_runner_.get();
}
const base::TickClock* mock_clock() const {
return mock_task_runner_->GetMockTickClock();
}
TestEventHandler& test_event_handler() { return *test_event_handler_; }
private:
std::unique_ptr<ui::test::EventGenerator> event_generator_;
scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
std::unique_ptr<SideSwipeDetector> side_swipe_detector_;
std::unique_ptr<TestEventHandler> test_event_handler_;
std::unique_ptr<MockCastGestureHandler> gesture_handler_;
};
// Test that initialization works and initial state is clean.
TEST_F(SideSwipeDetectorTest, Initialization) {
EXPECT_CALL(mock_gesture_handler(), CanHandleSwipe(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(mock_gesture_handler(), HandleSideSwipe(_, _, _)).Times(0);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, test_event_handler().NumTouchEventsReceived());
}
// A swipe in the middle of the screen should produce no system gesture.
TEST_F(SideSwipeDetectorTest, SwipeWithNoSystemGesture) {
gfx::Point drag_point(root_window()->bounds().width() / 2,
root_window()->bounds().height() / 2);
ui::test::EventGenerator& generator = GetEventGenerator();
generator.GestureScrollSequence(drag_point,
drag_point - gfx::Vector2d(0, kSwipeDistance),
kTimeDelay, kNumSteps);
EXPECT_CALL(mock_gesture_handler(), CanHandleSwipe(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(mock_gesture_handler(), HandleSideSwipe(_, _, _)).Times(0);
base::RunLoop().RunUntilIdle();
EXPECT_NE(0, test_event_handler().NumTouchEventsReceived());
}
TEST_F(SideSwipeDetectorTest, SwipeFromLeft) {
gfx::Point drag_point(0, root_window()->bounds().height() / 2);
auto end_point = drag_point + gfx::Vector2d(kSwipeDistance, 0);
EXPECT_CALL(mock_gesture_handler(),
CanHandleSwipe(Eq(CastSideSwipeOrigin::LEFT)))
.WillRepeatedly(Return(true));
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::BEGIN,
Eq(CastSideSwipeOrigin::LEFT), drag_point))
.Times(1);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
Eq(CastSideSwipeOrigin::LEFT), _))
.Times(kNumSteps);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::END,
Eq(CastSideSwipeOrigin::LEFT), end_point))
.Times(1);
ui::test::EventGenerator& generator = GetEventGenerator();
generator.GestureScrollSequence(drag_point, end_point, kTimeDelay, kNumSteps);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, test_event_handler().NumTouchEventsReceived());
}
TEST_F(SideSwipeDetectorTest, SwipeFromRight) {
gfx::Point drag_point(root_window()->bounds().width(),
root_window()->bounds().height() / 2);
auto end_point = drag_point - gfx::Vector2d(kSwipeDistance, 0);
EXPECT_CALL(mock_gesture_handler(),
CanHandleSwipe(Eq(CastSideSwipeOrigin::RIGHT)))
.WillRepeatedly(Return(true));
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::BEGIN,
Eq(CastSideSwipeOrigin::RIGHT), drag_point))
.Times(1);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
Eq(CastSideSwipeOrigin::RIGHT), _))
.Times(kNumSteps);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::END,
Eq(CastSideSwipeOrigin::RIGHT), end_point))
.Times(1);
ui::test::EventGenerator& generator = GetEventGenerator();
generator.GestureScrollSequence(drag_point, end_point, kTimeDelay, kNumSteps);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, test_event_handler().NumTouchEventsReceived());
}
TEST_F(SideSwipeDetectorTest, SwipeFromTop) {
gfx::Point drag_point(root_window()->bounds().width() / 2, 0);
auto end_point = drag_point + gfx::Vector2d(0, kSwipeDistance);
EXPECT_CALL(mock_gesture_handler(),
CanHandleSwipe(Eq(CastSideSwipeOrigin::TOP)))
.WillRepeatedly(Return(true));
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::BEGIN,
Eq(CastSideSwipeOrigin::TOP), drag_point))
.Times(1);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
Eq(CastSideSwipeOrigin::TOP), _))
.Times(kNumSteps);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::END,
Eq(CastSideSwipeOrigin::TOP), end_point))
.Times(1);
ui::test::EventGenerator& generator = GetEventGenerator();
generator.GestureScrollSequence(drag_point, end_point, kTimeDelay, kNumSteps);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, test_event_handler().NumTouchEventsReceived());
}
TEST_F(SideSwipeDetectorTest, SwipeFromBottom) {
gfx::Point drag_point(root_window()->bounds().width() / 2,
root_window()->bounds().height());
auto end_point = drag_point - gfx::Vector2d(0, kSwipeDistance);
EXPECT_CALL(mock_gesture_handler(),
CanHandleSwipe(Eq(CastSideSwipeOrigin::BOTTOM)))
.WillRepeatedly(Return(true));
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::BEGIN,
Eq(CastSideSwipeOrigin::BOTTOM), drag_point))
.Times(1);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
Eq(CastSideSwipeOrigin::BOTTOM), _))
.Times(kNumSteps);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::END,
Eq(CastSideSwipeOrigin::BOTTOM), end_point))
.Times(1);
ui::test::EventGenerator& generator = GetEventGenerator();
generator.GestureScrollSequence(drag_point, end_point, kTimeDelay, kNumSteps);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, test_event_handler().NumTouchEventsReceived());
}
TEST_F(SideSwipeDetectorTest, SwipeUnhandledIgnored) {
gfx::Point drag_point(root_window()->bounds().width() / 2,
root_window()->bounds().height());
auto end_point = drag_point - gfx::Vector2d(0, kSwipeDistance);
EXPECT_CALL(mock_gesture_handler(),
CanHandleSwipe(Eq(CastSideSwipeOrigin::BOTTOM)))
.WillRepeatedly(Return(false));
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::BEGIN,
Eq(CastSideSwipeOrigin::BOTTOM), drag_point))
.Times(0);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
Eq(CastSideSwipeOrigin::BOTTOM), _))
.Times(0);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::END,
Eq(CastSideSwipeOrigin::BOTTOM), end_point))
.Times(0);
ui::test::EventGenerator& generator = GetEventGenerator();
generator.GestureScrollSequence(drag_point, end_point, kTimeDelay, kNumSteps);
base::RunLoop().RunUntilIdle();
EXPECT_NE(0, test_event_handler().NumTouchEventsReceived());
}
// Test that a second gesture while the first is still in process will be
// ignored.
TEST_F(SideSwipeDetectorTest, IgnoreSecondFinger) {
gfx::Point drag_point(root_window()->bounds().width() / 2,
root_window()->bounds().height());
auto end_point = drag_point - gfx::Vector2d(0, kSwipeDistance);
EXPECT_CALL(mock_gesture_handler(),
CanHandleSwipe(Eq(CastSideSwipeOrigin::BOTTOM)))
.WillRepeatedly(Return(true));
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::BEGIN,
Eq(CastSideSwipeOrigin::BOTTOM), drag_point))
.Times(1);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
Eq(CastSideSwipeOrigin::BOTTOM), _))
.Times(1);
EXPECT_CALL(mock_gesture_handler(),
HandleSideSwipe(CastSideSwipeEvent::END,
Eq(CastSideSwipeOrigin::BOTTOM), end_point))
.Times(0);
// Start a drag but don't complete.
Drag(drag_point, base::Milliseconds(10) /*start_hold_time */,
base::Milliseconds(1000) /* drag_time */, end_point, 1 /* pointer_id */,
false /* end_release */);
// A second drag is started with another finger, but will be ignored as a
// swipe and all its events eaten.
Drag(drag_point, base::Milliseconds(10) /*start_hold_time */,
base::Milliseconds(1000) /* drag_time */, end_point, 2 /* pointer_id */,
true /* end_release */);
base::RunLoop().RunUntilIdle();
// There should be no events generated, even by the second finger.
EXPECT_EQ(0, test_event_handler().NumTouchEventsReceived());
}
} // namespace test
} // namespace chromecast