// 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 "ui/chromeos/touch_exploration_controller.h" #include "base/test/simple_test_tick_clock.h" #include "base/time/time.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/test/aura_test_base.h" #include "ui/aura/test/test_cursor_client.h" #include "ui/aura/window.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" #include "ui/events/gestures/gesture_provider_aura.h" #include "ui/events/test/event_generator.h" #include "ui/events/test/events_test_utils.h" #include "ui/gfx/geometry/point.h" #include "ui/gl/gl_implementation.h" #include "ui/gl/gl_surface.h" namespace ui { namespace { // Records all mouse, touch, gesture, and key events. class EventCapturer : public ui::EventHandler { public: EventCapturer() {} virtual ~EventCapturer() {} void Reset() { events_.clear(); } virtual void OnEvent(ui::Event* event) override { if (event->IsMouseEvent()) { events_.push_back( new ui::MouseEvent(static_cast(*event))); } else if (event->IsTouchEvent()) { events_.push_back( new ui::TouchEvent(static_cast(*event))); } else if (event->IsGestureEvent()) { events_.push_back( new ui::GestureEvent(static_cast(*event))); } else if (event->IsKeyEvent()) { events_.push_back(new ui::KeyEvent(static_cast(*event))); } else { return; } // Stop event propagation so we don't click on random stuff that // might break test assumptions. event->StopPropagation(); // If there is a possibility that we're in an infinite loop, we should // exit early with a sensible error rather than letting the test time out. ASSERT_LT(events_.size(), 100u); } const ScopedVector& captured_events() const { return events_; } private: ScopedVector events_; DISALLOW_COPY_AND_ASSIGN(EventCapturer); }; int Factorial(int n) { if (n <= 0) return 0; if (n == 1) return 1; return n * Factorial(n - 1); } class MockTouchExplorationControllerDelegate : public ui::TouchExplorationControllerDelegate { public: virtual void SetOutputLevel(int volume) override { volume_changes_.push_back(volume); } virtual void SilenceSpokenFeedback() override { } virtual void PlayVolumeAdjustEarcon() override { ++num_times_adjust_sound_played_; } virtual void PlayPassthroughEarcon() override { ++num_times_passthrough_played_; } virtual void PlayExitScreenEarcon() override { ++num_times_exit_screen_played_; } virtual void PlayEnterScreenEarcon() override { ++num_times_enter_screen_played_; } const std::vector VolumeChanges() { return volume_changes_; } size_t NumAdjustSounds() { return num_times_adjust_sound_played_; } size_t NumPassthroughSounds() { return num_times_passthrough_played_; } size_t NumExitScreenSounds() { return num_times_exit_screen_played_; } size_t NumEnterScreenSounds() { return num_times_enter_screen_played_; } void ResetCountersToZero() { num_times_adjust_sound_played_ = 0; num_times_passthrough_played_ = 0; num_times_exit_screen_played_ = 0; num_times_enter_screen_played_ = 0; } private: std::vector volume_changes_; size_t num_times_adjust_sound_played_ = 0; size_t num_times_passthrough_played_ = 0; size_t num_times_exit_screen_played_ = 0; size_t num_times_enter_screen_played_ = 0; }; } // namespace class TouchExplorationControllerTestApi { public: TouchExplorationControllerTestApi( TouchExplorationController* touch_exploration_controller) { touch_exploration_controller_.reset(touch_exploration_controller); } void CallTapTimerNowForTesting() { DCHECK(touch_exploration_controller_->tap_timer_.IsRunning()); touch_exploration_controller_->tap_timer_.Stop(); touch_exploration_controller_->OnTapTimerFired(); } void CallPassthroughTimerNowForTesting() { DCHECK(touch_exploration_controller_->passthrough_timer_.IsRunning()); touch_exploration_controller_->passthrough_timer_.Stop(); touch_exploration_controller_->OnPassthroughTimerFired(); } void CallTapTimerNowIfRunningForTesting() { if (touch_exploration_controller_->tap_timer_.IsRunning()) { touch_exploration_controller_->tap_timer_.Stop(); touch_exploration_controller_->OnTapTimerFired(); } } bool IsInNoFingersDownStateForTesting() const { return touch_exploration_controller_->state_ == touch_exploration_controller_->NO_FINGERS_DOWN; } bool IsInGestureInProgressStateForTesting() const { return touch_exploration_controller_->state_ == touch_exploration_controller_->GESTURE_IN_PROGRESS; } bool IsInSlideGestureStateForTesting() const { return touch_exploration_controller_->state_ == touch_exploration_controller_->SLIDE_GESTURE; } bool IsInTwoFingerTapStateForTesting() const { return touch_exploration_controller_->state_ == touch_exploration_controller_->TWO_FINGER_TAP; } bool IsInCornerPassthroughStateForTesting() const { return touch_exploration_controller_->state_ == touch_exploration_controller_->CORNER_PASSTHROUGH; } gfx::Rect BoundsOfRootWindowInDIPForTesting() const { return touch_exploration_controller_->root_window_->GetBoundsInScreen(); } // VLOGs should be suppressed in tests that generate a lot of logs, // for example permutations of nine touch events. void SuppressVLOGsForTesting(bool suppress) { touch_exploration_controller_->VLOG_on_ = !suppress; } float GetMaxDistanceFromEdge() const { return touch_exploration_controller_->kMaxDistanceFromEdge; } float GetSlopDistanceFromEdge() const { return touch_exploration_controller_->kSlopDistanceFromEdge; } void SetTickClockForTesting(base::TickClock* simulated_clock) { touch_exploration_controller_->tick_clock_ = simulated_clock; } private: scoped_ptr touch_exploration_controller_; DISALLOW_COPY_AND_ASSIGN(TouchExplorationControllerTestApi); }; class TouchExplorationTest : public aura::test::AuraTestBase { public: TouchExplorationTest() : simulated_clock_(new base::SimpleTestTickClock()) { // Tests fail if time is ever 0. simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); } virtual ~TouchExplorationTest() {} virtual void SetUp() override { if (gfx::GetGLImplementation() == gfx::kGLImplementationNone) gfx::GLSurface::InitializeOneOffForTests(); aura::test::AuraTestBase::SetUp(); cursor_client_.reset(new aura::test::TestCursorClient(root_window())); root_window()->AddPreTargetHandler(&event_capturer_); generator_.reset(new test::EventGenerator(root_window())); // The generator takes ownership of the tick clock. generator_->SetTickClock(scoped_ptr(simulated_clock_)); cursor_client()->ShowCursor(); cursor_client()->DisableMouseEvents(); } virtual void TearDown() override { root_window()->RemovePreTargetHandler(&event_capturer_); SwitchTouchExplorationMode(false); cursor_client_.reset(); aura::test::AuraTestBase::TearDown(); } protected: aura::client::CursorClient* cursor_client() { return cursor_client_.get(); } const ScopedVector& GetCapturedEvents() { return event_capturer_.captured_events(); } std::vector GetCapturedLocatedEvents() { const ScopedVector& all_events = GetCapturedEvents(); std::vector located_events; for (size_t i = 0; i < all_events.size(); ++i) { if (all_events[i]->IsMouseEvent() || all_events[i]->IsTouchEvent() || all_events[i]->IsGestureEvent()) { located_events.push_back(static_cast(all_events[i])); } } return located_events; } std::vector GetCapturedEventsOfType(int type) { const ScopedVector& all_events = GetCapturedEvents(); std::vector events; for (size_t i = 0; i < all_events.size(); ++i) { if (type == all_events[i]->type()) events.push_back(all_events[i]); } return events; } std::vector GetCapturedLocatedEventsOfType(int type) { std::vector located_events = GetCapturedLocatedEvents(); std::vector events; for (size_t i = 0; i < located_events.size(); ++i) { if (type == located_events[i]->type()) events.push_back(located_events[i]); } return events; } void ClearCapturedEvents() { event_capturer_.Reset(); } void AdvanceSimulatedTimePastTapDelay() { simulated_clock_->Advance(gesture_detector_config_.double_tap_timeout); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1)); touch_exploration_controller_->CallTapTimerNowForTesting(); } void AdvanceSimulatedTimePastPassthroughDelay() { simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); touch_exploration_controller_->CallPassthroughTimerNowForTesting(); } void AdvanceSimulatedTimePastPotentialTapDelay() { simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); touch_exploration_controller_->CallTapTimerNowIfRunningForTesting(); } void SuppressVLOGs(bool suppress) { touch_exploration_controller_->SuppressVLOGsForTesting(suppress); } void SetTickClock() { touch_exploration_controller_->SetTickClockForTesting( static_cast(simulated_clock_)); } void SwitchTouchExplorationMode(bool on) { if (!on && touch_exploration_controller_.get()) { touch_exploration_controller_.reset(); } else if (on && !touch_exploration_controller_.get()) { touch_exploration_controller_.reset( new ui::TouchExplorationControllerTestApi( new TouchExplorationController(root_window(), &delegate_))); cursor_client()->ShowCursor(); cursor_client()->DisableMouseEvents(); } } void EnterTouchExplorationModeAtLocation(gfx::Point tap_location) { ui::TouchEvent touch_press(ui::ET_TOUCH_PRESSED, tap_location, 0, Now()); generator_->Dispatch(&touch_press); AdvanceSimulatedTimePastTapDelay(); EXPECT_TRUE(IsInTouchToMouseMode()); } // Checks that Corner Passthrough is working. Assumes that corner is the // bottom left corner or the bottom right corner. void AssertCornerPassthroughWorking(gfx::Point corner) { ASSERT_EQ(0U, delegate_.NumPassthroughSounds()); ui::TouchEvent first_press(ui::ET_TOUCH_PRESSED, corner, 0, Now()); generator_->Dispatch(&first_press); AdvanceSimulatedTimePastPassthroughDelay(); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_TRUE(IsInCornerPassthroughState()); gfx::Rect window = BoundsOfRootWindowInDIP(); // The following events should be passed through. gfx::Point passthrough(window.right() / 2, window.bottom() / 2); ui::TouchEvent passthrough_press( ui::ET_TOUCH_PRESSED, passthrough, 1, Now()); ASSERT_EQ(1U, delegate_.NumPassthroughSounds()); generator_->Dispatch(&passthrough_press); generator_->ReleaseTouchId(1); generator_->PressTouchId(1); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); EXPECT_TRUE(IsInCornerPassthroughState()); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(3U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[2]->type()); generator_->ReleaseTouchId(1); ClearCapturedEvents(); generator_->ReleaseTouchId(0); captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(0U, captured_events.size()); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInCornerPassthroughState()); ClearCapturedEvents(); } bool IsInTouchToMouseMode() { aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(root_window()); return cursor_client && cursor_client->IsMouseEventsEnabled() && !cursor_client->IsCursorVisible(); } bool IsInNoFingersDownState() { return touch_exploration_controller_->IsInNoFingersDownStateForTesting(); } bool IsInGestureInProgressState() { return touch_exploration_controller_ ->IsInGestureInProgressStateForTesting(); } bool IsInSlideGestureState() { return touch_exploration_controller_->IsInSlideGestureStateForTesting(); } bool IsInTwoFingerTapState() { return touch_exploration_controller_->IsInTwoFingerTapStateForTesting(); } bool IsInCornerPassthroughState() { return touch_exploration_controller_ ->IsInCornerPassthroughStateForTesting(); } gfx::Rect BoundsOfRootWindowInDIP() { return touch_exploration_controller_->BoundsOfRootWindowInDIPForTesting(); } float GetMaxDistanceFromEdge() const { return touch_exploration_controller_->GetMaxDistanceFromEdge(); } float GetSlopDistanceFromEdge() const { return touch_exploration_controller_->GetSlopDistanceFromEdge(); } base::TimeDelta Now() { // This is the same as what EventTimeForNow() does, but here we do it // with our simulated clock. return base::TimeDelta::FromInternalValue( simulated_clock_->NowTicks().ToInternalValue()); } scoped_ptr generator_; ui::GestureDetector::Config gesture_detector_config_; // Owned by |generator_|. base::SimpleTestTickClock* simulated_clock_; MockTouchExplorationControllerDelegate delegate_; private: EventCapturer event_capturer_; scoped_ptr touch_exploration_controller_; scoped_ptr cursor_client_; DISALLOW_COPY_AND_ASSIGN(TouchExplorationTest); }; // Executes a number of assertions to confirm that |e1| and |e2| are touch // events and are equal to each other. void ConfirmEventsAreTouchAndEqual(ui::Event* e1, ui::Event* e2) { ASSERT_TRUE(e1->IsTouchEvent()); ASSERT_TRUE(e2->IsTouchEvent()); ui::TouchEvent* touch_event1 = static_cast(e1); ui::TouchEvent* touch_event2 = static_cast(e2); EXPECT_EQ(touch_event1->type(), touch_event2->type()); EXPECT_EQ(touch_event1->location(), touch_event2->location()); EXPECT_EQ(touch_event1->touch_id(), touch_event2->touch_id()); EXPECT_EQ(touch_event1->flags(), touch_event2->flags()); EXPECT_EQ(touch_event1->time_stamp(), touch_event2->time_stamp()); } // Executes a number of assertions to confirm that |e1| and |e2| are mouse // events and are equal to each other. void ConfirmEventsAreMouseAndEqual(ui::Event* e1, ui::Event* e2) { ASSERT_TRUE(e1->IsMouseEvent()); ASSERT_TRUE(e2->IsMouseEvent()); ui::MouseEvent* mouse_event1 = static_cast(e1); ui::MouseEvent* mouse_event2 = static_cast(e2); EXPECT_EQ(mouse_event1->type(), mouse_event2->type()); EXPECT_EQ(mouse_event1->location(), mouse_event2->location()); EXPECT_EQ(mouse_event1->root_location(), mouse_event2->root_location()); EXPECT_EQ(mouse_event1->flags(), mouse_event2->flags()); } // Executes a number of assertions to confirm that |e1| and |e2| are key events // and are equal to each other. void ConfirmEventsAreKeyAndEqual(ui::Event* e1, ui::Event* e2) { ASSERT_TRUE(e1->IsKeyEvent()); ASSERT_TRUE(e2->IsKeyEvent()); ui::KeyEvent* key_event1 = static_cast(e1); ui::KeyEvent* key_event2 = static_cast(e2); EXPECT_EQ(key_event1->type(), key_event2->type()); EXPECT_EQ(key_event1->key_code(), key_event2->key_code()); EXPECT_EQ(key_event1->code(), key_event2->code()); EXPECT_EQ(key_event1->flags(), key_event2->flags()); } #define CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(e1, e2) \ ASSERT_NO_FATAL_FAILURE(ConfirmEventsAreTouchAndEqual(e1, e2)) #define CONFIRM_EVENTS_ARE_MOUSE_AND_EQUAL(e1, e2) \ ASSERT_NO_FATAL_FAILURE(ConfirmEventsAreMouseAndEqual(e1, e2)) #define CONFIRM_EVENTS_ARE_KEY_AND_EQUAL(e1, e2) \ ASSERT_NO_FATAL_FAILURE(ConfirmEventsAreKeyAndEqual(e1, e2)) // TODO(mfomitchev): Need to investigate why we don't get mouse enter/exit // events when running these tests as part of ui_base_unittests. We do get them // when the tests are run as part of ash unit tests. // If a swipe has been successfully completed, then six key events will be // dispatched that correspond to shift+search+direction void AssertDirectionalNavigationEvents(const ScopedVector& events, ui::KeyboardCode direction) { ASSERT_EQ(6U, events.size()); ui::KeyEvent shift_pressed( ui::ET_KEY_PRESSED, ui::VKEY_SHIFT, ui::EF_SHIFT_DOWN); ui::KeyEvent search_pressed( ui::ET_KEY_PRESSED, ui::VKEY_LWIN, ui::EF_SHIFT_DOWN); ui::KeyEvent direction_pressed( ui::ET_KEY_PRESSED, direction, ui::EF_SHIFT_DOWN); ui::KeyEvent direction_released( ui::ET_KEY_RELEASED, direction, ui::EF_SHIFT_DOWN); ui::KeyEvent search_released( ui::ET_KEY_RELEASED, VKEY_LWIN, ui::EF_SHIFT_DOWN); ui::KeyEvent shift_released( ui::ET_KEY_RELEASED, ui::VKEY_SHIFT, ui::EF_NONE); CONFIRM_EVENTS_ARE_KEY_AND_EQUAL(&shift_pressed, events[0]); CONFIRM_EVENTS_ARE_KEY_AND_EQUAL(&search_pressed, events[1]); CONFIRM_EVENTS_ARE_KEY_AND_EQUAL(&direction_pressed, events[2]); CONFIRM_EVENTS_ARE_KEY_AND_EQUAL(&direction_released, events[3]); CONFIRM_EVENTS_ARE_KEY_AND_EQUAL(&search_released, events[4]); CONFIRM_EVENTS_ARE_KEY_AND_EQUAL(&shift_released, events[5]); } TEST_F(TouchExplorationTest, EntersTouchToMouseModeAfterPressAndDelay) { SwitchTouchExplorationMode(true); EXPECT_FALSE(IsInTouchToMouseMode()); generator_->PressTouch(); AdvanceSimulatedTimePastTapDelay(); EXPECT_TRUE(IsInTouchToMouseMode()); } TEST_F(TouchExplorationTest, EntersTouchToMouseModeAfterMoveOutsideSlop) { int slop = gesture_detector_config_.touch_slop; int half_slop = slop / 2; SwitchTouchExplorationMode(true); EXPECT_FALSE(IsInTouchToMouseMode()); generator_->set_current_location(gfx::Point(11, 12)); generator_->PressTouch(); generator_->MoveTouch(gfx::Point(11 + half_slop, 12)); EXPECT_FALSE(IsInTouchToMouseMode()); generator_->MoveTouch(gfx::Point(11, 12 + half_slop)); EXPECT_FALSE(IsInTouchToMouseMode()); AdvanceSimulatedTimePastTapDelay(); generator_->MoveTouch(gfx::Point(11 + slop + 1, 12)); EXPECT_TRUE(IsInTouchToMouseMode()); } TEST_F(TouchExplorationTest, OneFingerTap) { SwitchTouchExplorationMode(true); gfx::Point location(11, 12); generator_->set_current_location(location); generator_->PressTouch(); generator_->ReleaseTouch(); AdvanceSimulatedTimePastTapDelay(); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); EXPECT_EQ(location, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); EXPECT_TRUE(IsInNoFingersDownState()); } TEST_F(TouchExplorationTest, ActualMouseMovesUnaffected) { SwitchTouchExplorationMode(true); gfx::Point location_start(11, 12); gfx::Point location_end(13, 14); generator_->set_current_location(location_start); generator_->PressTouch(); AdvanceSimulatedTimePastTapDelay(); generator_->MoveTouch(location_end); gfx::Point location_real_mouse_move(15, 16); ui::MouseEvent mouse_move(ui::ET_MOUSE_MOVED, location_real_mouse_move, location_real_mouse_move, 0, 0); generator_->Dispatch(&mouse_move); generator_->ReleaseTouch(); AdvanceSimulatedTimePastTapDelay(); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(4U, events.size()); EXPECT_EQ(location_start, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); EXPECT_EQ(location_end, events[1]->location()); EXPECT_TRUE(events[1]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); // The real mouse move goes through. EXPECT_EQ(location_real_mouse_move, events[2]->location()); CONFIRM_EVENTS_ARE_MOUSE_AND_EQUAL(events[2], &mouse_move); EXPECT_FALSE(events[2]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_FALSE(events[2]->flags() & ui::EF_TOUCH_ACCESSIBILITY); // The touch release gets written as a mouse move. EXPECT_EQ(location_end, events[3]->location()); EXPECT_TRUE(events[3]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[3]->flags() & ui::EF_TOUCH_ACCESSIBILITY); EXPECT_TRUE(IsInNoFingersDownState()); } // Turn the touch exploration mode on in the middle of the touch gesture. // Confirm that events from the finger which was touching when the mode was // turned on don't get rewritten. TEST_F(TouchExplorationTest, TurnOnMidTouch) { SwitchTouchExplorationMode(false); generator_->PressTouchId(1); EXPECT_TRUE(cursor_client()->IsCursorVisible()); ClearCapturedEvents(); // Enable touch exploration mode while the first finger is touching the // screen. Ensure that subsequent events from that first finger are not // affected by the touch exploration mode, while the touch events from another // finger get rewritten. SwitchTouchExplorationMode(true); ui::TouchEvent touch_move(ui::ET_TOUCH_MOVED, gfx::Point(11, 12), 1, Now()); generator_->Dispatch(&touch_move); EXPECT_TRUE(cursor_client()->IsCursorVisible()); EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled()); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(1u, captured_events.size()); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_move); ClearCapturedEvents(); // The press from the second finger should get rewritten. generator_->PressTouchId(2); AdvanceSimulatedTimePastTapDelay(); EXPECT_TRUE(IsInTouchToMouseMode()); captured_events = GetCapturedLocatedEvents(); std::vector::const_iterator it; for (it = captured_events.begin(); it != captured_events.end(); ++it) { if ((*it)->type() == ui::ET_MOUSE_MOVED) break; } EXPECT_NE(captured_events.end(), it); ClearCapturedEvents(); // The release of the first finger shouldn't be affected. ui::TouchEvent touch_release(ui::ET_TOUCH_RELEASED, gfx::Point(11, 12), 1, Now()); generator_->Dispatch(&touch_release); captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(1u, captured_events.size()); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_release); ClearCapturedEvents(); // The move and release from the second finger should get rewritten. generator_->MoveTouchId(gfx::Point(13, 14), 2); generator_->ReleaseTouchId(2); AdvanceSimulatedTimePastTapDelay(); captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2u, captured_events.size()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[0]->type()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type()); EXPECT_TRUE(IsInNoFingersDownState()); } // If an event is received after the double-tap timeout has elapsed, but // before the timer has fired, a mouse move should still be generated. TEST_F(TouchExplorationTest, TimerFiresLateDuringTouchExploration) { SwitchTouchExplorationMode(true); // Make sure the touch is not in a corner of the screen. generator_->MoveTouch(gfx::Point(100, 200)); // Send a press, then add another finger after the double-tap timeout. generator_->PressTouchId(1); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); generator_->PressTouchId(2); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); generator_->ReleaseTouchId(2); generator_->ReleaseTouchId(1); AdvanceSimulatedTimePastTapDelay(); EXPECT_TRUE(IsInNoFingersDownState()); } // If a new tap is received after the double-tap timeout has elapsed from // a previous tap, but before the timer has fired, a mouse move should // still be generated from the old tap. TEST_F(TouchExplorationTest, TimerFiresLateAfterTap) { SwitchTouchExplorationMode(true); // Send a tap at location1. gfx::Point location0(11, 12); generator_->set_current_location(location0); generator_->PressTouch(); generator_->ReleaseTouch(); // Send a tap at location2, after the double-tap timeout, but before the // timer fires. gfx::Point location1(33, 34); generator_->set_current_location(location1); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(301)); generator_->PressTouch(); generator_->ReleaseTouch(); AdvanceSimulatedTimePastTapDelay(); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(2U, events.size()); EXPECT_EQ(location0, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); EXPECT_EQ(location1, events[1]->location()); EXPECT_TRUE(events[1]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); EXPECT_TRUE(IsInNoFingersDownState()); } // Double-tapping should send a touch press and release through to the location // of the last successful touch exploration. TEST_F(TouchExplorationTest, DoubleTap) { SwitchTouchExplorationMode(true); // Tap at one location, and get a mouse move event. gfx::Point tap_location(51, 52); generator_->set_current_location(tap_location); generator_->PressTouchId(1); generator_->ReleaseTouchId(1); AdvanceSimulatedTimePastTapDelay(); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); EXPECT_EQ(tap_location, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); ClearCapturedEvents(); // Now double-tap at a different location. This should result in // a single touch press and release at the location of the tap, // not at the location of the double-tap. gfx::Point double_tap_location(33, 34); generator_->set_current_location(double_tap_location); generator_->PressTouch(); generator_->ReleaseTouch(); generator_->PressTouch(); generator_->ReleaseTouch(); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(tap_location, captured_events[0]->location()); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); EXPECT_EQ(tap_location, captured_events[1]->location()); EXPECT_TRUE(IsInNoFingersDownState()); } // Double-tapping where the user holds their finger down for the second time // for a longer press should send a touch press and passthrough all further // events from that finger. Other finger presses should be ignored. TEST_F(TouchExplorationTest, DoubleTapPassthrough) { SwitchTouchExplorationMode(true); // Tap at one location, and get a mouse move event. gfx::Point tap_location(11, 12); generator_->set_current_location(tap_location); generator_->PressTouch(); generator_->ReleaseTouch(); AdvanceSimulatedTimePastTapDelay(); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); EXPECT_EQ(tap_location, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); ClearCapturedEvents(); // Now double-tap and hold at a different location. // This should result in a single touch press at the location of the tap, // not at the location of the double-tap. gfx::Point first_tap_location(13, 14); generator_->set_current_location(first_tap_location); generator_->PressTouchId(1); generator_->ReleaseTouchId(1); gfx::Point second_tap_location(15, 16); generator_->set_current_location(second_tap_location); generator_->PressTouchId(1); // Advance to the finger passing through. AdvanceSimulatedTimePastTapDelay(); gfx::Vector2d passthrough_offset = second_tap_location - tap_location; std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(1U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(second_tap_location - passthrough_offset, captured_events[0]->location()); ClearCapturedEvents(); // All events for the first finger should pass through now, displaced // relative to the last touch exploration location. gfx::Point first_move_location(17, 18); generator_->MoveTouchId(first_move_location, 1); gfx::Point second_move_location(12, 13); generator_->MoveTouchId(second_move_location, 1); captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_MOVED, captured_events[0]->type()); EXPECT_EQ(first_move_location - passthrough_offset, captured_events[0]->location()); EXPECT_EQ(ui::ET_TOUCH_MOVED, captured_events[1]->type()); EXPECT_EQ(second_move_location - passthrough_offset, captured_events[1]->location()); ClearCapturedEvents(); // Events for other fingers should do nothing. generator_->PressTouchId(2); generator_->PressTouchId(3); generator_->MoveTouchId(gfx::Point(34, 36), 2); generator_->ReleaseTouchId(2); captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(0U, captured_events.size()); // Even with finger 3 still down, events for the first finger should still // pass through. gfx::Point third_move_location(14, 15); generator_->MoveTouchId(third_move_location, 1); captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(1U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_MOVED, captured_events[0]->type()); EXPECT_EQ(third_move_location - passthrough_offset, captured_events[0]->location()); // No fingers down state is only reached when every finger is lifted. generator_->ReleaseTouchId(1); EXPECT_FALSE(IsInNoFingersDownState()); generator_->ReleaseTouchId(3); EXPECT_TRUE(IsInNoFingersDownState()); } // Double-tapping, going into passthrough, and holding for the longpress // time should send a touch press and released (right click) // to the location of the last successful touch exploration. TEST_F(TouchExplorationTest, DoubleTapLongPress) { SwitchTouchExplorationMode(true); SetTickClock(); // Tap at one location, and get a mouse move event. gfx::Point tap_location(11, 12); generator_->set_current_location(tap_location); generator_->PressTouch(); generator_->ReleaseTouch(); AdvanceSimulatedTimePastTapDelay(); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); EXPECT_EQ(tap_location, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); ClearCapturedEvents(); // Now double-tap and hold at a different location. // This should result in a single touch long press and release // at the location of the tap, not at the location of the double-tap. // There should be a time delay between the touch press and release. gfx::Point first_tap_location(33, 34); generator_->set_current_location(first_tap_location); generator_->PressTouch(); generator_->ReleaseTouch(); gfx::Point second_tap_location(23, 24); generator_->set_current_location(second_tap_location); generator_->PressTouch(); // Advance to the finger passing through, and then to the longpress timeout. AdvanceSimulatedTimePastTapDelay(); simulated_clock_->Advance(gesture_detector_config_.longpress_timeout); generator_->ReleaseTouch(); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(tap_location, captured_events[0]->location()); base::TimeDelta pressed_time = captured_events[0]->time_stamp(); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); EXPECT_EQ(tap_location, captured_events[1]->location()); base::TimeDelta released_time = captured_events[1]->time_stamp(); EXPECT_EQ(released_time - pressed_time, gesture_detector_config_.longpress_timeout); } // Single-tapping should send a touch press and release through to the location // of the last successful touch exploration if the grace period has not // elapsed. TEST_F(TouchExplorationTest, SingleTap) { SwitchTouchExplorationMode(true); // Tap once to simulate a mouse moved event. gfx::Point initial_location(11, 12); generator_->set_current_location(initial_location); generator_->PressTouch(); AdvanceSimulatedTimePastTapDelay(); ClearCapturedEvents(); // Move to another location for single tap gfx::Point tap_location(22, 23); generator_->MoveTouch(tap_location); generator_->ReleaseTouch(); // Allow time to pass within the grace period of releasing before // tapping again. gfx::Point final_location(33, 34); generator_->set_current_location(final_location); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(250)); generator_->PressTouch(); generator_->ReleaseTouch(); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(4U, captured_events.size()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[0]->type()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[2]->type()); EXPECT_EQ(tap_location, captured_events[2]->location()); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[3]->type()); EXPECT_EQ(tap_location, captured_events[3]->location()); } // Double-tapping without coming from touch exploration (no previous touch // exploration event) should not generate any events. TEST_F(TouchExplorationTest, DoubleTapNoTouchExplore) { SwitchTouchExplorationMode(true); // Double-tap without any previous touch. // Touch exploration mode has not been entered, so there is no previous // touch exploration event. The double-tap should be discarded, and no events // should be generated at all. gfx::Point double_tap_location(33, 34); generator_->set_current_location(double_tap_location); generator_->PressTouch(); generator_->ReleaseTouch(); generator_->PressTouch(); // Since the state stays in single_tap_released, we need to make sure the // tap timer doesn't fire and set the state to no fingers down (since there // is still a finger down). AdvanceSimulatedTimePastPotentialTapDelay(); EXPECT_FALSE(IsInNoFingersDownState()); generator_->ReleaseTouch(); EXPECT_TRUE(IsInNoFingersDownState()); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(0U, captured_events.size()); } // Tapping and releasing with a second finger when in touch exploration mode // should send a touch press and released to the location of the last // successful touch exploration and return to touch explore. TEST_F(TouchExplorationTest, SplitTap) { SwitchTouchExplorationMode(true); gfx::Point initial_touch_location(11, 12); gfx::Point second_touch_location(33, 34); // Tap and hold at one location, and get a mouse move event in touch explore. EnterTouchExplorationModeAtLocation(initial_touch_location); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); EXPECT_EQ(initial_touch_location, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); ClearCapturedEvents(); EXPECT_TRUE(IsInTouchToMouseMode()); // Now tap and release at a different location. This should result in a // single touch and release at the location of the first (held) tap, // not at the location of the second tap and release. // After the release, there is still a finger in touch explore mode. ui::TouchEvent split_tap_press( ui::ET_TOUCH_PRESSED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_press); // To simulate the behavior of the real device, we manually disable // mouse events. To not rely on manually setting the state, this is also // tested in touch_exploration_controller_browsertest. cursor_client()->DisableMouseEvents(); EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled()); EXPECT_FALSE(cursor_client()->IsCursorVisible()); EXPECT_FALSE(IsInGestureInProgressState()); ui::TouchEvent split_tap_release( ui::ET_TOUCH_RELEASED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_release); // Releasing the second finger should re-enable mouse events putting us // back into the touch exploration mode. EXPECT_TRUE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInNoFingersDownState()); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(initial_touch_location, captured_events[0]->location()); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); EXPECT_EQ(initial_touch_location, captured_events[1]->location()); ClearCapturedEvents(); ui::TouchEvent touch_explore_release( ui::ET_TOUCH_RELEASED, initial_touch_location, 0, Now()); generator_->Dispatch(&touch_explore_release); AdvanceSimulatedTimePastTapDelay(); EXPECT_TRUE(IsInNoFingersDownState()); } // If split tap is started but the touch explore finger is released first, // there should still be a touch press and release sent to the location of // the last successful touch exploration. // Both fingers should be released after the click goes through. TEST_F(TouchExplorationTest, SplitTapRelease) { SwitchTouchExplorationMode(true); gfx::Point initial_touch_location(11, 12); gfx::Point second_touch_location(33, 34); // Tap and hold at one location, and get a mouse move event in touch explore. EnterTouchExplorationModeAtLocation(initial_touch_location); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); ClearCapturedEvents(); // Now tap at a different location. Release at the first location, // then release at the second. This should result in a // single touch and release at the location of the first (held) tap, // not at the location of the second tap and release. ui::TouchEvent split_tap_press( ui::ET_TOUCH_PRESSED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_press); ui::TouchEvent touch_explore_release( ui::ET_TOUCH_RELEASED, initial_touch_location, 0, Now()); generator_->Dispatch(&touch_explore_release); ui::TouchEvent split_tap_release( ui::ET_TOUCH_RELEASED, second_touch_location , 1, Now()); generator_->Dispatch(&split_tap_release); EXPECT_TRUE(IsInNoFingersDownState()); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(initial_touch_location, captured_events[0]->location()); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); EXPECT_EQ(initial_touch_location, captured_events[1]->location()); } // When in touch exploration mode, making a long press with a second finger // should send a touch press and released to the location of the last // successful touch exploration. There should be a delay between the // touch and release events (right click). TEST_F(TouchExplorationTest, SplitTapLongPress) { SwitchTouchExplorationMode(true); gfx::Point initial_touch_location(11, 12); gfx::Point second_touch_location(33, 34); // Tap and hold at one location, and get a mouse move event in touch explore. EnterTouchExplorationModeAtLocation(initial_touch_location); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); ClearCapturedEvents(); // Now tap, hold, and release at a different location. This should result // in a single touch and release (long press) at the location of the first // (held) tap, not at the location of the second tap and release. ui::TouchEvent split_tap_press( ui::ET_TOUCH_PRESSED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_press); // To simulate the behavior of the real device, we manually disable // mouse events, like in the SplitTap test. cursor_client()->DisableMouseEvents(); EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled()); EXPECT_FALSE(cursor_client()->IsCursorVisible()); EXPECT_FALSE(IsInGestureInProgressState()); simulated_clock_->Advance(gesture_detector_config_.longpress_timeout); // After the release, there is still a finger in touch exploration, and // mouse events should be enabled again. ui::TouchEvent split_tap_release( ui::ET_TOUCH_RELEASED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_release); EXPECT_FALSE(IsInNoFingersDownState()); EXPECT_TRUE(IsInTouchToMouseMode()); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(initial_touch_location, captured_events[0]->location()); base::TimeDelta pressed_time = captured_events[0]->time_stamp(); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); EXPECT_EQ(initial_touch_location, captured_events[1]->location()); base::TimeDelta released_time = captured_events[1]->time_stamp(); EXPECT_EQ(gesture_detector_config_.longpress_timeout, released_time - pressed_time); } // If split tap is started but the touch explore finger is released first, // there should still be a touch press and release sent to the location of // the last successful touch exploration. If the remaining finger is held // as a longpress, there should be a delay between the sent touch and release // events (right click).All fingers should be released after the click // goes through. TEST_F(TouchExplorationTest, SplitTapReleaseLongPress) { SwitchTouchExplorationMode(true); gfx::Point initial_touch_location(11, 12); gfx::Point second_touch_location(33, 34); // Tap and hold at one location, and get a mouse move event in touch explore. EnterTouchExplorationModeAtLocation(initial_touch_location); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); ClearCapturedEvents(); // Now tap at a different location. Release at the first location, // then release at the second. This should result in a // single touch and release at the location of the first (held) tap, // not at the location of the second tap and release. // After the release, TouchToMouseMode should still be on. ui::TouchEvent split_tap_press( ui::ET_TOUCH_PRESSED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_press); ui::TouchEvent touch_explore_release( ui::ET_TOUCH_RELEASED, initial_touch_location, 0, Now()); generator_->Dispatch(&touch_explore_release); simulated_clock_->Advance(gesture_detector_config_.longpress_timeout); ui::TouchEvent split_tap_release( ui::ET_TOUCH_RELEASED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_release); EXPECT_TRUE(IsInTouchToMouseMode()); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(initial_touch_location, captured_events[0]->location()); base::TimeDelta pressed_time = captured_events[0]->time_stamp(); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); EXPECT_EQ(initial_touch_location, captured_events[1]->location()); base::TimeDelta released_time = captured_events[1]->time_stamp(); EXPECT_EQ(gesture_detector_config_.longpress_timeout, released_time - pressed_time); } TEST_F(TouchExplorationTest, SplitTapMultiFinger) { SwitchTouchExplorationMode(true); gfx::Point initial_touch_location(11, 12); gfx::Point second_touch_location(33, 34); gfx::Point third_touch_location(16, 17); // Tap and hold at one location, and get a mouse move event in touch explore. EnterTouchExplorationModeAtLocation(initial_touch_location); std::vector events = GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); ASSERT_EQ(1U, events.size()); EXPECT_EQ(initial_touch_location, events[0]->location()); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); ClearCapturedEvents(); // Now tap at a different location and hold for long press. ui::TouchEvent split_tap_press( ui::ET_TOUCH_PRESSED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_press); simulated_clock_->Advance(gesture_detector_config_.longpress_timeout); // Placing a third finger on the screen should cancel the initial press and // enter the wait state. ui::TouchEvent third_press( ui::ET_TOUCH_PRESSED, third_touch_location, 2, Now()); generator_->Dispatch(&third_press); // When all three fingers are released, the only events captured should be a // press and touch cancel. All fingers should then be up. ui::TouchEvent touch_explore_release( ui::ET_TOUCH_RELEASED, initial_touch_location, 0, Now()); generator_->Dispatch(&touch_explore_release); ui::TouchEvent split_tap_release( ui::ET_TOUCH_RELEASED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_release); ui::TouchEvent third_tap_release( ui::ET_TOUCH_RELEASED, third_touch_location, 2, Now()); generator_->Dispatch(&third_tap_release); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(initial_touch_location, captured_events[0]->location()); EXPECT_EQ(ui::ET_TOUCH_CANCELLED, captured_events[1]->type()); EXPECT_EQ(initial_touch_location, captured_events[1]->location()); EXPECT_TRUE(IsInNoFingersDownState()); } TEST_F(TouchExplorationTest, SplitTapLeaveSlop) { SwitchTouchExplorationMode(true); gfx::Point first_touch_location(11, 12); gfx::Point second_touch_location(33, 34); gfx::Point first_move_location( first_touch_location.x() + gesture_detector_config_.touch_slop * 3 + 1, first_touch_location.y()); gfx::Point second_move_location( second_touch_location.x() + gesture_detector_config_.touch_slop * 3 + 1, second_touch_location.y()); // Tap and hold at one location, and get a mouse move event in touch explore. EnterTouchExplorationModeAtLocation(first_touch_location); ClearCapturedEvents(); // Now tap at a different location for split tap. ui::TouchEvent split_tap_press( ui::ET_TOUCH_PRESSED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_press); // Move the first finger out of slop and release both fingers. The split // tap should have been cancelled, so a touch press and touch cancel event // should go through at the last touch exploration location (the first press). ui::TouchEvent first_touch_move( ui::ET_TOUCH_MOVED, first_move_location, 0, Now()); generator_->Dispatch(&first_touch_move); ui::TouchEvent first_touch_release( ui::ET_TOUCH_RELEASED, first_move_location, 0, Now()); generator_->Dispatch(&first_touch_release); ui::TouchEvent second_touch_release( ui::ET_TOUCH_RELEASED, second_touch_location, 1, Now()); generator_->Dispatch(&second_touch_release); std::vector captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(first_touch_location, captured_events[0]->location()); EXPECT_EQ(ui::ET_TOUCH_CANCELLED, captured_events[1]->type()); EXPECT_EQ(first_touch_location, captured_events[1]->location()); EXPECT_TRUE(IsInNoFingersDownState()); // Now do the same, but moving the split tap finger out of slop EnterTouchExplorationModeAtLocation(first_touch_location); ClearCapturedEvents(); ui::TouchEvent split_tap_press2( ui::ET_TOUCH_PRESSED, second_touch_location, 1, Now()); generator_->Dispatch(&split_tap_press2); // Move the second finger out of slop and release both fingers. The split // tap should have been cancelled, so a touch press and touch cancel event // should go through at the last touch exploration location (the first press). ui::TouchEvent second_touch_move2( ui::ET_TOUCH_MOVED, second_move_location, 1, Now()); generator_->Dispatch(&second_touch_move2); ui::TouchEvent first_touch_release2( ui::ET_TOUCH_RELEASED, first_touch_location, 0, Now()); generator_->Dispatch(&first_touch_release2); ui::TouchEvent second_touch_release2( ui::ET_TOUCH_RELEASED, second_move_location, 1, Now()); generator_->Dispatch(&second_touch_release2); captured_events = GetCapturedLocatedEvents(); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type()); EXPECT_EQ(first_touch_location, captured_events[0]->location()); EXPECT_EQ(ui::ET_TOUCH_CANCELLED, captured_events[1]->type()); EXPECT_EQ(first_touch_location, captured_events[1]->location()); EXPECT_TRUE(IsInNoFingersDownState()); } // Finger must have moved more than slop, faster than the minimum swipe // velocity, and before the tap timer fires in order to enter // GestureInProgress state. Otherwise, if the tap timer fires before the a // gesture is completed, enter touch exploration. TEST_F(TouchExplorationTest, EnterGestureInProgressState) { SwitchTouchExplorationMode(true); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); float distance = gesture_detector_config_.touch_slop + 1; ui::TouchEvent first_press(ui::ET_TOUCH_PRESSED, gfx::Point(0, 1), 0, Now()); gfx::Point second_location(distance / 2, 1); gfx::Point third_location(distance, 1); gfx::Point touch_exploration_location(20, 21); generator_->Dispatch(&first_press); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); // Since we are not out of the touch slop yet, we should not be in gesture in // progress. generator_->MoveTouch(second_location); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); // Once we are out of slop, we should be in GestureInProgress. generator_->MoveTouch(third_location); EXPECT_TRUE(IsInGestureInProgressState()); EXPECT_FALSE(IsInTouchToMouseMode()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); // Exit out of gesture mode once grace period is over and enter touch // exploration. There should be a move when entering touch exploration and // also for the touch move. AdvanceSimulatedTimePastTapDelay(); generator_->MoveTouch(touch_exploration_location); ASSERT_EQ(2U, captured_events.size()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[0]->type()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type()); EXPECT_TRUE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); } // A swipe+direction gesture should trigger a Shift+Search+Direction // keyboard event. TEST_F(TouchExplorationTest, GestureSwipe) { SwitchTouchExplorationMode(true); std::vector directions; directions.push_back(ui::VKEY_RIGHT); directions.push_back(ui::VKEY_LEFT); directions.push_back(ui::VKEY_UP); directions.push_back(ui::VKEY_DOWN); // This value was taken from gesture_recognizer_unittest.cc in a swipe // detector test, since it seems to be about the right amount to get a swipe. const int kSteps = 15; // There are gestures supported with up to four fingers. for (int num_fingers = 1; num_fingers <= 4; num_fingers++) { std::vector start_points; for (int j = 0; j < num_fingers; j++) { start_points.push_back(gfx::Point(j * 10 + 100, j * 10 + 200)); } gfx::Point* start_points_array = &start_points[0]; const float distance = gesture_detector_config_.touch_slop + 1; // Iterate through each swipe direction for this number of fingers. for (std::vector::const_iterator it = directions.begin(); it != directions.end(); ++it) { int move_x = 0; int move_y = 0; ui::KeyboardCode direction = *it; switch (direction) { case ui::VKEY_RIGHT: move_x = distance; break; case ui::VKEY_LEFT: move_x = 0 - distance; break; case ui::VKEY_UP: move_y = 0 - distance; break; case ui::VKEY_DOWN: move_y = distance; break; default: return; } // A swipe is made when a fling starts float delta_time = distance / gesture_detector_config_.maximum_fling_velocity; // delta_time is in seconds, so we convert to ms. int delta_time_ms = floor(delta_time * 1000); generator_->GestureMultiFingerScroll(num_fingers, start_points_array, delta_time_ms, kSteps, move_x * 2, move_y * 2); // The swipe registered and sent the appropriate key events. const ScopedVector& captured_events = GetCapturedEvents(); if (num_fingers == 1) AssertDirectionalNavigationEvents(captured_events, direction); else { // Most of the time this is 2 right now, but two of the two finger // swipes are mapped to chromevox commands which dispatch 6 key events, // and these will probably be remapped a lot as we're developing. ASSERT_GE(captured_events.size(), 2U); std::vector::size_type i; for (i = 0; i != captured_events.size(); i++) { EXPECT_TRUE(captured_events[i]->IsKeyEvent()); } } EXPECT_TRUE(IsInNoFingersDownState()); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); ClearCapturedEvents(); } } } // Since there are so many permutations, this test is fairly slow. Therefore, it // is disabled and will be turned on to check during development. TEST_F(TouchExplorationTest, DISABLED_AllFingerPermutations) { SwitchTouchExplorationMode(true); SuppressVLOGs(true); // We will test all permutations of events from three different fingers // to ensure that we return to NO_FINGERS_DOWN when fingers have been // released. ScopedVector all_events; for (int touch_id = 0; touch_id < 3; touch_id++){ int x = 10*touch_id + 1; int y = 10*touch_id + 2; all_events.push_back(new TouchEvent( ui::ET_TOUCH_PRESSED, gfx::Point(x++, y++), touch_id, Now())); all_events.push_back(new TouchEvent( ui::ET_TOUCH_MOVED, gfx::Point(x++, y++), touch_id, Now())); all_events.push_back(new TouchEvent( ui::ET_TOUCH_RELEASED, gfx::Point(x, y), touch_id, Now())); } // I'm going to explain this algorithm, and use an example in parentheses. // The example will be all permutations of a b c d. // There are four letters and 4! = 24 permutations. const int num_events = all_events.size(); const int num_permutations = Factorial(num_events); for (int p = 0; p < num_permutations; p++) { std::vector queued_events = all_events.get(); std::vector fingers_pressed(3, false); int current_num_permutations = num_permutations; for (int events_left = num_events; events_left > 0; events_left--) { // |p| indexes to each permutation when there are num_permutations // permutations. (e.g. 0 is abcd, 1 is abdc, 2 is acbd, 3 is acdb...) // But how do we find the index for the current number of permutations? // To find the permutation within the part of the sequence we're // currently looking at, we need a number between 0 and // |current_num_permutations| - 1. // (e.g. if we already chose the first letter, there are 3! = 6 // options left, so we do p % 6. So |current_permutation| would go // from 0 to 5 and then reset to 0 again, for all combinations of // whichever three letters are remaining, as we loop through the // permutations) int current_permutation = p % current_num_permutations; // Since this is is the total number of permutations starting with // this event and including future events, there could be multiple // values of current_permutation that will generate the same event // in this iteration. // (e.g. If we chose 'a' but have b c d to choose from, we choose b when // |current_permutation| = 0, 1 and c when |current_permutation| = 2, 3. // Note that each letter gets two numbers, which is the next // current_num_permutations, 2! for the two letters left.) // Branching out from the first event, there are num_permutations // permutations, and each value of |p| is associated with one of these // permutations. However, once the first event is chosen, there // are now |num_events| - 1 events left, so the number of permutations // for the rest of the events changes, and will always be equal to // the factorial of the events_left. // (e.g. There are 3! = 6 permutations that start with 'a', so if we // start with 'a' there will be 6 ways to then choose from b c d.) // So we now set-up for the next iteration by setting // current_num_permutations to the factorial of the next number of // events left. current_num_permutations /= events_left; // To figure out what current event we want to choose, we integer // divide the current permutation by the next current_num_permutations. // (e.g. If there are 4 letters a b c d and 24 permutations, we divide // by 24/4 = 6. Values 0 to 5 when divided by 6 equals 0, so the first // 6 permutations start with 'a', and the last 6 will start with 'd'. // Note that there are 6 that start with 'a' because there are 6 // permutations for the next three letters that follow 'a'.) int index = current_permutation / current_num_permutations; ui::TouchEvent* next_dispatch = queued_events[index]; ASSERT_TRUE(next_dispatch != NULL); // |next_dispatch| has to be put in this container so that its time // stamp can be changed to this point in the test, when it is being // dispatched.. EventTestApi test_dispatch(next_dispatch); test_dispatch.set_time_stamp(Now()); generator_->Dispatch(next_dispatch); queued_events.erase(queued_events.begin() + index); // Keep track of what fingers have been pressed, to release // only those fingers at the end, so the check for being in // no fingers down can be accurate. if (next_dispatch->type() == ET_TOUCH_PRESSED) { fingers_pressed[next_dispatch->touch_id()] = true; } else if (next_dispatch->type() == ET_TOUCH_RELEASED) { fingers_pressed[next_dispatch->touch_id()] = false; } } ASSERT_EQ(queued_events.size(), 0u); // Release fingers recorded as pressed. for(int j = 0; j < int(fingers_pressed.size()); j++){ if (fingers_pressed[j] == true) { generator_->ReleaseTouchId(j); fingers_pressed[j] = false; } } AdvanceSimulatedTimePastPotentialTapDelay(); EXPECT_TRUE(IsInNoFingersDownState()); ClearCapturedEvents(); } } // With the simple swipe gestures, if additional fingers are added and the tap // timer times out, then the state should change to the wait for one finger // state. TEST_F(TouchExplorationTest, GestureAddedFinger) { SwitchTouchExplorationMode(true); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); float distance = gesture_detector_config_.touch_slop + 1; ui::TouchEvent first_press( ui::ET_TOUCH_PRESSED, gfx::Point(100, 200), 0, Now()); generator_->Dispatch(&first_press); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); gfx::Point second_location(100 + distance, 200); generator_->MoveTouch(second_location); EXPECT_TRUE(IsInGestureInProgressState()); EXPECT_FALSE(IsInTouchToMouseMode()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); // Generate a second press, but time out past the gesture period so that // gestures are prevented from continuing to go through. ui::TouchEvent second_press( ui::ET_TOUCH_PRESSED, gfx::Point(20, 21), 1, Now()); generator_->Dispatch(&second_press); AdvanceSimulatedTimePastTapDelay(); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInTouchToMouseMode()); ASSERT_EQ(0U, captured_events.size()); } TEST_F(TouchExplorationTest, EnterSlideGestureState) { SwitchTouchExplorationMode(true); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); int window_right = BoundsOfRootWindowInDIP().right(); float distance = gesture_detector_config_.touch_slop + 1; ui::TouchEvent first_press( ui::ET_TOUCH_PRESSED, gfx::Point(window_right, 1), 0, Now()); gfx::Point second_location(window_right, 1 + distance / 2); gfx::Point third_location(window_right, 1 + distance); gfx::Point fourth_location(window_right, 35); generator_->Dispatch(&first_press); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); // Since we haven't moved past slop yet, we should not be in slide gesture. generator_->MoveTouch(second_location); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); // Once we are out of slop, we should be in slide gesture since we are along // the edge of the screen. generator_->MoveTouch(third_location); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_TRUE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); // Now that we are in slide gesture, we can adjust the volume. generator_->MoveTouch(fourth_location); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); // Since we are at the right edge of the screen, but the sound timer has not // elapsed, there should have been a sound that fired and a volume // change. size_t num_adjust_sounds = delegate_.NumAdjustSounds(); ASSERT_EQ(1U, num_adjust_sounds); ASSERT_EQ(1U, delegate_.VolumeChanges().size()); // Exit out of slide gesture once touch is lifted, but not before even if the // grace period is over. AdvanceSimulatedTimePastPotentialTapDelay(); ASSERT_EQ(0U, captured_events.size()); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_TRUE(IsInSlideGestureState()); generator_->ReleaseTouch(); ASSERT_EQ(0U, captured_events.size()); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); } // If a press + move occurred outside the boundaries, but within the slop // boundaries and then moved into the boundaries of an edge, there still should // not be a slide gesture. TEST_F(TouchExplorationTest, AvoidEnteringSlideGesture) { SwitchTouchExplorationMode(true); gfx::Rect window = BoundsOfRootWindowInDIP(); float distance = gesture_detector_config_.touch_slop + 1; ui::TouchEvent first_press( ui::ET_TOUCH_PRESSED, gfx::Point(window.right() - GetSlopDistanceFromEdge(), 1), 0, Now()); gfx::Point out_of_slop(window.right() - GetSlopDistanceFromEdge() + distance, 1); gfx::Point into_boundaries(window.right() - GetMaxDistanceFromEdge() / 2, 1); generator_->Dispatch(&first_press); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); generator_->MoveTouch(out_of_slop); EXPECT_FALSE(IsInTouchToMouseMode()); EXPECT_TRUE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); // Since we did not start moving while in the boundaries, we should not be in // slide gestures. generator_->MoveTouch(into_boundaries); EXPECT_TRUE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); generator_->ReleaseTouch(); } // If the slide gesture begins within the boundaries and then moves // SlopDistanceFromEdge there should still be a sound change. If the finger // moves into the center screen, there should no longer be a sound change but it // should still be in slide gesture. If the finger moves back into the edges // without lifting, it should start changing sound again. TEST_F(TouchExplorationTest, TestingBoundaries) { SwitchTouchExplorationMode(true); gfx::Rect window = BoundsOfRootWindowInDIP(); gfx::Point initial_press(window.right() - GetMaxDistanceFromEdge() / 2, 1); gfx::Point center_screen(window.right() / 2, window.bottom() / 2); ui::TouchEvent first_press(ui::ET_TOUCH_PRESSED, initial_press, 0, Now()); generator_->Dispatch(&first_press); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); // Move past the touch slop to begin slide gestures. // + slop + 1 to actually leave slop. gfx::Point touch_move( initial_press.x(), initial_press.y() + gesture_detector_config_.touch_slop + 1); generator_->MoveTouch(touch_move); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_TRUE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); // Move the touch into slop boundaries. It should still be in slide gestures // and adjust the volume. gfx::Point into_slop_boundaries( window.right() - GetSlopDistanceFromEdge() / 2, 1); generator_->MoveTouch(into_slop_boundaries); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_TRUE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); // The sound is rate limiting so it only activates every 150ms. simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(200)); size_t num_adjust_sounds = delegate_.NumAdjustSounds(); ASSERT_EQ(1U, num_adjust_sounds); ASSERT_EQ(1U, delegate_.VolumeChanges().size()); // Move the touch into the center of the window. It should still be in slide // gestures, but there should not be anymore volume adjustments. generator_->MoveTouch(center_screen); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_TRUE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(200)); num_adjust_sounds = delegate_.NumAdjustSounds(); ASSERT_EQ(1U, num_adjust_sounds); ASSERT_EQ(1U, delegate_.VolumeChanges().size()); // Move the touch back into slop edge distance and volume should be changing // again, one volume change for each new move. generator_->MoveTouch(into_slop_boundaries); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_TRUE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); generator_->MoveTouch( gfx::Point(into_slop_boundaries.x() + gesture_detector_config_.touch_slop, into_slop_boundaries.y())); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(200)); num_adjust_sounds = delegate_.NumAdjustSounds(); ASSERT_EQ(2U, num_adjust_sounds); ASSERT_EQ(3U, delegate_.VolumeChanges().size()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); generator_->ReleaseTouch(); } // Even if the gesture starts within bounds, if it has not moved past slop // within the grace period, it should go to touch exploration. TEST_F(TouchExplorationTest, InBoundariesTouchExploration) { SwitchTouchExplorationMode(true); gfx::Rect window = BoundsOfRootWindowInDIP(); gfx::Point initial_press(window.right() - GetMaxDistanceFromEdge() / 2, 1); ui::TouchEvent first_press( ui::ET_TOUCH_PRESSED, initial_press, 0, Now()); generator_->Dispatch(&first_press); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); EXPECT_FALSE(IsInTouchToMouseMode()); AdvanceSimulatedTimePastTapDelay(); EXPECT_FALSE(IsInGestureInProgressState()); EXPECT_FALSE(IsInSlideGestureState()); EXPECT_TRUE(IsInTouchToMouseMode()); } // If two fingers tap the screen at the same time and release before the tap // timer runs out, a control key event should be sent to silence chromevox. TEST_F(TouchExplorationTest, TwoFingerTap) { SwitchTouchExplorationMode(true); generator_->set_current_location(gfx::Point(101, 102)); generator_->PressTouchId(1); EXPECT_FALSE(IsInTwoFingerTapState()); generator_->PressTouchId(2); EXPECT_TRUE(IsInTwoFingerTapState()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); generator_->ReleaseTouchId(1); EXPECT_TRUE(IsInTwoFingerTapState()); generator_->ReleaseTouchId(2); // Two key events should have been sent to silence the feedback. EXPECT_EQ(2U, captured_events.size()); } // If the fingers are not released before the tap timer runs out, a control // keyevent is not sent and the state will no longer be in two finger tap. TEST_F(TouchExplorationTest, TwoFingerTapAndHold) { SwitchTouchExplorationMode(true); generator_->PressTouchId(1); EXPECT_FALSE(IsInTwoFingerTapState()); generator_->PressTouchId(2); EXPECT_TRUE(IsInTwoFingerTapState()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); AdvanceSimulatedTimePastTapDelay(); // Since the tap delay has elapsed, it should no longer be in two finger tap. EXPECT_FALSE(IsInTwoFingerTapState()); } // The next two tests set up two finger swipes to happen. If one of the fingers // moves out of slop before the tap timer fires, a two finger tap is not made. // In this first test, the first finger placed will move out of slop. TEST_F(TouchExplorationTest, TwoFingerTapAndMoveFirstFinger) { SwitchTouchExplorationMode(true); // Once one of the fingers leaves slop, it should no longer be in two finger // tap. ui::TouchEvent first_press_id_1( ui::ET_TOUCH_PRESSED, gfx::Point(100, 200), 1, Now()); ui::TouchEvent first_press_id_2( ui::ET_TOUCH_PRESSED, gfx::Point(110, 200), 2, Now()); ui::TouchEvent slop_move_id_1( ui::ET_TOUCH_MOVED, gfx::Point(100 + gesture_detector_config_.touch_slop, 200), 1, Now()); ui::TouchEvent slop_move_id_2( ui::ET_TOUCH_MOVED, gfx::Point(110 + gesture_detector_config_.touch_slop, 200), 2, Now()); ui::TouchEvent out_slop_id_1( ui::ET_TOUCH_MOVED, gfx::Point(100 + gesture_detector_config_.touch_slop + 1, 200), 1, Now()); // Dispatch the inital presses. generator_->Dispatch(&first_press_id_1); EXPECT_FALSE(IsInTwoFingerTapState()); generator_->Dispatch(&first_press_id_2); EXPECT_TRUE(IsInTwoFingerTapState()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); // The presses have not moved out of slop yet so it should still be in // TwoFingerTap. generator_->Dispatch(&slop_move_id_1); EXPECT_TRUE(IsInTwoFingerTapState()); generator_->Dispatch(&slop_move_id_2); EXPECT_TRUE(IsInTwoFingerTapState()); // Once one of the fingers moves out of slop, we are no longer in // TwoFingerTap. generator_->Dispatch(&out_slop_id_1); EXPECT_FALSE(IsInTwoFingerTapState()); } // Similar test to the previous test except the second finger placed will be the // one to move out of slop. TEST_F(TouchExplorationTest, TwoFingerTapAndMoveSecondFinger) { SwitchTouchExplorationMode(true); // Once one of the fingers leaves slop, it should no longer be in two finger // tap. ui::TouchEvent first_press_id_1( ui::ET_TOUCH_PRESSED, gfx::Point(100, 200), 1, Now()); ui::TouchEvent first_press_id_2( ui::ET_TOUCH_PRESSED, gfx::Point(110, 200), 2, Now()); ui::TouchEvent out_slop_id_2( ui::ET_TOUCH_MOVED, gfx::Point(100 + gesture_detector_config_.touch_slop + 1, 200), 1, Now()); generator_->Dispatch(&first_press_id_1); EXPECT_FALSE(IsInTwoFingerTapState()); generator_->Dispatch(&first_press_id_2); EXPECT_TRUE(IsInTwoFingerTapState()); const ScopedVector& captured_events = GetCapturedEvents(); ASSERT_EQ(0U, captured_events.size()); generator_->Dispatch(&out_slop_id_2); EXPECT_FALSE(IsInTwoFingerTapState()); } // Corner passthrough should turn on if the user first holds down on either the // right or left corner past a delay and then places a finger anywhere else on // the screen. TEST_F(TouchExplorationTest, ActivateLeftCornerPassthrough) { SwitchTouchExplorationMode(true); gfx::Rect window = BoundsOfRootWindowInDIP(); gfx::Point left_corner(10, window.bottom() - GetMaxDistanceFromEdge() / 2); AssertCornerPassthroughWorking(left_corner); } TEST_F(TouchExplorationTest, ActivateRightCornerPassthrough) { SwitchTouchExplorationMode(true); gfx::Rect window = BoundsOfRootWindowInDIP(); gfx::Point right_corner(window.right() - GetMaxDistanceFromEdge() / 2, window.bottom() - GetMaxDistanceFromEdge() / 2); AssertCornerPassthroughWorking(right_corner); } // Earcons should play if the user slides off the screen or enters the screen // from the edge. TEST_F(TouchExplorationTest, EnterEarconPlays) { SwitchTouchExplorationMode(true); gfx::Rect window = BoundsOfRootWindowInDIP(); gfx::Point upper_left_corner(0, 0); gfx::Point upper_right_corner(window.right(), 0); gfx::Point lower_left_corner(0, window.bottom()); gfx::Point lower_right_corner(window.right(), window.bottom()); gfx::Point left_edge(0, 30); gfx::Point right_edge(window.right(), 30); gfx::Point top_edge(30, 0); gfx::Point bottom_edge(30, window.bottom()); std::vector locations; locations.push_back(upper_left_corner); locations.push_back(upper_right_corner); locations.push_back(lower_left_corner); locations.push_back(lower_right_corner); locations.push_back(left_edge); locations.push_back(right_edge); locations.push_back(top_edge); locations.push_back(bottom_edge); for (std::vector::const_iterator point = locations.begin(); point != locations.end(); ++point) { ui::TouchEvent touch_event(ui::ET_TOUCH_PRESSED, *point, 1, Now()); generator_->Dispatch(&touch_event); ASSERT_EQ(1U, delegate_.NumEnterScreenSounds()); generator_->ReleaseTouchId(1); delegate_.ResetCountersToZero(); } } TEST_F(TouchExplorationTest, ExitEarconPlays) { SwitchTouchExplorationMode(true); // On the device, it cannot actually tell if the finger has left the screen or // not. If the finger has left the screen, it reads it as a release that // occurred very close to the edge of the screen even if the finger is still // technically touching the moniter. To simulate this, a release that occurs // close to the edge is dispatched. gfx::Point initial_press(100, 200); gfx::Rect window = BoundsOfRootWindowInDIP(); gfx::Point upper_left_corner(0, 0); gfx::Point upper_right_corner(window.right(), 0); gfx::Point lower_left_corner(0, window.bottom()); gfx::Point lower_right_corner(window.right(), window.bottom()); gfx::Point left_edge(0, 30); gfx::Point right_edge(window.right(), 30); gfx::Point top_edge(30, 0); gfx::Point bottom_edge(30, window.bottom()); std::vector locations; locations.push_back(upper_left_corner); locations.push_back(upper_right_corner); locations.push_back(lower_left_corner); locations.push_back(lower_right_corner); locations.push_back(left_edge); locations.push_back(right_edge); locations.push_back(top_edge); locations.push_back(bottom_edge); for (std::vector::const_iterator point = locations.begin(); point != locations.end(); ++point) { generator_->PressTouch(); generator_->MoveTouch(initial_press); generator_->MoveTouch(*point); generator_->ReleaseTouch(); ASSERT_EQ(1U, delegate_.NumExitScreenSounds()); delegate_.ResetCountersToZero(); } } } // namespace ui