|  | // Copyright 2014 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "ash/accessibility/chromevox/touch_exploration_controller.h" | 
|  |  | 
|  | #include <math.h> | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <array> | 
|  | #include <memory> | 
|  | #include <vector> | 
|  |  | 
|  | #include "ash/accessibility/chromevox/mock_touch_exploration_controller_delegate.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/test/simple_test_tick_clock.h" | 
|  | #include "base/time/time.h" | 
|  | #include "ui/accessibility/ax_enums.mojom.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/events/types/event_type.h" | 
|  | #include "ui/gfx/geometry/point.h" | 
|  | #include "ui/gfx/geometry/transform.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 ash { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Records all mouse, touch, gesture, and key events. | 
|  | class EventCapturer : public ui::EventHandler { | 
|  | public: | 
|  | EventCapturer() {} | 
|  |  | 
|  | EventCapturer(const EventCapturer&) = delete; | 
|  | EventCapturer& operator=(const EventCapturer&) = delete; | 
|  |  | 
|  | ~EventCapturer() override {} | 
|  |  | 
|  | void Reset() { events_.clear(); } | 
|  |  | 
|  | void OnEvent(ui::Event* event) override { | 
|  | if (event->IsMouseEvent() || event->IsTouchEvent() || | 
|  | event->IsGestureEvent() || event->IsKeyEvent()) { | 
|  | events_.push_back(event->Clone()); | 
|  | } 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_; | 
|  | }; | 
|  |  | 
|  | int Factorial(int n) { | 
|  | if (n <= 0) | 
|  | return 0; | 
|  | if (n == 1) | 
|  | return 1; | 
|  | return n * Factorial(n - 1); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class TouchExplorationControllerTestApi { | 
|  | public: | 
|  | TouchExplorationControllerTestApi( | 
|  | TouchExplorationController* touch_exploration_controller) { | 
|  | touch_exploration_controller_.reset(touch_exploration_controller); | 
|  | } | 
|  |  | 
|  | TouchExplorationControllerTestApi(const TouchExplorationControllerTestApi&) = | 
|  | delete; | 
|  | TouchExplorationControllerTestApi& operator=( | 
|  | const TouchExplorationControllerTestApi&) = delete; | 
|  |  | 
|  | void CallTapTimerNowForTesting() { | 
|  | DCHECK(touch_exploration_controller_->tap_timer_.IsRunning()); | 
|  | touch_exploration_controller_->tap_timer_.Stop(); | 
|  | touch_exploration_controller_->OnTapTimerFired(); | 
|  | } | 
|  |  | 
|  | void CallTapTimerNowIfRunningForTesting() { | 
|  | if (touch_exploration_controller_->tap_timer_.IsRunning()) { | 
|  | touch_exploration_controller_->tap_timer_.Stop(); | 
|  | touch_exploration_controller_->OnTapTimerFired(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CallLongPressTimerNowIfRunningForTesting() { | 
|  | if (touch_exploration_controller_->long_press_timer_.IsRunning()) { | 
|  | touch_exploration_controller_->long_press_timer_.Stop(); | 
|  | touch_exploration_controller_->OnLiftActivationLongPressTimerFired(); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | void SetExcludeBounds(const gfx::Rect& bounds) { | 
|  | touch_exploration_controller_->SetExcludeBounds(bounds); | 
|  | } | 
|  |  | 
|  | void SetLiftActivationBounds(const gfx::Rect& bounds) { | 
|  | touch_exploration_controller_->SetLiftActivationBounds(bounds); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<TouchExplorationController> touch_exploration_controller_; | 
|  | }; | 
|  |  | 
|  | class TouchExplorationTest : public aura::test::AuraTestBase { | 
|  | public: | 
|  | TouchExplorationTest() {} | 
|  |  | 
|  | TouchExplorationTest(const TouchExplorationTest&) = delete; | 
|  | TouchExplorationTest& operator=(const TouchExplorationTest&) = delete; | 
|  |  | 
|  | ~TouchExplorationTest() override {} | 
|  |  | 
|  | void SetUp() override { | 
|  | if (gl::GetGLImplementation() == gl::kGLImplementationNone) | 
|  | gl::GLSurfaceTestSupport::InitializeOneOff(); | 
|  | aura::test::AuraTestBase::SetUp(); | 
|  | cursor_client_ = | 
|  | std::make_unique<aura::test::TestCursorClient>(root_window()); | 
|  | root_window()->AddPreTargetHandler(&event_capturer_); | 
|  | generator_ = std::make_unique<ui::test::EventGenerator>(root_window()); | 
|  |  | 
|  | // Tests fail if time is ever 0. | 
|  | simulated_clock_.Advance(base::Milliseconds(10)); | 
|  | // ui takes ownership of the tick clock. | 
|  | ui::SetEventTickClockForTesting(&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::LocatedEvent*> GetCapturedLocatedEventsOfType( | 
|  | ui::EventType 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; | 
|  | } | 
|  |  | 
|  | std::vector<gfx::Point>& GetTouchExplorePoints() { | 
|  | return delegate_.GetTouchExplorePoints(); | 
|  | } | 
|  |  | 
|  | void ClearCapturedAndGestureEvents() { | 
|  | event_capturer_.Reset(); | 
|  | GetTouchExplorePoints().clear(); | 
|  | } | 
|  |  | 
|  | void AdvanceSimulatedTimePastTapDelay() { | 
|  | simulated_clock_.Advance(gesture_detector_config_.double_tap_timeout); | 
|  | simulated_clock_.Advance(base::Milliseconds(1)); | 
|  | touch_exploration_controller_->CallTapTimerNowForTesting(); | 
|  | } | 
|  |  | 
|  | void AdvanceSimulatedTimePastPotentialTapDelay() { | 
|  | simulated_clock_.Advance(base::Milliseconds(1000)); | 
|  | touch_exploration_controller_->CallTapTimerNowIfRunningForTesting(); | 
|  | } | 
|  |  | 
|  | void AdvanceSimulatedTimePastLongPressDelay() { | 
|  | simulated_clock_.Advance(base::Milliseconds(5000)); | 
|  | touch_exploration_controller_->CallLongPressTimerNowIfRunningForTesting(); | 
|  | } | 
|  |  | 
|  | 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_ = | 
|  | std::make_unique<TouchExplorationControllerTestApi>( | 
|  | new TouchExplorationController(root_window(), &delegate_, | 
|  | nullptr)); | 
|  | cursor_client()->ShowCursor(); | 
|  | cursor_client()->DisableMouseEvents(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void EnterTouchExplorationModeAtLocation(gfx::Point tap_location) { | 
|  | ui::TouchEvent touch_press( | 
|  | ui::EventType::kTouchPressed, tap_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&touch_press); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | EXPECT_TRUE(IsInTouchToMouseMode()); | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | void SetExcludeBounds(const gfx::Rect& bounds) { | 
|  | touch_exploration_controller_->SetExcludeBounds(bounds); | 
|  | } | 
|  |  | 
|  | void SetLiftActivationBounds(const gfx::Rect& bounds) { | 
|  | touch_exploration_controller_->SetLiftActivationBounds(bounds); | 
|  | } | 
|  |  | 
|  | // Taps at |tap_location|, waiting past tap delay to enter touch | 
|  | // exploration. Pass true to |set_anchor_point| to ensure any subsequent | 
|  | // gestures like a double tap go to |tap_location|. Usually, ChromeVox sets | 
|  | // the anchor, which is the center of the focused node and can differ from | 
|  | // |tap_location|. | 
|  | void TapAndVerifyTouchExplore(gfx::Point tap_location, | 
|  | bool set_anchor_point = false) { | 
|  | generator_->set_current_screen_location(tap_location); | 
|  | generator_->PressTouchId(1); | 
|  | generator_->ReleaseTouchId(1); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | if (set_anchor_point) | 
|  | SetTouchAccessibilityAnchorPoint(tap_location); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> events = | 
|  | GetCapturedLocatedEventsOfType(ui::EventType::kMouseMoved); | 
|  | ASSERT_TRUE(events.empty()); | 
|  |  | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | EXPECT_EQ(tap_location, GetTouchExplorePoints()[0]); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ui::test::EventGenerator> generator_; | 
|  | ui::GestureDetector::Config gesture_detector_config_; | 
|  | 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_; | 
|  | }; | 
|  |  | 
|  | // 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->pointer_details().id, | 
|  | touch_event2->pointer_details().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_screen_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_screen_location(location); | 
|  | generator_->PressTouch(); | 
|  | generator_->ReleaseTouch(); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> events = | 
|  | GetCapturedLocatedEventsOfType(ui::EventType::kMouseMoved); | 
|  | ASSERT_EQ(0U, events.size()); | 
|  |  | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | EXPECT_EQ(location, GetTouchExplorePoints()[0]); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  | } | 
|  |  | 
|  | TEST_F(TouchExplorationTest, ActualMouseMovesUnaffected) { | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | gfx::Point location_start(11, 12); | 
|  | gfx::Point location_end(13, 14); | 
|  | generator_->set_current_screen_location(location_start); | 
|  | generator_->PressTouch(); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | generator_->MoveTouch(location_end); | 
|  |  | 
|  | gfx::Point location_real_mouse_move(15, 16); | 
|  | ui::MouseEvent mouse_move(ui::EventType::kMouseMoved, | 
|  | 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::EventType::kMouseMoved); | 
|  | std::vector<gfx::Point> touch_explore_points = GetTouchExplorePoints(); | 
|  | ASSERT_EQ(1U, events.size()); | 
|  | ASSERT_EQ(3U, touch_explore_points.size()); | 
|  |  | 
|  | EXPECT_EQ(location_start, touch_explore_points[0]); | 
|  | EXPECT_EQ(location_end, touch_explore_points[1]); | 
|  | EXPECT_EQ(location_end, touch_explore_points[2]); | 
|  |  | 
|  | // The real mouse move goes through. | 
|  | EXPECT_EQ(location_real_mouse_move, events[0]->location()); | 
|  | CONFIRM_EVENTS_ARE_MOUSE_AND_EQUAL(events[0], &mouse_move); | 
|  | EXPECT_FALSE(events[0]->flags() & ui::EF_IS_SYNTHESIZED); | 
|  | EXPECT_FALSE(events[0]->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()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // 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::EventType::kTouchMoved, gfx::Point(11, 12), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // The press from the second finger should get rewritten. | 
|  | generator_->PressTouchId(2); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | EXPECT_TRUE(IsInTouchToMouseMode()); | 
|  |  | 
|  | ASSERT_FALSE(GetTouchExplorePoints().empty()); | 
|  |  | 
|  | // The release of the first finger shouldn't be affected. | 
|  | ui::TouchEvent touch_release( | 
|  | ui::EventType::kTouchReleased, gfx::Point(11, 12), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | generator_->Dispatch(&touch_release); | 
|  | captured_events = GetCapturedLocatedEvents(); | 
|  | ASSERT_EQ(1u, captured_events.size()); | 
|  | CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_release); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // 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_TRUE(captured_events.empty()); | 
|  | ASSERT_EQ(2U, GetTouchExplorePoints().size()); | 
|  | 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::Milliseconds(1000)); | 
|  | generator_->PressTouchId(2); | 
|  | std::vector<ui::LocatedEvent*> events = | 
|  | GetCapturedLocatedEventsOfType(ui::EventType::kMouseMoved); | 
|  | ASSERT_TRUE(events.empty()); | 
|  |  | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  |  | 
|  | 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_screen_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_screen_location(location1); | 
|  | simulated_clock_.Advance(base::Milliseconds(301)); | 
|  | generator_->PressTouch(); | 
|  | generator_->ReleaseTouch(); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> events = | 
|  | GetCapturedLocatedEventsOfType(ui::EventType::kMouseMoved); | 
|  | ASSERT_TRUE(events.empty()); | 
|  |  | 
|  | ASSERT_EQ(2U, GetTouchExplorePoints().size()); | 
|  | EXPECT_EQ(location0, GetTouchExplorePoints()[0]); | 
|  | EXPECT_EQ(location1, GetTouchExplorePoints()[1]); | 
|  | 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); | 
|  | gfx::Point tap_location(51, 52); | 
|  | TapAndVerifyTouchExplore(tap_location); | 
|  | // Now double-tap at a different location. This should result in | 
|  | // no touches at all, but a click gesture to ChromeVox. | 
|  | gfx::Point double_tap_location(33, 34); | 
|  | generator_->set_current_screen_location(double_tap_location); | 
|  | generator_->PressTouch(); | 
|  | generator_->ReleaseTouch(); | 
|  | generator_->PressTouch(); | 
|  | generator_->ReleaseTouch(); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); | 
|  | ASSERT_TRUE(captured_events.empty()); | 
|  | EXPECT_EQ(ax::mojom::Gesture::kClick, delegate_.GetLastGesture()); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  | } | 
|  |  | 
|  | // The press of the second tap in a double-tap must come within the double-tap | 
|  | // timeout, but the release of the second tap can come later. | 
|  | TEST_F(TouchExplorationTest, DoubleTapTiming) { | 
|  | SwitchTouchExplorationMode(true); | 
|  | gfx::Point tap_location(51, 52); | 
|  | TapAndVerifyTouchExplore(tap_location, true); | 
|  |  | 
|  | // The press of the second tap happens in time, but the release does not. | 
|  | gfx::Point double_tap_location(33, 34); | 
|  | generator_->set_current_screen_location(double_tap_location); | 
|  | generator_->PressTouch(); | 
|  | generator_->ReleaseTouch(); | 
|  | simulated_clock_.Advance(gesture_detector_config_.double_tap_timeout - | 
|  | base::Milliseconds(25)); | 
|  | generator_->PressTouch(); | 
|  | simulated_clock_.Advance(base::Milliseconds(50)); | 
|  | generator_->ReleaseTouch(); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); | 
|  | ASSERT_EQ(0U, captured_events.size()); | 
|  | EXPECT_EQ(ax::mojom::Gesture::kClick, delegate_.GetLastGesture()); | 
|  | } | 
|  |  | 
|  | // 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); | 
|  |  | 
|  | gfx::Point tap_location(51, 52); | 
|  | TapAndVerifyTouchExplore(tap_location, true); | 
|  |  | 
|  | // Now double-tap at a different location. This should result in | 
|  | // a click gesture. | 
|  | gfx::Point double_tap_location(33, 34); | 
|  | generator_->set_current_screen_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(ax::mojom::Gesture::kClick, 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); | 
|  | gfx::Point tap_location(11, 12); | 
|  | TapAndVerifyTouchExplore(tap_location); | 
|  |  | 
|  | // 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_screen_location(first_tap_location); | 
|  | generator_->PressTouchId(1); | 
|  | generator_->ReleaseTouchId(1); | 
|  | ASSERT_EQ(0U, delegate_.NumPassthroughSounds()); | 
|  | gfx::Point second_tap_location(15, 16); | 
|  | generator_->set_current_screen_location(second_tap_location); | 
|  | generator_->PressTouchId(1); | 
|  | // Advance to the finger passing through. | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | ASSERT_EQ(1U, delegate_.NumPassthroughSounds()); | 
|  |  | 
|  | 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::EventType::kTouchPressed, 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); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // 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::EventType::kTouchMoved, captured_events[0]->type()); | 
|  | EXPECT_EQ(first_move_location - passthrough_offset, | 
|  | captured_events[0]->location()); | 
|  | EXPECT_EQ(ui::EventType::kTouchMoved, 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); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // 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::EventType::kTouchMoved, 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()); | 
|  |  | 
|  | // There should have only ever been one pass through earcon played. | 
|  | ASSERT_EQ(1U, delegate_.NumPassthroughSounds()); | 
|  | } | 
|  |  | 
|  | // 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); | 
|  | gfx::Point tap_location(11, 12); | 
|  | TapAndVerifyTouchExplore(tap_location); | 
|  |  | 
|  | // 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_screen_location(first_tap_location); | 
|  | generator_->PressTouch(); | 
|  | generator_->ReleaseTouch(); | 
|  | gfx::Point second_tap_location(23, 24); | 
|  | generator_->set_current_screen_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::EventType::kTouchPressed, 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::EventType::kTouchReleased, 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 touch explore point. | 
|  | gfx::Point initial_location(11, 12); | 
|  | generator_->set_current_screen_location(initial_location); | 
|  | generator_->PressTouch(); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  |  | 
|  | // 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_screen_location(final_location); | 
|  | simulated_clock_.Advance(base::Milliseconds(250)); | 
|  | generator_->PressTouch(); | 
|  | generator_->ReleaseTouch(); | 
|  |  | 
|  | ASSERT_EQ(3U, GetTouchExplorePoints().size()); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); | 
|  | ASSERT_TRUE(captured_events.empty()); | 
|  | EXPECT_EQ(ax::mojom::Gesture::kClick, delegate_.GetLastGesture()); | 
|  | } | 
|  |  | 
|  | // 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_screen_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::EventType::kMouseMoved); | 
|  | ASSERT_TRUE(events.empty()); | 
|  |  | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | EXPECT_EQ(initial_touch_location, GetTouchExplorePoints()[0]); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | 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::EventType::kTouchPressed, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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::EventType::kTouchReleased, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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_TRUE(captured_events.empty()); | 
|  | EXPECT_EQ(ax::mojom::Gesture::kClick, delegate_.GetLastGesture()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | ui::TouchEvent touch_explore_release( | 
|  | ui::EventType::kTouchReleased, initial_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | 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::EventType::kMouseMoved); | 
|  | ASSERT_TRUE(events.empty()); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  |  | 
|  | // Now tap at a different location. Release at the first location, | 
|  | // then release at the second. This should result in a | 
|  | // click gesture to ChromeVox. | 
|  | ui::TouchEvent split_tap_press( | 
|  | ui::EventType::kTouchPressed, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | generator_->Dispatch(&split_tap_press); | 
|  | ui::TouchEvent touch_explore_release( | 
|  | ui::EventType::kTouchReleased, initial_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&touch_explore_release); | 
|  | ui::TouchEvent split_tap_release( | 
|  | ui::EventType::kTouchReleased, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | generator_->Dispatch(&split_tap_release); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents(); | 
|  | EXPECT_TRUE(captured_events.empty()); | 
|  | EXPECT_EQ(ax::mojom::Gesture::kClick, delegate_.GetLastGesture()); | 
|  | } | 
|  |  | 
|  | 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::EventType::kMouseMoved); | 
|  | ASSERT_TRUE(events.empty()); | 
|  |  | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | EXPECT_EQ(initial_touch_location, GetTouchExplorePoints()[0]); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // Now tap at a different location | 
|  | ui::TouchEvent split_tap_press( | 
|  | ui::EventType::kTouchPressed, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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::EventType::kTouchPressed, third_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 2)); | 
|  | 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::EventType::kTouchReleased, initial_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&touch_explore_release); | 
|  | ui::TouchEvent split_tap_release( | 
|  | ui::EventType::kTouchReleased, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | generator_->Dispatch(&split_tap_release); | 
|  | ui::TouchEvent third_tap_release( | 
|  | ui::EventType::kTouchReleased, third_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 2)); | 
|  | 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); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // Now tap at a different location for split tap. | 
|  | ui::TouchEvent split_tap_press( | 
|  | ui::EventType::kTouchPressed, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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::EventType::kTouchMoved, first_move_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&first_touch_move); | 
|  | ui::TouchEvent first_touch_release( | 
|  | ui::EventType::kTouchReleased, first_move_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&first_touch_release); | 
|  | ui::TouchEvent second_touch_release( | 
|  | ui::EventType::kTouchReleased, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | ui::TouchEvent split_tap_press2( | 
|  | ui::EventType::kTouchPressed, second_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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::EventType::kTouchMoved, second_move_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | generator_->Dispatch(&second_touch_move2); | 
|  | ui::TouchEvent first_touch_release2( | 
|  | ui::EventType::kTouchReleased, first_touch_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&first_touch_release2); | 
|  | ui::TouchEvent second_touch_release2( | 
|  | ui::EventType::kTouchReleased, second_move_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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::EventType::kTouchPressed, gfx::Point(0, 1), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | 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::Milliseconds(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::Milliseconds(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 touch explore gesture when entering touch | 
|  | // exploration and also for the touch move. | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | generator_->MoveTouch(touch_exploration_location); | 
|  | ASSERT_TRUE(captured_events.empty()); | 
|  | EXPECT_EQ(2U, GetTouchExplorePoints().size()); | 
|  |  | 
|  | 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; | 
|  | ax::mojom::Gesture expected_gesture; | 
|  | }; | 
|  | constexpr std::array<GestureInfo, 16> gestures_to_test = {{ | 
|  | {-1, 0, 1, ax::mojom::Gesture::kSwipeLeft1}, | 
|  | {0, -1, 1, ax::mojom::Gesture::kSwipeUp1}, | 
|  | {1, 0, 1, ax::mojom::Gesture::kSwipeRight1}, | 
|  | {0, 1, 1, ax::mojom::Gesture::kSwipeDown1}, | 
|  | {-1, 0, 2, ax::mojom::Gesture::kSwipeLeft2}, | 
|  | {0, -1, 2, ax::mojom::Gesture::kSwipeUp2}, | 
|  | {1, 0, 2, ax::mojom::Gesture::kSwipeRight2}, | 
|  | {0, 1, 2, ax::mojom::Gesture::kSwipeDown2}, | 
|  | {-1, 0, 3, ax::mojom::Gesture::kSwipeLeft3}, | 
|  | {0, -1, 3, ax::mojom::Gesture::kSwipeUp3}, | 
|  | {1, 0, 3, ax::mojom::Gesture::kSwipeRight3}, | 
|  | {0, 1, 3, ax::mojom::Gesture::kSwipeDown3}, | 
|  | {-1, 0, 4, ax::mojom::Gesture::kSwipeLeft4}, | 
|  | {0, -1, 4, ax::mojom::Gesture::kSwipeUp4}, | 
|  | {1, 0, 4, ax::mojom::Gesture::kSwipeRight4}, | 
|  | {0, 1, 4, ax::mojom::Gesture::kSwipeDown4}, | 
|  | }}; | 
|  |  | 
|  | // 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 (auto& gesture : gestures_to_test) { | 
|  | const float distance = 2 * gesture_detector_config_.touch_slop + 1; | 
|  | int move_x = gesture.move_x * distance; | 
|  | int move_y = gesture.move_y * distance; | 
|  | int num_fingers = gesture.num_fingers; | 
|  | ax::mojom::Gesture expected_gesture = gesture.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)); | 
|  | } | 
|  |  | 
|  | // 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, | 
|  | delta_time_ms, kSteps, move_x, move_y); | 
|  | EXPECT_EQ(expected_gesture, delegate_.GetLastGesture()); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  | EXPECT_FALSE(IsInTouchToMouseMode()); | 
|  | EXPECT_FALSE(IsInGestureInProgressState()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(TouchExplorationTest, GestureSwipePortrit) { | 
|  | // Rotate the window 90-degrees counter-clockwise. | 
|  | root_window()->GetHost()->SetRootTransform(gfx::Transform::RowMajor( | 
|  | 0, 1, 0, 0, -1, 0, 0, root_window()->bounds().height(), 0, 0, 0, 0, 0, 0, | 
|  | 0, 0)); | 
|  |  | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | // Test 2-4 finger gestures. | 
|  | struct GestureInfo { | 
|  | int move_x; | 
|  | int move_y; | 
|  | int num_fingers; | 
|  | ax::mojom::Gesture expected_gesture; | 
|  | }; | 
|  | constexpr std::array<GestureInfo, 12> gestures_to_test = {{ | 
|  | {-1, 0, 2, ax::mojom::Gesture::kSwipeDown2}, | 
|  | {0, -1, 2, ax::mojom::Gesture::kSwipeLeft2}, | 
|  | {1, 0, 2, ax::mojom::Gesture::kSwipeUp2}, | 
|  | {0, 1, 2, ax::mojom::Gesture::kSwipeRight2}, | 
|  | {-1, 0, 3, ax::mojom::Gesture::kSwipeDown3}, | 
|  | {0, -1, 3, ax::mojom::Gesture::kSwipeLeft3}, | 
|  | {1, 0, 3, ax::mojom::Gesture::kSwipeUp3}, | 
|  | {0, 1, 3, ax::mojom::Gesture::kSwipeRight3}, | 
|  | {-1, 0, 4, ax::mojom::Gesture::kSwipeDown4}, | 
|  | {0, -1, 4, ax::mojom::Gesture::kSwipeLeft4}, | 
|  | {1, 0, 4, ax::mojom::Gesture::kSwipeUp4}, | 
|  | {0, 1, 4, ax::mojom::Gesture::kSwipeRight4}, | 
|  | }}; | 
|  |  | 
|  | // 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 (auto& gesture : gestures_to_test) { | 
|  | const float distance = 2 * gesture_detector_config_.touch_slop + 1; | 
|  | int move_x = gesture.move_x * distance; | 
|  | int move_y = gesture.move_y * distance; | 
|  | int num_fingers = gesture.num_fingers; | 
|  | ax::mojom::Gesture expected_gesture = gesture.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)); | 
|  | } | 
|  |  | 
|  | // 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, | 
|  | delta_time_ms, kSteps, move_x, move_y); | 
|  | EXPECT_EQ(expected_gesture, delegate_.GetLastGesture()); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  | EXPECT_FALSE(IsInTouchToMouseMode()); | 
|  | EXPECT_FALSE(IsInGestureInProgressState()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(TouchExplorationTest, AllFingerPermutations) { | 
|  | SwitchTouchExplorationMode(true); | 
|  | SuppressVLOGs(true); | 
|  | // We will test all permutations of events from one finger | 
|  | // 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) { | 
|  | all_events.clear(); | 
|  | int x = 10 * touch_id + 1; | 
|  | int y = 10 * touch_id + 2; | 
|  | all_events.push_back(std::make_unique<ui::TouchEvent>( | 
|  | ui::EventType::kTouchPressed, gfx::Point(x++, y++), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, touch_id))); | 
|  | all_events.push_back(std::make_unique<ui::TouchEvent>( | 
|  | ui::EventType::kTouchMoved, gfx::Point(x++, y++), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, touch_id))); | 
|  | all_events.push_back(std::make_unique<ui::TouchEvent>( | 
|  | ui::EventType::kTouchReleased, gfx::Point(x, y), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, touch_id))); | 
|  |  | 
|  | // 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); | 
|  |  | 
|  | // Initialize queued_events. | 
|  | for (size_t i = 0; i < all_events.size(); ++i) { | 
|  | queued_events.push_back(all_events[i].get()); | 
|  | } | 
|  |  | 
|  | 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.. | 
|  | ui::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() == ui::EventType::kTouchPressed) { | 
|  | fingers_pressed[next_dispatch->pointer_details().id] = true; | 
|  | } else if (next_dispatch->type() == ui::EventType::kTouchReleased) { | 
|  | fingers_pressed[next_dispatch->pointer_details().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()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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::EventType::kTouchPressed, gfx::Point(100, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&first_press); | 
|  | simulated_clock_.Advance(base::Milliseconds(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::EventType::kTouchPressed, gfx::Point(20, 21), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | 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::EventType::kTouchPressed, gfx::Point(window_right, 1), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | 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::Milliseconds(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::Milliseconds(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::EventType::kTouchPressed, | 
|  | gfx::Point(window.right() - GetSlopDistanceFromEdge(), 1), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | 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::Milliseconds(10)); | 
|  |  | 
|  | generator_->MoveTouch(out_of_slop); | 
|  | EXPECT_FALSE(IsInTouchToMouseMode()); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  | EXPECT_FALSE(IsInSlideGestureState()); | 
|  | simulated_clock_.Advance(base::Milliseconds(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::EventType::kTouchPressed, initial_press, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&first_press); | 
|  | simulated_clock_.Advance(base::Milliseconds(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::Milliseconds(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::Milliseconds(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::Milliseconds(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::Milliseconds(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::EventType::kTouchPressed, initial_press, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | 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_screen_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); | 
|  |  | 
|  | EXPECT_EQ(0U, captured_events.size()); | 
|  | ASSERT_EQ(ax::mojom::Gesture::kTap2, delegate_.GetLastGesture()); | 
|  | } | 
|  |  | 
|  | // 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::EventType::kTouchPressed, gfx::Point(100, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | ui::TouchEvent first_press_id_2( | 
|  | ui::EventType::kTouchPressed, gfx::Point(110, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 2)); | 
|  |  | 
|  | ui::TouchEvent slop_move_id_1( | 
|  | ui::EventType::kTouchMoved, | 
|  | gfx::Point(100 + gesture_detector_config_.touch_slop, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | ui::TouchEvent slop_move_id_2( | 
|  | ui::EventType::kTouchMoved, | 
|  | gfx::Point(110 + gesture_detector_config_.touch_slop, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 2)); | 
|  |  | 
|  | ui::TouchEvent out_slop_id_1( | 
|  | ui::EventType::kTouchMoved, | 
|  | gfx::Point(100 + gesture_detector_config_.touch_slop + 1, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  |  | 
|  | // 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::EventType::kTouchPressed, gfx::Point(100, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | ui::TouchEvent first_press_id_2( | 
|  | ui::EventType::kTouchPressed, gfx::Point(110, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 2)); | 
|  |  | 
|  | ui::TouchEvent out_slop_id_2( | 
|  | ui::EventType::kTouchMoved, | 
|  | gfx::Point(100 + gesture_detector_config_.touch_slop + 1, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  |  | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | TEST_F(TouchExplorationTest, ExclusionArea) { | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | gfx::Rect window = BoundsOfRootWindowInDIP(); | 
|  | gfx::Rect exclude = window; | 
|  | exclude.Inset(gfx::Insets::TLBR(0, 0, 30, 0)); | 
|  | SetExcludeBounds(exclude); | 
|  |  | 
|  | gfx::Point in_pt = exclude.CenterPoint(); | 
|  | gfx::Point in_mv_pt(in_pt.x(), (in_pt.y() + exclude.bottom()) / 2); | 
|  | gfx::Point out_pt(in_pt.x(), exclude.bottom() + 20); | 
|  | gfx::Point out_mv_pt(in_pt.x(), exclude.bottom() + 10); | 
|  |  | 
|  | // Motion starting in exclusion bounds is passed-through unchanged. | 
|  | { | 
|  | generator_->set_current_screen_location(in_pt); | 
|  | generator_->PressTouchId(0); | 
|  | AdvanceSimulatedTimePastPotentialTapDelay(); | 
|  | generator_->MoveTouchId(out_mv_pt, 0); | 
|  | generator_->ReleaseTouchId(0); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  | const EventList& captured_events = GetCapturedEvents(); | 
|  | ASSERT_EQ(3U, captured_events.size()); | 
|  | EXPECT_EQ(ui::EventType::kTouchPressed, captured_events[0]->type()); | 
|  | EXPECT_EQ(ui::EventType::kTouchMoved, captured_events[1]->type()); | 
|  | EXPECT_EQ(ui::EventType::kTouchReleased, captured_events[2]->type()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | } | 
|  |  | 
|  | // Complete motion outside exclusion is rewritten. | 
|  | { | 
|  | generator_->set_current_screen_location(out_pt); | 
|  | generator_->PressTouchId(0); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | generator_->MoveTouchId(out_mv_pt, 0); | 
|  | generator_->ReleaseTouchId(0); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  | const EventList& captured_events = GetCapturedEvents(); | 
|  | ASSERT_TRUE(captured_events.empty()); | 
|  | ASSERT_EQ(3U, GetTouchExplorePoints().size()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | } | 
|  |  | 
|  | // For a motion starting outside: outside events are rewritten, inside | 
|  | // events are discarded unless they end the motion. | 
|  | { | 
|  | // finger 0 down outside, moves inside. | 
|  | generator_->set_current_screen_location(out_pt); | 
|  | generator_->PressTouchId(0); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | generator_->MoveTouchId(out_mv_pt, 0); | 
|  | generator_->MoveTouchId(in_mv_pt, 0); | 
|  | ASSERT_TRUE(GetCapturedEvents().empty()); | 
|  | ASSERT_EQ(2U, GetTouchExplorePoints().size()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | // finger 1 down inside, moves outside | 
|  | generator_->set_current_screen_location(in_pt); | 
|  | generator_->PressTouchId(1); | 
|  | generator_->MoveTouchId(out_mv_pt, 1); | 
|  | generator_->ReleaseTouchId(1); | 
|  | ASSERT_TRUE(GetCapturedEvents().empty()); | 
|  | ASSERT_TRUE(GetTouchExplorePoints().empty()); | 
|  | EXPECT_FALSE(IsInNoFingersDownState()); | 
|  |  | 
|  | generator_->ReleaseTouchId(0); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  |  | 
|  | ASSERT_TRUE(GetCapturedEvents().empty()); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(TouchExplorationTest, SingleTapInLiftActivationArea) { | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | gfx::Rect lift_activation = BoundsOfRootWindowInDIP(); | 
|  | lift_activation.Inset(gfx::Insets::TLBR(0, 0, 30, 0)); | 
|  | SetLiftActivationBounds(lift_activation); | 
|  |  | 
|  | // Tap at one location, and get tap and mouse move events. | 
|  | gfx::Point tap_location = lift_activation.CenterPoint(); | 
|  |  | 
|  | // The user has to have previously selected something. | 
|  | SetTouchAccessibilityAnchorPoint(tap_location); | 
|  |  | 
|  | generator_->set_current_screen_location(tap_location); | 
|  | generator_->PressTouchId(1); | 
|  | generator_->ReleaseTouchId(1); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  |  | 
|  | const EventList& captured_events = GetCapturedEvents(); | 
|  | ASSERT_EQ(2U, captured_events.size()); | 
|  | EXPECT_EQ(ui::EventType::kTouchPressed, captured_events[0]->type()); | 
|  | EXPECT_EQ(ui::EventType::kTouchReleased, captured_events[1]->type()); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  |  | 
|  | gfx::Point out_tap_location(tap_location.x(), lift_activation.bottom() + 20); | 
|  | SetTouchAccessibilityAnchorPoint(out_tap_location); | 
|  | generator_->set_current_screen_location(out_tap_location); | 
|  | generator_->PressTouchId(1); | 
|  | generator_->ReleaseTouchId(1); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  |  | 
|  | const EventList& out_captured_events = GetCapturedEvents(); | 
|  | ASSERT_TRUE(out_captured_events.empty()); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | } | 
|  |  | 
|  | TEST_F(TouchExplorationTest, TouchExploreLiftInLiftActivationArea) { | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | gfx::Rect lift_activation = BoundsOfRootWindowInDIP(); | 
|  | lift_activation.Inset(gfx::Insets::TLBR(0, 0, 30, 0)); | 
|  | SetLiftActivationBounds(lift_activation); | 
|  |  | 
|  | // Explore at one location, and get tap and touch explore events. | 
|  | gfx::Point tap_location = lift_activation.CenterPoint(); | 
|  | EnterTouchExplorationModeAtLocation(tap_location); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | ASSERT_EQ(0U, delegate_.NumTouchTypeSounds()); | 
|  |  | 
|  | // A touch release should trigger a tap. | 
|  | ui::TouchEvent touch_explore_release( | 
|  | ui::EventType::kTouchReleased, tap_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&touch_explore_release); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  |  | 
|  | const EventList& captured_events = GetCapturedEvents(); | 
|  | ASSERT_EQ(2U, captured_events.size()); | 
|  | EXPECT_EQ(ui::EventType::kTouchPressed, captured_events[0]->type()); | 
|  | EXPECT_EQ(ui::EventType::kTouchReleased, captured_events[1]->type()); | 
|  | ASSERT_EQ(1U, delegate_.NumTouchTypeSounds()); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | delegate_.ResetCountersToZero(); | 
|  |  | 
|  | // Touch explore inside the activation bounds, but lift outside. | 
|  | gfx::Point out_tap_location(tap_location.x(), lift_activation.bottom() + 20); | 
|  | SetTouchAccessibilityAnchorPoint(out_tap_location); | 
|  | EnterTouchExplorationModeAtLocation(tap_location); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | ui::TouchEvent out_touch_explore_release( | 
|  | ui::EventType::kTouchReleased, out_tap_location, Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 0)); | 
|  | generator_->Dispatch(&out_touch_explore_release); | 
|  | AdvanceSimulatedTimePastTapDelay(); | 
|  |  | 
|  | const EventList& out_captured_events = GetCapturedEvents(); | 
|  | ASSERT_TRUE(out_captured_events.empty()); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | ASSERT_EQ(0U, delegate_.NumTouchTypeSounds()); | 
|  | } | 
|  |  | 
|  | // Ensure that any touch release events received after | 
|  | // TouchExplorationController starts up are canceled, if we haven't | 
|  | // seen any touch press events yet. http://crbug.com/751348 | 
|  | TEST_F(TouchExplorationTest, AlreadyHeldFingersGetCanceled) { | 
|  | generator_->PressTouch(); | 
|  | SwitchTouchExplorationMode(true); | 
|  | generator_->ReleaseTouch(); | 
|  |  | 
|  | std::vector<ui::LocatedEvent*> events = | 
|  | GetCapturedLocatedEventsOfType(ui::EventType::kTouchCancelled); | 
|  | ASSERT_EQ(1U, events.size()); | 
|  | } | 
|  |  | 
|  | // Ensure 3 or 4 finger tap gets recognized correctly. | 
|  | TEST_F(TouchExplorationTest, ThreeOrFourFingerTap) { | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | ui::TouchEvent press_id_1( | 
|  | ui::EventType::kTouchPressed, gfx::Point(100, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | ui::TouchEvent release_id_1( | 
|  | ui::EventType::kTouchReleased, gfx::Point(100, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 1)); | 
|  | ui::TouchEvent press_id_2( | 
|  | ui::EventType::kTouchPressed, gfx::Point(110, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 2)); | 
|  | ui::TouchEvent release_id_2( | 
|  | ui::EventType::kTouchReleased, gfx::Point(110, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 2)); | 
|  | ui::TouchEvent press_id_3( | 
|  | ui::EventType::kTouchPressed, gfx::Point(120, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 3)); | 
|  | ui::TouchEvent release_id_3( | 
|  | ui::EventType::kTouchReleased, gfx::Point(120, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 3)); | 
|  | ui::TouchEvent press_id_4( | 
|  | ui::EventType::kTouchPressed, gfx::Point(130, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 4)); | 
|  | ui::TouchEvent release_id_4( | 
|  | ui::EventType::kTouchReleased, gfx::Point(120, 200), Now(), | 
|  | ui::PointerDetails(ui::EventPointerType::kTouch, 4)); | 
|  |  | 
|  | // Three fingers down. | 
|  | generator_->Dispatch(&press_id_1); | 
|  | EXPECT_FALSE(IsInTwoFingerTapState()); | 
|  | generator_->Dispatch(&press_id_2); | 
|  | EXPECT_TRUE(IsInTwoFingerTapState()); | 
|  | generator_->Dispatch(&press_id_3); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  |  | 
|  | // Three fingers up. | 
|  | generator_->Dispatch(&release_id_1); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  | generator_->Dispatch(&release_id_2); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  |  | 
|  | ASSERT_EQ(ax::mojom::Gesture::kNone, delegate_.GetLastGesture()); | 
|  |  | 
|  | generator_->Dispatch(&release_id_3); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  |  | 
|  | ASSERT_EQ(ax::mojom::Gesture::kTap3, delegate_.GetLastGesture()); | 
|  | delegate_.ResetLastGesture(); | 
|  |  | 
|  | // Four fingers down. | 
|  | generator_->Dispatch(&press_id_1); | 
|  | EXPECT_FALSE(IsInTwoFingerTapState()); | 
|  | generator_->Dispatch(&press_id_2); | 
|  | EXPECT_TRUE(IsInTwoFingerTapState()); | 
|  | generator_->Dispatch(&press_id_3); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  | generator_->Dispatch(&press_id_4); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  |  | 
|  | // Four fingers up. | 
|  | generator_->Dispatch(&release_id_1); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  | generator_->Dispatch(&release_id_2); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  | generator_->Dispatch(&release_id_3); | 
|  | EXPECT_TRUE(IsInGestureInProgressState()); | 
|  |  | 
|  | ASSERT_EQ(ax::mojom::Gesture::kNone, delegate_.GetLastGesture()); | 
|  |  | 
|  | generator_->Dispatch(&release_id_4); | 
|  | EXPECT_TRUE(IsInNoFingersDownState()); | 
|  |  | 
|  | ASSERT_EQ(ax::mojom::Gesture::kTap4, delegate_.GetLastGesture()); | 
|  | } | 
|  |  | 
|  | // Triggers right-click when anchor point remains in position after delay. | 
|  | TEST_F(TouchExplorationTest, TriggersRightClickAfterDelay) { | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | gfx::Rect lift_activation = BoundsOfRootWindowInDIP(); | 
|  | SetLiftActivationBounds(lift_activation); | 
|  |  | 
|  | // Explore at one location. | 
|  | gfx::Point tap_location = lift_activation.CenterPoint(); | 
|  | EnterTouchExplorationModeAtLocation(tap_location); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | ASSERT_EQ(0U, delegate_.NumTouchTypeSounds()); | 
|  |  | 
|  | // Stay in the same anchor point after delay. | 
|  | AdvanceSimulatedTimePastLongPressDelay(); | 
|  |  | 
|  | // Any event should be rewritten as a right-mouse click. | 
|  | generator_->set_current_screen_location(tap_location); | 
|  | generator_->MoveTouch(tap_location); | 
|  |  | 
|  | const EventList& captured_events = GetCapturedEvents(); | 
|  | ASSERT_EQ(2U, captured_events.size()); | 
|  | EXPECT_EQ(ui::EventType::kMousePressed, captured_events[0]->type()); | 
|  | EXPECT_EQ(ui::EventType::kMouseReleased, captured_events[1]->type()); | 
|  | // We immediately go back to touch exploration so there will be a touch | 
|  | // explore event from the touch exploration. | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | EXPECT_EQ(1U, delegate_.NumLongPressRightClickSounds()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | delegate_.ResetCountersToZero(); | 
|  | } | 
|  |  | 
|  | // Does not trigger right-click when anchor point is not in lift activation | 
|  | // bounds. | 
|  | TEST_F(TouchExplorationTest, | 
|  | DoesNotTriggerRightClickInNonLiftActivationBounds) { | 
|  | SwitchTouchExplorationMode(true); | 
|  |  | 
|  | // Explore at one location. | 
|  | gfx::Point tap_location = BoundsOfRootWindowInDIP().CenterPoint(); | 
|  | EnterTouchExplorationModeAtLocation(tap_location); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | ASSERT_EQ(0U, delegate_.NumTouchTypeSounds()); | 
|  |  | 
|  | // Stay in the same anchor point after delay. | 
|  | AdvanceSimulatedTimePastLongPressDelay(); | 
|  |  | 
|  | generator_->set_current_screen_location(tap_location); | 
|  | generator_->MoveTouch(tap_location); | 
|  |  | 
|  | const EventList& captured_events = GetCapturedEvents(); | 
|  | ASSERT_TRUE(captured_events.empty()); | 
|  | ASSERT_EQ(1U, GetTouchExplorePoints().size()); | 
|  | EXPECT_EQ(0U, delegate_.NumLongPressRightClickSounds()); | 
|  | ClearCapturedAndGestureEvents(); | 
|  | delegate_.ResetCountersToZero(); | 
|  | } | 
|  |  | 
|  | }  // namespace ash |