| // 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 <math.h> |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/macros.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/test/gl_surface_test_support.h" |
| |
| using EventList = std::vector<std::unique_ptr<ui::Event>>; |
| |
| namespace ui { |
| |
| namespace { |
| |
| // Records all mouse, touch, gesture, and key events. |
| class EventCapturer : public ui::EventHandler { |
| public: |
| EventCapturer() {} |
| ~EventCapturer() override {} |
| |
| void Reset() { |
| events_.clear(); |
| } |
| |
| void OnEvent(ui::Event* event) override { |
| if (event->IsMouseEvent() || event->IsTouchEvent() || |
| event->IsGestureEvent() || event->IsKeyEvent()) { |
| events_.push_back(ui::Event::Clone(*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 EventList& captured_events() const { return events_; } |
| |
| private: |
| EventList 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: |
| void SetOutputLevel(int volume) override { |
| volume_changes_.push_back(volume); |
| } |
| void SilenceSpokenFeedback() override {} |
| void PlayVolumeAdjustEarcon() override { ++num_times_adjust_sound_played_; } |
| void PlayPassthroughEarcon() override { ++num_times_passthrough_played_; } |
| void PlayExitScreenEarcon() override { ++num_times_exit_screen_played_; } |
| void PlayEnterScreenEarcon() override { ++num_times_enter_screen_played_; } |
| void HandleAccessibilityGesture(ui::AXGesture gesture) override { |
| last_gesture_ = gesture; |
| } |
| |
| const std::vector<float> VolumeChanges() const { return volume_changes_; } |
| size_t NumAdjustSounds() const { return num_times_adjust_sound_played_; } |
| size_t NumPassthroughSounds() const { return num_times_passthrough_played_; } |
| size_t NumExitScreenSounds() const { return num_times_exit_screen_played_; } |
| size_t NumEnterScreenSounds() const { return num_times_enter_screen_played_; } |
| ui::AXGesture GetLastGesture() const { return last_gesture_; } |
| |
| 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<float> 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; |
| ui::AXGesture last_gesture_ = ui::AX_GESTURE_NONE; |
| }; |
| |
| } // 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 SetTouchAccessibilityAnchorPoint(const gfx::Point& location) { |
| touch_exploration_controller_->SetTouchAccessibilityAnchorPoint(location); |
| } |
| |
| private: |
| std::unique_ptr<TouchExplorationController> touch_exploration_controller_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TouchExplorationControllerTestApi); |
| }; |
| |
| class TouchExplorationTest : public aura::test::AuraTestBase { |
| public: |
| TouchExplorationTest() : simulated_clock_(nullptr) {} |
| ~TouchExplorationTest() override {} |
| |
| void SetUp() override { |
| if (gl::GetGLImplementation() == gl::kGLImplementationNone) |
| gl::GLSurfaceTestSupport::InitializeOneOff(); |
| 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())); |
| |
| simulated_clock_ = new base::SimpleTestTickClock(); |
| // Tests fail if time is ever 0. |
| simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(10)); |
| // ui takes ownership of the tick clock. |
| ui::SetEventTickClockForTesting( |
| std::unique_ptr<base::TickClock>(simulated_clock_)); |
| |
| cursor_client()->ShowCursor(); |
| cursor_client()->DisableMouseEvents(); |
| } |
| |
| void TearDown() override { |
| ui::SetEventTickClockForTesting(nullptr); |
| 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 EventList& GetCapturedEvents() { |
| return event_capturer_.captured_events(); |
| } |
| |
| std::vector<ui::LocatedEvent*> GetCapturedLocatedEvents() { |
| const EventList& all_events = GetCapturedEvents(); |
| std::vector<ui::LocatedEvent*> 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<ui::LocatedEvent*>(all_events[i].get())); |
| } |
| } |
| return located_events; |
| } |
| |
| std::vector<ui::Event*> GetCapturedEventsOfType(int type) { |
| const EventList& all_events = GetCapturedEvents(); |
| std::vector<ui::Event*> events; |
| for (size_t i = 0; i < all_events.size(); ++i) { |
| if (type == all_events[i]->type()) |
| events.push_back(all_events[i].get()); |
| } |
| return events; |
| } |
| |
| std::vector<ui::LocatedEvent*> GetCapturedLocatedEventsOfType(int type) { |
| std::vector<ui::LocatedEvent*> located_events = GetCapturedLocatedEvents(); |
| std::vector<ui::LocatedEvent*> 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 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<ui::LocatedEvent*> 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::TimeTicks Now() { return ui::EventTimeForNow(); } |
| |
| void SetTouchAccessibilityAnchorPoint(const gfx::Point& location) { |
| touch_exploration_controller_->SetTouchAccessibilityAnchorPoint(location); |
| } |
| |
| std::unique_ptr<test::EventGenerator> generator_; |
| ui::GestureDetector::Config gesture_detector_config_; |
| // Owned by |ui|. |
| base::SimpleTestTickClock* simulated_clock_; |
| MockTouchExplorationControllerDelegate delegate_; |
| |
| private: |
| EventCapturer event_capturer_; |
| std::unique_ptr<TouchExplorationControllerTestApi> |
| touch_exploration_controller_; |
| std::unique_ptr<aura::test::TestCursorClient> 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 = e1->AsTouchEvent(); |
| ui::TouchEvent* touch_event2 = e2->AsTouchEvent(); |
| 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 = e1->AsMouseEvent(); |
| ui::MouseEvent* mouse_event2 = e2->AsMouseEvent(); |
| 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 = e1->AsKeyEvent(); |
| ui::KeyEvent* key_event2 = e2->AsKeyEvent(); |
| 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. |
| |
| 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<ui::LocatedEvent*> 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, ui::EventTimeForNow(), 0, |
| 0); |
| generator_->Dispatch(&mouse_move); |
| generator_->ReleaseTouch(); |
| AdvanceSimulatedTimePastTapDelay(); |
| |
| std::vector<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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<ui::LocatedEvent*>::const_iterator it; |
| for (it = captured_events.begin(); it != captured_events.end(); ++it) { |
| if ((*it)->type() == ui::ET_MOUSE_MOVED) { |
| EXPECT_TRUE((*it)->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| 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_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type()); |
| EXPECT_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| 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<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); |
| EXPECT_EQ(tap_location, captured_events[1]->location()); |
| EXPECT_TRUE(captured_events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_TRUE(IsInNoFingersDownState()); |
| } |
| |
| // If an explicit anchor point is set during touch exploration, double-tapping |
| // should send a 'click' gesture rather than a simulated touch press and |
| // release. |
| TEST_F(TouchExplorationTest, DoubleTapWithExplicitAnchorPoint) { |
| 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(); |
| |
| SetTouchAccessibilityAnchorPoint(tap_location); |
| |
| std::vector<ui::LocatedEvent*> 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 click gesture. |
| gfx::Point double_tap_location(33, 34); |
| generator_->set_current_location(double_tap_location); |
| generator_->PressTouch(); |
| generator_->ReleaseTouch(); |
| generator_->PressTouch(); |
| generator_->ReleaseTouch(); |
| |
| std::vector<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); |
| ASSERT_EQ(0U, captured_events.size()); |
| EXPECT_TRUE(IsInNoFingersDownState()); |
| EXPECT_EQ(ui::AX_GESTURE_CLICK, delegate_.GetLastGesture()); |
| } |
| |
| // 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<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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()); |
| EXPECT_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| |
| 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()); |
| EXPECT_TRUE(captured_events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| |
| 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()); |
| EXPECT_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| |
| // 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); |
| // 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<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| base::TimeTicks 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()); |
| EXPECT_TRUE(captured_events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| base::TimeTicks 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<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); |
| ASSERT_EQ(4U, captured_events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[0]->type()); |
| EXPECT_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type()); |
| EXPECT_TRUE(captured_events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[2]->type()); |
| EXPECT_EQ(tap_location, captured_events[2]->location()); |
| EXPECT_TRUE(captured_events[2]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[3]->type()); |
| EXPECT_EQ(tap_location, captured_events[3]->location()); |
| EXPECT_TRUE(captured_events[3]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| } |
| |
| // 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<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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<ui::LocatedEvent*> 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_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); |
| EXPECT_EQ(initial_touch_location, captured_events[1]->location()); |
| EXPECT_TRUE(captured_events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| 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<ui::LocatedEvent*> events = |
| GetCapturedLocatedEventsOfType(ui::ET_MOUSE_MOVED); |
| ASSERT_EQ(1U, events.size()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| 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<ui::LocatedEvent*> 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_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type()); |
| EXPECT_EQ(initial_touch_location, captured_events[1]->location()); |
| EXPECT_TRUE(captured_events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| } |
| |
| 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<ui::LocatedEvent*> 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 |
| 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 split tap 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, no events should be captured. |
| // 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<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); |
| ASSERT_EQ(0U, captured_events.size()); |
| 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. |
| 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<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); |
| ASSERT_EQ(0U, captured_events.size()); |
| 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. |
| 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(0U, captured_events.size()); |
| 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 EventList& 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_TRUE(captured_events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type()); |
| EXPECT_TRUE(captured_events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY); |
| EXPECT_TRUE(IsInTouchToMouseMode()); |
| EXPECT_FALSE(IsInGestureInProgressState()); |
| } |
| |
| // A swipe+direction gesture should trigger a Shift+Search+Direction |
| // keyboard event. |
| TEST_F(TouchExplorationTest, GestureSwipe) { |
| SwitchTouchExplorationMode(true); |
| |
| // Test all four swipe directions with 1 to 4 fingers. |
| struct GestureInfo { |
| int move_x; |
| int move_y; |
| int num_fingers; |
| ui::AXGesture expected_gesture; |
| } gestures_to_test[] = { |
| {-1, 0, 1, ui::AX_GESTURE_SWIPE_LEFT_1}, |
| {0, -1, 1, ui::AX_GESTURE_SWIPE_UP_1}, |
| {1, 0, 1, ui::AX_GESTURE_SWIPE_RIGHT_1}, |
| {0, 1, 1, ui::AX_GESTURE_SWIPE_DOWN_1}, |
| {-1, 0, 2, ui::AX_GESTURE_SWIPE_LEFT_2}, |
| {0, -1, 2, ui::AX_GESTURE_SWIPE_UP_2}, |
| {1, 0, 2, ui::AX_GESTURE_SWIPE_RIGHT_2}, |
| {0, 1, 2, ui::AX_GESTURE_SWIPE_DOWN_2}, |
| {-1, 0, 3, ui::AX_GESTURE_SWIPE_LEFT_3}, |
| {0, -1, 3, ui::AX_GESTURE_SWIPE_UP_3}, |
| {1, 0, 3, ui::AX_GESTURE_SWIPE_RIGHT_3}, |
| {0, 1, 3, ui::AX_GESTURE_SWIPE_DOWN_3}, |
| {-1, 0, 4, ui::AX_GESTURE_SWIPE_LEFT_4}, |
| {0, -1, 4, ui::AX_GESTURE_SWIPE_UP_4}, |
| {1, 0, 4, ui::AX_GESTURE_SWIPE_RIGHT_4}, |
| {0, 1, 4, ui::AX_GESTURE_SWIPE_DOWN_4}, |
| }; |
| |
| // 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; |
| |
| for (size_t i = 0; i < arraysize(gestures_to_test); ++i) { |
| const float distance = 2 * gesture_detector_config_.touch_slop + 1; |
| int move_x = gestures_to_test[i].move_x * distance; |
| int move_y = gestures_to_test[i].move_y * distance; |
| int num_fingers = gestures_to_test[i].num_fingers; |
| ui::AXGesture expected_gesture = gestures_to_test[i].expected_gesture; |
| |
| std::vector<gfx::Point> 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]; |
| |
| // 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, move_y); |
| EXPECT_EQ(expected_gesture, delegate_.GetLastGesture()); |
| 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. |
| std::vector<std::unique_ptr<ui::TouchEvent>> all_events; |
| |
| // A copy of all events list which can be modified without destrying events. |
| std::vector<ui::TouchEvent*> queued_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(base::MakeUnique<TouchEvent>( |
| ui::ET_TOUCH_PRESSED, gfx::Point(x++, y++), touch_id, Now())); |
| queued_events.push_back(all_events.back().get()); |
| all_events.push_back(base::MakeUnique<TouchEvent>( |
| ui::ET_TOUCH_MOVED, gfx::Point(x++, y++), touch_id, Now())); |
| queued_events.push_back(all_events.back().get()); |
| all_events.push_back(base::MakeUnique<TouchEvent>( |
| ui::ET_TOUCH_RELEASED, gfx::Point(x, y), touch_id, Now())); |
| queued_events.push_back(all_events.back().get()); |
| } |
| |
| // 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<bool> 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 EventList& 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 EventList& 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 EventList& 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 EventList& 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 EventList& 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 EventList& 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 EventList& 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 EventList& 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<gfx::Point> 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<gfx::Point>::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<gfx::Point> 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<gfx::Point>::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 |