Commit 2d3961fa authored by dmazzoni@chromium.org's avatar dmazzoni@chromium.org

Support double-tap to click in touch accessibility controller.

This refactors the touch accessibility controller to work like a state
machine, and implements support for double-tap to send a click to the
last location where the user did touch exploration previously. This also
means reimplementing existing touch interactions so that nothing is
passed through until the double-tap timeout passes or the user moves
outside the slop region.

BUG=377900

Review URL: https://codereview.chromium.org/296403011

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@275784 0039d316-1c4b-4281-b951-d872f2087c98
parent c01bd2b4
...@@ -55,7 +55,13 @@ IN_PROC_BROWSER_TEST_F(TouchExplorationTest, ToggleOnOff) { ...@@ -55,7 +55,13 @@ IN_PROC_BROWSER_TEST_F(TouchExplorationTest, ToggleOnOff) {
SwitchTouchExplorationMode(true); SwitchTouchExplorationMode(true);
aura::test::EventGenerator generator(root_window); aura::test::EventGenerator generator(root_window);
generator.set_current_location(gfx::Point(100, 200));
generator.PressTouchId(1); generator.PressTouchId(1);
// Since the touch exploration controller doesn't know if the user is
// double-tapping or not, touch exploration is only initiated if the
// user moves more than 8 pixels away from the initial location (the "slop"),
// or after 300 ms has elapsed.
generator.MoveTouchId(gfx::Point(109, 209), 1);
// Number of mouse events may be greater than 1 because of ET_MOUSE_ENTERED. // Number of mouse events may be greater than 1 because of ET_MOUSE_ENTERED.
EXPECT_GT(event_handler->num_mouse_events(), 0); EXPECT_GT(event_handler->num_mouse_events(), 0);
EXPECT_EQ(0, event_handler->num_touch_events()); EXPECT_EQ(0, event_handler->num_touch_events());
...@@ -68,7 +74,9 @@ IN_PROC_BROWSER_TEST_F(TouchExplorationTest, ToggleOnOff) { ...@@ -68,7 +74,9 @@ IN_PROC_BROWSER_TEST_F(TouchExplorationTest, ToggleOnOff) {
event_handler->Reset(); event_handler->Reset();
SwitchTouchExplorationMode(true); SwitchTouchExplorationMode(true);
generator.set_current_location(gfx::Point(500, 600));
generator.PressTouchId(2); generator.PressTouchId(2);
generator.MoveTouchId(gfx::Point(509, 609), 2);
EXPECT_GT(event_handler->num_mouse_events(), 0); EXPECT_GT(event_handler->num_mouse_events(), 0);
EXPECT_EQ(0, event_handler->num_touch_events()); EXPECT_EQ(0, event_handler->num_touch_events());
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop_proxy.h" #include "base/message_loop/message_loop_proxy.h"
#include "base/time/default_tick_clock.h"
#include "ui/aura/client/screen_position_client.h" #include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window_event_dispatcher.h" #include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h" #include "ui/aura/window_tree_host.h"
...@@ -69,8 +70,9 @@ class TestTouchEvent : public ui::TouchEvent { ...@@ -69,8 +70,9 @@ class TestTouchEvent : public ui::TouchEvent {
TestTouchEvent(ui::EventType type, TestTouchEvent(ui::EventType type,
const gfx::Point& root_location, const gfx::Point& root_location,
int touch_id, int touch_id,
int flags) int flags,
: TouchEvent(type, root_location, flags, touch_id, ui::EventTimeForNow(), base::TimeDelta timestamp)
: TouchEvent(type, root_location, flags, touch_id, timestamp,
1.0f, 1.0f, 1.0f, 1.0f) { 1.0f, 1.0f, 1.0f, 1.0f) {
} }
...@@ -87,7 +89,8 @@ EventGenerator::EventGenerator(Window* root_window) ...@@ -87,7 +89,8 @@ EventGenerator::EventGenerator(Window* root_window)
current_host_(delegate_->GetHostAt(current_location_)), current_host_(delegate_->GetHostAt(current_location_)),
flags_(0), flags_(0),
grab_(false), grab_(false),
async_(false) { async_(false),
tick_clock_(new base::DefaultTickClock()) {
} }
EventGenerator::EventGenerator(Window* root_window, const gfx::Point& point) EventGenerator::EventGenerator(Window* root_window, const gfx::Point& point)
...@@ -96,7 +99,8 @@ EventGenerator::EventGenerator(Window* root_window, const gfx::Point& point) ...@@ -96,7 +99,8 @@ EventGenerator::EventGenerator(Window* root_window, const gfx::Point& point)
current_host_(delegate_->GetHostAt(current_location_)), current_host_(delegate_->GetHostAt(current_location_)),
flags_(0), flags_(0),
grab_(false), grab_(false),
async_(false) { async_(false),
tick_clock_(new base::DefaultTickClock()) {
} }
EventGenerator::EventGenerator(Window* root_window, Window* window) EventGenerator::EventGenerator(Window* root_window, Window* window)
...@@ -105,7 +109,8 @@ EventGenerator::EventGenerator(Window* root_window, Window* window) ...@@ -105,7 +109,8 @@ EventGenerator::EventGenerator(Window* root_window, Window* window)
current_host_(delegate_->GetHostAt(current_location_)), current_host_(delegate_->GetHostAt(current_location_)),
flags_(0), flags_(0),
grab_(false), grab_(false),
async_(false) { async_(false),
tick_clock_(new base::DefaultTickClock()) {
} }
EventGenerator::EventGenerator(EventGeneratorDelegate* delegate) EventGenerator::EventGenerator(EventGeneratorDelegate* delegate)
...@@ -113,7 +118,8 @@ EventGenerator::EventGenerator(EventGeneratorDelegate* delegate) ...@@ -113,7 +118,8 @@ EventGenerator::EventGenerator(EventGeneratorDelegate* delegate)
current_host_(delegate_->GetHostAt(current_location_)), current_host_(delegate_->GetHostAt(current_location_)),
flags_(0), flags_(0),
grab_(false), grab_(false),
async_(false) { async_(false),
tick_clock_(new base::DefaultTickClock()) {
} }
EventGenerator::~EventGenerator() { EventGenerator::~EventGenerator() {
...@@ -219,7 +225,8 @@ void EventGenerator::PressTouch() { ...@@ -219,7 +225,8 @@ void EventGenerator::PressTouch() {
void EventGenerator::PressTouchId(int touch_id) { void EventGenerator::PressTouchId(int touch_id) {
TestTouchEvent touchev( TestTouchEvent touchev(
ui::ET_TOUCH_PRESSED, GetLocationInCurrentRoot(), touch_id, flags_); ui::ET_TOUCH_PRESSED, GetLocationInCurrentRoot(), touch_id, flags_,
Now());
Dispatch(&touchev); Dispatch(&touchev);
} }
...@@ -230,7 +237,8 @@ void EventGenerator::MoveTouch(const gfx::Point& point) { ...@@ -230,7 +237,8 @@ void EventGenerator::MoveTouch(const gfx::Point& point) {
void EventGenerator::MoveTouchId(const gfx::Point& point, int touch_id) { void EventGenerator::MoveTouchId(const gfx::Point& point, int touch_id) {
current_location_ = point; current_location_ = point;
TestTouchEvent touchev( TestTouchEvent touchev(
ui::ET_TOUCH_MOVED, GetLocationInCurrentRoot(), touch_id, flags_); ui::ET_TOUCH_MOVED, GetLocationInCurrentRoot(), touch_id, flags_,
Now());
Dispatch(&touchev); Dispatch(&touchev);
if (!grab_) if (!grab_)
...@@ -243,7 +251,8 @@ void EventGenerator::ReleaseTouch() { ...@@ -243,7 +251,8 @@ void EventGenerator::ReleaseTouch() {
void EventGenerator::ReleaseTouchId(int touch_id) { void EventGenerator::ReleaseTouchId(int touch_id) {
TestTouchEvent touchev( TestTouchEvent touchev(
ui::ET_TOUCH_RELEASED, GetLocationInCurrentRoot(), touch_id, flags_); ui::ET_TOUCH_RELEASED, GetLocationInCurrentRoot(), touch_id, flags_,
Now());
Dispatch(&touchev); Dispatch(&touchev);
} }
...@@ -263,7 +272,7 @@ void EventGenerator::GestureEdgeSwipe() { ...@@ -263,7 +272,7 @@ void EventGenerator::GestureEdgeSwipe() {
0, 0,
0, 0,
0, 0,
ui::EventTimeForNow(), Now(),
ui::GestureEventDetails(ui::ET_GESTURE_WIN8_EDGE_SWIPE, 0, 0), ui::GestureEventDetails(ui::ET_GESTURE_WIN8_EDGE_SWIPE, 0, 0),
0); 0);
Dispatch(&gesture); Dispatch(&gesture);
...@@ -274,7 +283,7 @@ void EventGenerator::GestureTapAt(const gfx::Point& location) { ...@@ -274,7 +283,7 @@ void EventGenerator::GestureTapAt(const gfx::Point& location) {
ui::TouchEvent press(ui::ET_TOUCH_PRESSED, ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
location, location,
kTouchId, kTouchId,
ui::EventTimeForNow()); Now());
Dispatch(&press); Dispatch(&press);
ui::TouchEvent release( ui::TouchEvent release(
...@@ -288,7 +297,7 @@ void EventGenerator::GestureTapDownAndUp(const gfx::Point& location) { ...@@ -288,7 +297,7 @@ void EventGenerator::GestureTapDownAndUp(const gfx::Point& location) {
ui::TouchEvent press(ui::ET_TOUCH_PRESSED, ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
location, location,
kTouchId, kTouchId,
ui::EventTimeForNow()); Now());
Dispatch(&press); Dispatch(&press);
ui::TouchEvent release( ui::TouchEvent release(
...@@ -312,7 +321,7 @@ void EventGenerator::GestureScrollSequenceWithCallback( ...@@ -312,7 +321,7 @@ void EventGenerator::GestureScrollSequenceWithCallback(
int steps, int steps,
const ScrollStepCallback& callback) { const ScrollStepCallback& callback) {
const int kTouchId = 5; const int kTouchId = 5;
base::TimeDelta timestamp = ui::EventTimeForNow(); base::TimeDelta timestamp = Now();
ui::TouchEvent press(ui::ET_TOUCH_PRESSED, start, kTouchId, timestamp); ui::TouchEvent press(ui::ET_TOUCH_PRESSED, start, kTouchId, timestamp);
Dispatch(&press); Dispatch(&press);
...@@ -367,7 +376,7 @@ void EventGenerator::GestureMultiFingerScrollWithDelays( ...@@ -367,7 +376,7 @@ void EventGenerator::GestureMultiFingerScrollWithDelays(
points[i] = start[i]; points[i] = start[i];
} }
base::TimeDelta press_time_first = ui::EventTimeForNow(); base::TimeDelta press_time_first = Now();
base::TimeDelta press_time[kMaxTouchPoints]; base::TimeDelta press_time[kMaxTouchPoints];
bool pressed[kMaxTouchPoints]; bool pressed[kMaxTouchPoints];
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
...@@ -417,8 +426,7 @@ void EventGenerator::ScrollSequence(const gfx::Point& start, ...@@ -417,8 +426,7 @@ void EventGenerator::ScrollSequence(const gfx::Point& start,
float y_offset, float y_offset,
int steps, int steps,
int num_fingers) { int num_fingers) {
base::TimeDelta timestamp = base::TimeDelta::FromInternalValue( base::TimeDelta timestamp = Now();
base::TimeTicks::Now().ToInternalValue());
ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL,
start, start,
timestamp, timestamp,
...@@ -457,7 +465,7 @@ void EventGenerator::ScrollSequence(const gfx::Point& start, ...@@ -457,7 +465,7 @@ void EventGenerator::ScrollSequence(const gfx::Point& start,
const std::vector<gfx::Point>& offsets, const std::vector<gfx::Point>& offsets,
int num_fingers) { int num_fingers) {
int steps = offsets.size(); int steps = offsets.size();
base::TimeDelta timestamp = ui::EventTimeForNow(); base::TimeDelta timestamp = Now();
ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL,
start, start,
timestamp, timestamp,
...@@ -501,6 +509,17 @@ void EventGenerator::Dispatch(ui::Event* event) { ...@@ -501,6 +509,17 @@ void EventGenerator::Dispatch(ui::Event* event) {
DoDispatchEvent(event, async_); DoDispatchEvent(event, async_);
} }
void EventGenerator::SetTickClock(scoped_ptr<base::TickClock> tick_clock) {
tick_clock_ = tick_clock.Pass();
}
base::TimeDelta EventGenerator::Now() {
// This is the same as what EventTimeForNow() does, but here we do it
// with a tick clock that can be replaced with a simulated clock for tests.
return base::TimeDelta::FromInternalValue(
tick_clock_->NowTicks().ToInternalValue());
}
void EventGenerator::DispatchKeyEvent(bool is_press, void EventGenerator::DispatchKeyEvent(bool is_press,
ui::KeyboardCode key_code, ui::KeyboardCode key_code,
int flags) { int flags) {
...@@ -638,6 +657,5 @@ void EventGenerator::DispatchNextPendingEvent() { ...@@ -638,6 +657,5 @@ void EventGenerator::DispatchNextPendingEvent() {
} }
} }
} // namespace test } // namespace test
} // namespace aura } // namespace aura
...@@ -11,12 +11,13 @@ ...@@ -11,12 +11,13 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
#include "ui/events/event_constants.h" #include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes.h" #include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/point.h" #include "ui/gfx/point.h"
namespace base { namespace base {
class TimeDelta; class TickClock;
} }
namespace ui { namespace ui {
...@@ -317,6 +318,12 @@ class EventGenerator { ...@@ -317,6 +318,12 @@ class EventGenerator {
current_host_ = host; current_host_ = host;
} }
// Specify an alternative tick clock to be used for simulating time in tests.
void SetTickClock(scoped_ptr<base::TickClock> tick_clock);
// Get the current time from the tick clock.
base::TimeDelta Now();
private: private:
// Dispatch a key event to the WindowEventDispatcher. // Dispatch a key event to the WindowEventDispatcher.
void DispatchKeyEvent(bool is_press, ui::KeyboardCode key_code, int flags); void DispatchKeyEvent(bool is_press, ui::KeyboardCode key_code, int flags);
...@@ -346,6 +353,7 @@ class EventGenerator { ...@@ -346,6 +353,7 @@ class EventGenerator {
std::list<ui::Event*> pending_events_; std::list<ui::Event*> pending_events_;
// Set to true to cause events to be posted asynchronously. // Set to true to cause events to be posted asynchronously.
bool async_; bool async_;
scoped_ptr<base::TickClock> tick_clock_;
DISALLOW_COPY_AND_ASSIGN(EventGenerator); DISALLOW_COPY_AND_ASSIGN(EventGenerator);
}; };
......
...@@ -7,14 +7,34 @@ ...@@ -7,14 +7,34 @@
#include "base/logging.h" #include "base/logging.h"
#include "ui/aura/client/cursor_client.h" #include "ui/aura/client/cursor_client.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h" #include "ui/aura/window_tree_host.h"
#include "ui/events/event.h" #include "ui/events/event.h"
#include "ui/events/event_processor.h"
namespace ui { namespace ui {
namespace {
// The default value for initial_touch_id_passthrough_mapping_ used
// when the user has not yet released any fingers yet, so there's no
// touch id remapping yet.
const int kTouchIdUnassigned = 0;
// The value for initial_touch_id_passthrough_mapping_ if the user has
// released the first finger but some other fingers are held down. In this
// state we don't do any touch id remapping, but we distinguish it from the
// kTouchIdUnassigned state because we don't want to assign
// initial_touch_id_passthrough_mapping_ a touch id anymore,
// until all fingers are released.
const int kTouchIdNone = -1;
} // namespace
TouchExplorationController::TouchExplorationController( TouchExplorationController::TouchExplorationController(
aura::Window* root_window) aura::Window* root_window)
: root_window_(root_window) { : root_window_(root_window),
initial_touch_id_passthrough_mapping_(kTouchIdUnassigned),
state_(NO_FINGERS_DOWN),
event_handler_for_testing_(NULL) {
CHECK(root_window); CHECK(root_window);
root_window->GetHost()->GetEventSource()->AddEventRewriter(this); root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
} }
...@@ -23,124 +43,287 @@ TouchExplorationController::~TouchExplorationController() { ...@@ -23,124 +43,287 @@ TouchExplorationController::~TouchExplorationController() {
root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
} }
void TouchExplorationController::CallTapTimerNowForTesting() {
DCHECK(tap_timer_.IsRunning());
tap_timer_.Stop();
OnTapTimerFired();
}
void TouchExplorationController::SetEventHandlerForTesting(
ui::EventHandler* event_handler_for_testing) {
event_handler_for_testing_ = event_handler_for_testing;
}
bool TouchExplorationController::IsInNoFingersDownStateForTesting() const {
return state_ == NO_FINGERS_DOWN;
}
ui::EventRewriteStatus TouchExplorationController::RewriteEvent( ui::EventRewriteStatus TouchExplorationController::RewriteEvent(
const ui::Event& event, const ui::Event& event,
scoped_ptr<ui::Event>* rewritten_event) { scoped_ptr<ui::Event>* rewritten_event) {
if (!event.IsTouchEvent()) if (!event.IsTouchEvent())
return ui::EVENT_REWRITE_CONTINUE; return ui::EVENT_REWRITE_CONTINUE;
const ui::TouchEvent& touch_event = const ui::TouchEvent& touch_event =
static_cast<const ui::TouchEvent&>(event); static_cast<const ui::TouchEvent&>(event);
// If the tap timer should have fired by now but hasn't, run it now and
// stop the timer. This is important so that behavior is consistent with
// the timestamps of the events, and not dependent on the granularity of
// the timer.
if (tap_timer_.IsRunning() &&
touch_event.time_stamp() - initial_press_->time_stamp() >
gesture_detector_config_.double_tap_timeout) {
tap_timer_.Stop();
OnTapTimerFired();
// Note: this may change the state. We should now continue and process
// this event under this new state.
}
const ui::EventType type = touch_event.type(); const ui::EventType type = touch_event.type();
const gfx::PointF& location = touch_event.location_f(); const gfx::PointF& location = touch_event.location_f();
const int touch_id = touch_event.touch_id(); const int touch_id = touch_event.touch_id();
const int flags = touch_event.flags();
// Always update touch ids and touch locations, so we can use those
// no matter what state we're in.
if (type == ui::ET_TOUCH_PRESSED) { if (type == ui::ET_TOUCH_PRESSED) {
touch_ids_.push_back(touch_id); current_touch_ids_.push_back(touch_id);
touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location)); touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
// If this is the first and only finger touching - rewrite the touch as a
// mouse move. Otherwise let the it go through as is.
if (touch_ids_.size() == 1) {
*rewritten_event = CreateMouseMoveEvent(location, flags);
EnterTouchToMouseMode();
return ui::EVENT_REWRITE_REWRITTEN;
}
return ui::EVENT_REWRITE_CONTINUE;
} else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
std::vector<int>::iterator it = std::vector<int>::iterator it = std::find(
std::find(touch_ids_.begin(), touch_ids_.end(), touch_id); current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
// We may fail to find the finger if the exploration mode was turned on
// while the user had some fingers touching the screen. We simply ignore // Can happen if touch exploration is enabled while fingers were down.
// those fingers for the purposes of event transformation. if (it == current_touch_ids_.end())
if (it == touch_ids_.end())
return ui::EVENT_REWRITE_CONTINUE; return ui::EVENT_REWRITE_CONTINUE;
const bool first_finger_released = it == touch_ids_.begin();
touch_ids_.erase(it);
int num_erased = touch_locations_.erase(touch_id);
DCHECK_EQ(num_erased, 1);
const int num_fingers_remaining = touch_ids_.size();
if (num_fingers_remaining == 0) {
*rewritten_event = CreateMouseMoveEvent(location, flags);
return ui::EVENT_REWRITE_REWRITTEN;
}
// If we are left with one finger - enter the mouse move mode. current_touch_ids_.erase(it);
const bool enter_mouse_move_mode = num_fingers_remaining == 1; touch_locations_.erase(touch_id);
} else if (type == ui::ET_TOUCH_MOVED) {
std::vector<int>::iterator it = std::find(
current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
if (!enter_mouse_move_mode && !first_finger_released) { // Can happen if touch exploration is enabled while fingers were down.
// No special handling needed. if (it == current_touch_ids_.end())
return ui::EVENT_REWRITE_CONTINUE; return ui::EVENT_REWRITE_CONTINUE;
}
// If the finger which was released was the first one, - we need to rewrite touch_locations_[*it] = location;
// the release event as a release of the was second / now first finger. }
// This is the finger which will now be getting "substracted".
if (first_finger_released) { // The rest of the processing depends on what state we're in.
int rewritten_release_id = touch_ids_[0]; switch(state_) {
const gfx::PointF& rewritten_release_location = case NO_FINGERS_DOWN:
touch_locations_[rewritten_release_id]; return InNoFingersDown(touch_event, rewritten_event);
ui::TouchEvent* rewritten_release_event = new ui::TouchEvent( case SINGLE_TAP_PRESSED:
ui::ET_TOUCH_RELEASED, return InSingleTapPressed(touch_event, rewritten_event);
rewritten_release_location, case SINGLE_TAP_RELEASED:
rewritten_release_id, return InSingleTapReleased(touch_event, rewritten_event);
event.time_stamp()); case DOUBLE_TAP_PRESSED:
rewritten_release_event->set_flags(touch_event.flags()); return InDoubleTapPressed(touch_event, rewritten_event);
rewritten_event->reset(rewritten_release_event); case TOUCH_EXPLORATION:
} else if (enter_mouse_move_mode) { return InTouchExploration(touch_event, rewritten_event);
// Dispatch the release event as is. case PASSTHROUGH_MINUS_ONE:
// TODO(mfomitchev): We can get rid of this clause once we have return InPassthroughMinusOne(touch_event, rewritten_event);
// EVENT_REWRITE_DISPATCH_ANOTHER working without having to set }
// rewritten_event.
rewritten_event->reset(new ui::TouchEvent(touch_event));
}
if (enter_mouse_move_mode) { NOTREACHED();
// Since we are entering the mouse move mode - also dispatch a mouse move return ui::EVENT_REWRITE_CONTINUE;
// event at the location of the one remaining finger. (num_fingers == 1) }
const gfx::PointF& mouse_move_location = touch_locations_[touch_ids_[0]];
next_dispatch_event_ = ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
CreateMouseMoveEvent(mouse_move_location, flags).Pass(); const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) {
return ui::EVENT_REWRITE_DISPATCH_ANOTHER; NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus TouchExplorationController::InNoFingersDown(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
const ui::EventType type = event.type();
if (type == ui::ET_TOUCH_PRESSED) {
initial_press_.reset(new TouchEvent(event));
tap_timer_.Start(FROM_HERE,
gesture_detector_config_.double_tap_timeout,
this,
&TouchExplorationController::OnTapTimerFired);
state_ = SINGLE_TAP_PRESSED;
return ui::EVENT_REWRITE_DISCARD;
}
NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
const ui::EventType type = event.type();
if (type == ui::ET_TOUCH_PRESSED) {
// Adding a second finger within the timeout period switches to
// passthrough.
state_ = PASSTHROUGH_MINUS_ONE;
return InPassthroughMinusOne(event, rewritten_event);
} else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
DCHECK_EQ(0U, current_touch_ids_.size());
state_ = SINGLE_TAP_RELEASED;
return EVENT_REWRITE_DISCARD;
} else if (type == ui::ET_TOUCH_MOVED) {
// If the user moves far enough from the initial touch location (outside
// the "slop" region, jump to the touch exploration mode early.
// TODO(evy, lisayin): Add gesture recognition here instead -
// we should probably jump to gesture mode here if the velocity is
// high enough, and touch exploration if the velocity is lower.
float delta = (event.location() - initial_press_->location()).Length();
if (delta > gesture_detector_config_.touch_slop) {
EnterTouchToMouseMode();
state_ = TOUCH_EXPLORATION;
return InTouchExploration(event, rewritten_event);
} }
return EVENT_REWRITE_DISCARD;
}
NOTREACHED() << "Unexpected event type received.";
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus TouchExplorationController::InSingleTapReleased(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
const ui::EventType type = event.type();
if (type == ui::ET_TOUCH_PRESSED) {
// This is the second tap in a double-tap (or double tap-hold).
// Rewrite at location of last touch exploration.
ui::TouchEvent* rewritten_press_event = new ui::TouchEvent(
ui::ET_TOUCH_PRESSED,
last_touch_exploration_location_,
event.touch_id(),
event.time_stamp());
rewritten_press_event->set_flags(event.flags());
rewritten_event->reset(rewritten_press_event);
state_ = DOUBLE_TAP_PRESSED;
return ui::EVENT_REWRITE_REWRITTEN;
}
NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus TouchExplorationController::InDoubleTapPressed(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
const ui::EventType type = event.type();
if (type == ui::ET_TOUCH_PRESSED) {
return ui::EVENT_REWRITE_DISCARD;
} else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
if (current_touch_ids_.size() != 0)
return EVENT_REWRITE_DISCARD;
// Rewrite at location of last touch exploration.
ui::TouchEvent* rewritten_release_event = new ui::TouchEvent(
ui::ET_TOUCH_RELEASED,
last_touch_exploration_location_,
event.touch_id(),
event.time_stamp());
rewritten_release_event->set_flags(event.flags());
rewritten_event->reset(rewritten_release_event);
ResetToNoFingersDown();
return ui::EVENT_REWRITE_REWRITTEN; return ui::EVENT_REWRITE_REWRITTEN;
} else if (type == ui::ET_TOUCH_MOVED) { } else if (type == ui::ET_TOUCH_MOVED) {
std::vector<int>::iterator it = return ui::EVENT_REWRITE_DISCARD;
std::find(touch_ids_.begin(), touch_ids_.end(), touch_id); }
// We may fail to find the finger if the exploration mode was turned on NOTREACHED() << "Unexpected event type received.";
// while the user had some fingers touching the screen. We simply ignore return ui::EVENT_REWRITE_CONTINUE;
// those fingers for the purposes of event transformation. }
if (it == touch_ids_.end())
return ui::EVENT_REWRITE_CONTINUE; ui::EventRewriteStatus TouchExplorationController::InTouchExploration(
touch_locations_[*it] = location; const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
if (touch_ids_.size() == 1) { const ui::EventType type = event.type();
// Touch moves are rewritten as mouse moves when there's only one finger if (type == ui::ET_TOUCH_PRESSED) {
// touching the screen. // Ignore any additional fingers when we're already in touch exploration
*rewritten_event = CreateMouseMoveEvent(location, flags).Pass(); // mode. TODO(evy, lisayin): Support "split-tap" here instead.
return ui::EVENT_REWRITE_REWRITTEN; return ui::EVENT_REWRITE_DISCARD;
} else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
if (current_touch_ids_.size() == 0)
ResetToNoFingersDown();
} else if (type != ui::ET_TOUCH_MOVED) {
NOTREACHED() << "Unexpected event type received.";
return ui::EVENT_REWRITE_CONTINUE;
}
// Rewrite as a mouse-move event.
*rewritten_event = CreateMouseMoveEvent(event.location(), event.flags());
last_touch_exploration_location_ = event.location();
return ui::EVENT_REWRITE_REWRITTEN;
}
ui::EventRewriteStatus TouchExplorationController::InPassthroughMinusOne(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
ui::EventType type = event.type();
gfx::PointF location = event.location_f();
if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
if (current_touch_ids_.size() == 0)
ResetToNoFingersDown();
if (initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
if (event.touch_id() == initial_press_->touch_id()) {
initial_touch_id_passthrough_mapping_ = kTouchIdNone;
} else {
// If the only finger now remaining is the first finger,
// rewrite as a move to the location of the first finger.
initial_touch_id_passthrough_mapping_ = event.touch_id();
ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent(
ui::ET_TOUCH_MOVED,
touch_locations_[initial_press_->touch_id()],
initial_touch_id_passthrough_mapping_,
event.time_stamp());
rewritten_passthrough_event->set_flags(event.flags());
rewritten_event->reset(rewritten_passthrough_event);
return ui::EVENT_REWRITE_REWRITTEN;
}
} }
if (touch_id == touch_ids_.front()) { }
// Touch moves of the first finger are discarded when there's more than
// one finger touching. if (event.touch_id() == initial_press_->touch_id()) {
if (initial_touch_id_passthrough_mapping_ == kTouchIdNone ||
initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
return ui::EVENT_REWRITE_DISCARD; return ui::EVENT_REWRITE_DISCARD;
} }
return ui::EVENT_REWRITE_CONTINUE;
ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent(
type,
location,
initial_touch_id_passthrough_mapping_,
event.time_stamp());
rewritten_passthrough_event->set_flags(event.flags());
rewritten_event->reset(rewritten_passthrough_event);
return ui::EVENT_REWRITE_REWRITTEN;
} }
NOTREACHED() << "Unexpected event type received.";
return ui::EVENT_REWRITE_CONTINUE; return ui::EVENT_REWRITE_CONTINUE;
} }
ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent( void TouchExplorationController::OnTapTimerFired() {
const ui::Event& last_event, if (state_ != SINGLE_TAP_RELEASED && state_ != SINGLE_TAP_PRESSED)
scoped_ptr<ui::Event>* new_event) { return;
CHECK(next_dispatch_event_);
DCHECK(last_event.IsTouchEvent()); if (state_ == SINGLE_TAP_RELEASED) {
*new_event = next_dispatch_event_.Pass(); ResetToNoFingersDown();
// Enter the mouse move mode if needed } else {
if ((*new_event)->IsMouseEvent())
EnterTouchToMouseMode(); EnterTouchToMouseMode();
return ui::EVENT_REWRITE_REWRITTEN; state_ = TOUCH_EXPLORATION;
}
scoped_ptr<ui::Event> mouse_move = CreateMouseMoveEvent(
initial_press_->location(), initial_press_->flags());
DispatchEvent(mouse_move.get());
last_touch_exploration_location_ = initial_press_->location();
}
void TouchExplorationController::DispatchEvent(ui::Event* event) {
if (event_handler_for_testing_) {
event_handler_for_testing_->OnEvent(event);
return;
}
ui::EventDispatchDetails result ALLOW_UNUSED =
root_window_->GetHost()->dispatcher()->OnEventFromSource(event);
} }
scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent( scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent(
...@@ -164,4 +347,11 @@ void TouchExplorationController::EnterTouchToMouseMode() { ...@@ -164,4 +347,11 @@ void TouchExplorationController::EnterTouchToMouseMode() {
cursor_client->HideCursor(); cursor_client->HideCursor();
} }
void TouchExplorationController::ResetToNoFingersDown() {
state_ = NO_FINGERS_DOWN;
initial_touch_id_passthrough_mapping_ = kTouchIdUnassigned;
if (tap_timer_.IsRunning())
tap_timer_.Stop();
}
} // namespace ui } // namespace ui
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
#ifndef UI_CHROMEOS_TOUCH_EXPLORATION_CONTROLLER_H_ #ifndef UI_CHROMEOS_TOUCH_EXPLORATION_CONTROLLER_H_
#define UI_CHROMEOS_TOUCH_EXPLORATION_CONTROLLER_H_ #define UI_CHROMEOS_TOUCH_EXPLORATION_CONTROLLER_H_
#include "base/timer/timer.h"
#include "base/values.h" #include "base/values.h"
#include "ui/chromeos/ui_chromeos_export.h" #include "ui/chromeos/ui_chromeos_export.h"
#include "ui/events/event_rewriter.h" #include "ui/events/event_rewriter.h"
#include "ui/events/gesture_detection/gesture_detector.h"
#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point.h"
namespace aura { namespace aura {
...@@ -17,17 +19,58 @@ class Window; ...@@ -17,17 +19,58 @@ class Window;
namespace ui { namespace ui {
class Event; class Event;
class EventHandler;
class TouchEvent;
// TouchExplorationController is used in tandem with "Spoken Feedback" to // TouchExplorationController is used in tandem with "Spoken Feedback" to
// make the touch UI accessible. TouchExplorationController rewrites the // make the touch UI accessible.
// incoming touch events as follows: //
// - When one finger is touching the screen, touch events are converted to mouse // ** Short version **
// moves. This is the "Touch Exploration Mode". (The idea is that mouse moves //
// will be subsequently used by another component to move focus between UI // At a high-level, single-finger events are used for accessibility -
// elements, and the elements will be read out to the user.) // exploring the screen gets turned into mouse moves (which can then be
// - When more than one finger is touching the screen, touches from the // spoken by an accessibility service running), a double-tap simulates a
// first (i.e. "oldest") finger are ignored, and the other touches go through // click, and gestures can be used to send high-level accessibility commands.
// as is. // When two or more fingers are pressed initially, from then on the events
// are passed through, but with the initial finger removed - so if you swipe
// down with two fingers, the running app will see a one-finger swipe.
//
// ** Long version **
//
// Here are the details of the implementation:
//
// When the first touch is pressed, a 300 ms grace period timer starts.
//
// If the user keeps their finger down for more than 300 ms and doesn't
// perform a supported accessibility gesture in that time (e.g. swipe right),
// they enter touch exploration mode, and all movements are translated into
// synthesized mouse move events.
//
// Also, if the user moves their single finger outside a certain slop region
// (without performing a gesture), they enter touch exploration mode earlier
// than 300 ms.
//
// If the user taps and releases their finger, after 300 ms from the initial
// touch, a single mouse move is fired.
//
// If the user double-taps, the second tap is passed through, allowing the
// user to click - however, the double-tap location is changed to the location
// of the last successful touch exploration - that allows the user to explore
// anywhere on the screen, hear its description, then double-tap anywhere
// to activate it.
//
// If the user adds a second finger during the grace period, they enter
// passthrough mode. In this mode, the first finger is ignored but all
// additional touch events are mostly passed through unmodified. So a
// two-finger scroll gets passed through as a one-finger scroll. However,
// once in passthrough mode, if one finger is released, the remaining fingers
// continue to pass through events, allowing the user to start a scroll
// with two fingers but finish it with one. Sometimes this requires rewriting
// the touch ids.
//
// Once either touch exploration or passthrough mode has been activated,
// it remains in that mode until all fingers have been released.
//
// The caller is expected to retain ownership of instances of this class and // The caller is expected to retain ownership of instances of this class and
// destroy them before |root_window| is destroyed. // destroy them before |root_window| is destroyed.
class UI_CHROMEOS_EXPORT TouchExplorationController : class UI_CHROMEOS_EXPORT TouchExplorationController :
...@@ -36,28 +79,120 @@ class UI_CHROMEOS_EXPORT TouchExplorationController : ...@@ -36,28 +79,120 @@ class UI_CHROMEOS_EXPORT TouchExplorationController :
explicit TouchExplorationController(aura::Window* root_window); explicit TouchExplorationController(aura::Window* root_window);
virtual ~TouchExplorationController(); virtual ~TouchExplorationController();
private: void CallTapTimerNowForTesting();
scoped_ptr<ui::Event> CreateMouseMoveEvent(const gfx::PointF& location, void SetEventHandlerForTesting(ui::EventHandler* event_handler_for_testing);
int flags); bool IsInNoFingersDownStateForTesting() const;
void EnterTouchToMouseMode();
private:
// Overridden from ui::EventRewriter // Overridden from ui::EventRewriter
virtual ui::EventRewriteStatus RewriteEvent( virtual ui::EventRewriteStatus RewriteEvent(
const ui::Event& event, scoped_ptr<ui::Event>* rewritten_event) OVERRIDE; const ui::Event& event, scoped_ptr<ui::Event>* rewritten_event) OVERRIDE;
virtual ui::EventRewriteStatus NextDispatchEvent( virtual ui::EventRewriteStatus NextDispatchEvent(
const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) OVERRIDE; const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) OVERRIDE;
// Event handlers based on the current state - see State, below.
ui::EventRewriteStatus InNoFingersDown(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event);
ui::EventRewriteStatus InSingleTapPressed(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event);
ui::EventRewriteStatus InSingleTapReleased(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event);
ui::EventRewriteStatus InDoubleTapPressed(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event);
ui::EventRewriteStatus InTouchExploration(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event);
ui::EventRewriteStatus InPassthroughMinusOne(
const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event);
// This timer is started every time we get the first press event, and
// it fires after the double-click timeout elapses (300 ms by default).
// If the user taps and releases within 300 ms and doesn't press again,
// we treat that as a single mouse move (touch exploration) event.
void OnTapTimerFired();
// Dispatch a new event outside of the event rewriting flow.
void DispatchEvent(ui::Event* event);
scoped_ptr<ui::Event> CreateMouseMoveEvent(const gfx::PointF& location,
int flags);
void EnterTouchToMouseMode();
// Set the state to NO_FINGERS_DOWN and reset any other fields to their
// default value.
void ResetToNoFingersDown();
enum State {
// No fingers are down and no events are pending.
NO_FINGERS_DOWN,
// A single finger is down, but we're not yet sure if this is going
// to be touch exploration or something else.
SINGLE_TAP_PRESSED,
// The user pressed and released a single finger - a tap - but we have
// to wait until the end of the grace period to allow the user to tap the
// second time. If the second tap doesn't occurs within the grace period,
// we dispatch a mouse move at the location of the first tap.
SINGLE_TAP_RELEASED,
// The user tapped once, and before the grace period expired, pressed
// one finger down to begin a double-tap, but has not released it yet.
DOUBLE_TAP_PRESSED,
// We're in touch exploration mode. Anything other than the first finger
// is ignored, and movements of the first finger are rewritten as mouse
// move events. This mode is entered if a single finger is pressed and
// after the grace period the user hasn't added a second finger or
// moved the finger outside of the slop region. We'll stay in this
// mode until all fingers are lifted.
TOUCH_EXPLORATION,
// The user placed two or more fingers down within the grace period.
// We're now in passthrough mode until all fingers are lifted. Initially
// the first finger is ignored and other fingers are passed through
// as-is. If a finger other than the initial one is the first to be
// released, we rewrite the first finger with the touch id of the finger
// that was released, from now on. The motivation for this is that if
// the user starts a scroll with 2 fingers, they can release either one
// and continue the scrolling.
PASSTHROUGH_MINUS_ONE,
};
aura::Window* root_window_;
// A set of touch ids for fingers currently touching the screen. // A set of touch ids for fingers currently touching the screen.
std::vector<int> touch_ids_; std::vector<int> current_touch_ids_;
// Map of touch ids to their last known location. // Map of touch ids to their last known location.
std::map<int, gfx::PointF> touch_locations_; std::map<int, gfx::PointF> touch_locations_;
// Initialized from RewriteEvent() and dispatched in NextDispatchEvent(). // The touch id that any events on the initial finger should be rewritten
scoped_ptr<ui::Event> next_dispatch_event_; // as in passthrough-minus-one mode. If kTouchIdUnassigned, events on the
// initial finger are discarded. If kTouchIdNone, the initial finger
// has been released and no more rewriting will be done.
int initial_touch_id_passthrough_mapping_;
aura::Window* root_window_; // The current state.
State state_;
// A copy of the event from the initial touch press.
scoped_ptr<ui::TouchEvent> initial_press_;
// The last location where we synthesized a mouse move event.
// When the user double-taps, we send the passed-through tap here.
gfx::PointF last_touch_exploration_location_;
// A timer to fire the mouse move event after the double-tap delay.
base::OneShotTimer<TouchExplorationController> tap_timer_;
// For testing only, an event handler to use for generated events
// outside of the normal event rewriting flow.
ui::EventHandler* event_handler_for_testing_;
// A default gesture detector config, so we can share the same
// timeout and pixel slop constants.
ui::GestureDetector::Config gesture_detector_config_;
DISALLOW_COPY_AND_ASSIGN(TouchExplorationController); DISALLOW_COPY_AND_ASSIGN(TouchExplorationController);
}; };
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "ui/chromeos/touch_exploration_controller.h" #include "ui/chromeos/touch_exploration_controller.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "ui/aura/client/cursor_client.h" #include "ui/aura/client/cursor_client.h"
#include "ui/aura/test/aura_test_base.h" #include "ui/aura/test/aura_test_base.h"
...@@ -60,7 +61,8 @@ class EventCapturer : public ui::EventHandler { ...@@ -60,7 +61,8 @@ class EventCapturer : public ui::EventHandler {
class TouchExplorationTest : public aura::test::AuraTestBase { class TouchExplorationTest : public aura::test::AuraTestBase {
public: public:
TouchExplorationTest() {} TouchExplorationTest()
: simulated_clock_(new base::SimpleTestTickClock()) {}
virtual ~TouchExplorationTest() {} virtual ~TouchExplorationTest() {}
virtual void SetUp() OVERRIDE { virtual void SetUp() OVERRIDE {
...@@ -69,6 +71,11 @@ class TouchExplorationTest : public aura::test::AuraTestBase { ...@@ -69,6 +71,11 @@ class TouchExplorationTest : public aura::test::AuraTestBase {
aura::test::AuraTestBase::SetUp(); aura::test::AuraTestBase::SetUp();
cursor_client_.reset(new aura::test::TestCursorClient(root_window())); cursor_client_.reset(new aura::test::TestCursorClient(root_window()));
root_window()->AddPreTargetHandler(&event_capturer_); root_window()->AddPreTargetHandler(&event_capturer_);
generator_.reset(new aura::test::EventGenerator(root_window()));
// The generator takes ownership of the clock.
generator_->SetTickClock(scoped_ptr<base::TickClock>(simulated_clock_));
cursor_client()->ShowCursor();
cursor_client()->DisableMouseEvents();
} }
virtual void TearDown() OVERRIDE { virtual void TearDown() OVERRIDE {
...@@ -78,23 +85,43 @@ class TouchExplorationTest : public aura::test::AuraTestBase { ...@@ -78,23 +85,43 @@ class TouchExplorationTest : public aura::test::AuraTestBase {
aura::test::AuraTestBase::TearDown(); aura::test::AuraTestBase::TearDown();
} }
protected:
aura::client::CursorClient* cursor_client() { return cursor_client_.get(); }
const ScopedVector<ui::LocatedEvent>& GetCapturedEvents() { const ScopedVector<ui::LocatedEvent>& GetCapturedEvents() {
return event_capturer_.captured_events(); return event_capturer_.captured_events();
} }
std::vector<ui::LocatedEvent*> GetCapturedEventsOfType(int type) {
const ScopedVector<ui::LocatedEvent>& all_events = GetCapturedEvents();
std::vector<ui::LocatedEvent*> events;
for (size_t i = 0; i < all_events.size(); ++i) {
if (type == all_events[i]->type())
events.push_back(all_events[i]);
}
return events;
}
void ClearCapturedEvents() { void ClearCapturedEvents() {
event_capturer_.Reset(); event_capturer_.Reset();
} }
protected: void AdvanceSimulatedTimePastTapDelay() {
aura::client::CursorClient* cursor_client() { return cursor_client_.get(); } simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
touch_exploration_controller_->CallTapTimerNowForTesting();
}
void SwitchTouchExplorationMode(bool on) { void SwitchTouchExplorationMode(bool on) {
if (!on && touch_exploration_controller_.get()) if (!on && touch_exploration_controller_.get()) {
touch_exploration_controller_.reset(); touch_exploration_controller_.reset();
else if (on && !touch_exploration_controller_.get()) } else if (on && !touch_exploration_controller_.get()) {
touch_exploration_controller_.reset( touch_exploration_controller_.reset(
new ui::TouchExplorationController(root_window())); new ui::TouchExplorationController(root_window()));
touch_exploration_controller_->SetEventHandlerForTesting(
&event_capturer_);
cursor_client()->ShowCursor();
cursor_client()->DisableMouseEvents();
}
} }
bool IsInTouchToMouseMode() { bool IsInTouchToMouseMode() {
...@@ -105,6 +132,22 @@ class TouchExplorationTest : public aura::test::AuraTestBase { ...@@ -105,6 +132,22 @@ class TouchExplorationTest : public aura::test::AuraTestBase {
!cursor_client->IsCursorVisible(); !cursor_client->IsCursorVisible();
} }
bool IsInNoFingersDownState() {
return touch_exploration_controller_->IsInNoFingersDownStateForTesting();
}
base::TimeDelta Now() {
// This is the same as what EventTimeForNow() does, but here we do it
// with our simulated clock.
return base::TimeDelta::FromInternalValue(
simulated_clock_->NowTicks().ToInternalValue());
}
scoped_ptr<aura::test::EventGenerator> generator_;
ui::GestureDetector::Config gesture_detector_config_;
// Owned by |generator_|.
base::SimpleTestTickClock* simulated_clock_;
private: private:
EventCapturer event_capturer_; EventCapturer event_capturer_;
scoped_ptr<ui::TouchExplorationController> touch_exploration_controller_; scoped_ptr<ui::TouchExplorationController> touch_exploration_controller_;
...@@ -150,50 +193,90 @@ void ConfirmEventsAreMouseAndEqual(ui::Event* e1, ui::Event* e2) { ...@@ -150,50 +193,90 @@ void ConfirmEventsAreMouseAndEqual(ui::Event* e1, ui::Event* e2) {
// events when running these tests as part of ui_unittests. We do get them when // events when running these tests as part of ui_unittests. We do get them when
// the tests are run as part of ash unit tests. // the tests are run as part of ash unit tests.
// Simple test to confirm one-finger touches are transformed into mouse moves. TEST_F(TouchExplorationTest, EntersTouchToMouseModeAfterPressAndDelay) {
TEST_F(TouchExplorationTest, OneFingerTouch) { 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); SwitchTouchExplorationMode(true);
cursor_client()->ShowCursor(); EXPECT_FALSE(IsInTouchToMouseMode());
cursor_client()->DisableMouseEvents(); generator_->set_current_location(gfx::Point(11, 12));
aura::test::EventGenerator generator(root_window()); generator_->PressTouch();
gfx::Point location_start = generator.current_location(); generator_->MoveTouch(gfx::Point(11 + half_slop, 12));
gfx::Point location_end(11, 12); EXPECT_FALSE(IsInTouchToMouseMode());
generator.PressTouch(); generator_->MoveTouch(gfx::Point(11, 12 + half_slop));
EXPECT_FALSE(IsInTouchToMouseMode());
generator_->MoveTouch(gfx::Point(11 + slop + 1, 12));
EXPECT_TRUE(IsInTouchToMouseMode()); EXPECT_TRUE(IsInTouchToMouseMode());
generator.MoveTouch(location_end); }
// Confirm the actual mouse moves are unaffected.
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 =
GetCapturedEventsOfType(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, ui::MouseEvent mouse_move(ui::ET_MOUSE_MOVED,
gfx::Point(13, 14), location_real_mouse_move,
gfx::Point(13, 14), location_real_mouse_move,
0, 0,
0); 0);
generator.Dispatch(&mouse_move); generator_->Dispatch(&mouse_move);
generator.ReleaseTouch(); generator_->ReleaseTouch();
const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents(); std::vector<ui::LocatedEvent*> events =
ScopedVector<ui::LocatedEvent>::const_iterator it; GetCapturedEventsOfType(ui::ET_MOUSE_MOVED);
// TODO(mfomitchev): mouse enter/exit events ASSERT_EQ(4U, events.size());
int num_mouse_moves = 0;
for (it = captured_events.begin(); it != captured_events.end(); ++it) { EXPECT_EQ(location_start, events[0]->location());
int type = (*it)->type(); EXPECT_TRUE(events[0]->flags() & ui::EF_IS_SYNTHESIZED);
// Ignore enter and exit mouse events synthesized when the mouse cursor is EXPECT_TRUE(events[0]->flags() & ui::EF_TOUCH_ACCESSIBILITY);
// shown or hidden.
if (type == ui::ET_MOUSE_ENTERED || type == ui::ET_MOUSE_EXITED) EXPECT_EQ(location_end, events[1]->location());
continue; EXPECT_TRUE(events[1]->flags() & ui::EF_IS_SYNTHESIZED);
EXPECT_EQ(ui::ET_MOUSE_MOVED, (*it)->type()); EXPECT_TRUE(events[1]->flags() & ui::EF_TOUCH_ACCESSIBILITY);
if (num_mouse_moves == 0)
EXPECT_EQ(location_start, (*it)->location()); // The real mouse move goes through.
if (num_mouse_moves == 1 || num_mouse_moves == 3) EXPECT_EQ(location_real_mouse_move, events[2]->location());
EXPECT_EQ(location_end, (*it)->location()); CONFIRM_EVENTS_ARE_MOUSE_AND_EQUAL(events[2], &mouse_move);
if (num_mouse_moves == 2) EXPECT_FALSE(events[2]->flags() & ui::EF_IS_SYNTHESIZED);
CONFIRM_EVENTS_ARE_MOUSE_AND_EQUAL(*it, &mouse_move); EXPECT_FALSE(events[2]->flags() & ui::EF_TOUCH_ACCESSIBILITY);
if (num_mouse_moves != 2) {
EXPECT_TRUE((*it)->flags() & ui::EF_IS_SYNTHESIZED); // The touch release gets written as a mouse move.
EXPECT_TRUE((*it)->flags() & ui::EF_TOUCH_ACCESSIBILITY); EXPECT_EQ(location_end, events[3]->location());
} EXPECT_TRUE(events[3]->flags() & ui::EF_IS_SYNTHESIZED);
num_mouse_moves++; EXPECT_TRUE(events[3]->flags() & ui::EF_TOUCH_ACCESSIBILITY);
} EXPECT_TRUE(IsInNoFingersDownState());
EXPECT_EQ(4, num_mouse_moves);
} }
// Turn the touch exploration mode on in the middle of the touch gesture. // Turn the touch exploration mode on in the middle of the touch gesture.
...@@ -201,12 +284,8 @@ TEST_F(TouchExplorationTest, OneFingerTouch) { ...@@ -201,12 +284,8 @@ TEST_F(TouchExplorationTest, OneFingerTouch) {
// turned on don't get rewritten. // turned on don't get rewritten.
TEST_F(TouchExplorationTest, TurnOnMidTouch) { TEST_F(TouchExplorationTest, TurnOnMidTouch) {
SwitchTouchExplorationMode(false); SwitchTouchExplorationMode(false);
cursor_client()->ShowCursor(); generator_->PressTouchId(1);
cursor_client()->DisableMouseEvents();
aura::test::EventGenerator generator(root_window());
generator.PressTouchId(1);
EXPECT_TRUE(cursor_client()->IsCursorVisible()); EXPECT_TRUE(cursor_client()->IsCursorVisible());
EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled());
ClearCapturedEvents(); ClearCapturedEvents();
// Enable touch exploration mode while the first finger is touching the // Enable touch exploration mode while the first finger is touching the
...@@ -217,8 +296,8 @@ TEST_F(TouchExplorationTest, TurnOnMidTouch) { ...@@ -217,8 +296,8 @@ TEST_F(TouchExplorationTest, TurnOnMidTouch) {
ui::TouchEvent touch_move(ui::ET_TOUCH_MOVED, ui::TouchEvent touch_move(ui::ET_TOUCH_MOVED,
gfx::Point(11, 12), gfx::Point(11, 12),
1, 1,
ui::EventTimeForNow()); Now());
generator.Dispatch(&touch_move); generator_->Dispatch(&touch_move);
EXPECT_TRUE(cursor_client()->IsCursorVisible()); EXPECT_TRUE(cursor_client()->IsCursorVisible());
EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled()); EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled());
const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents(); const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents();
...@@ -227,9 +306,9 @@ TEST_F(TouchExplorationTest, TurnOnMidTouch) { ...@@ -227,9 +306,9 @@ TEST_F(TouchExplorationTest, TurnOnMidTouch) {
ClearCapturedEvents(); ClearCapturedEvents();
// The press from the second finger should get rewritten. // The press from the second finger should get rewritten.
generator.PressTouchId(2); generator_->PressTouchId(2);
AdvanceSimulatedTimePastTapDelay();
EXPECT_TRUE(IsInTouchToMouseMode()); EXPECT_TRUE(IsInTouchToMouseMode());
// TODO(mfomitchev): mouse enter/exit events
ScopedVector<ui::LocatedEvent>::const_iterator it; ScopedVector<ui::LocatedEvent>::const_iterator it;
for (it = captured_events.begin(); it != captured_events.end(); ++it) { for (it = captured_events.begin(); it != captured_events.end(); ++it) {
if ((*it)->type() == ui::ET_MOUSE_MOVED) if ((*it)->type() == ui::ET_MOUSE_MOVED)
...@@ -242,34 +321,33 @@ TEST_F(TouchExplorationTest, TurnOnMidTouch) { ...@@ -242,34 +321,33 @@ TEST_F(TouchExplorationTest, TurnOnMidTouch) {
ui::TouchEvent touch_release(ui::ET_TOUCH_RELEASED, ui::TouchEvent touch_release(ui::ET_TOUCH_RELEASED,
gfx::Point(11, 12), gfx::Point(11, 12),
1, 1,
ui::EventTimeForNow()); Now());
generator.Dispatch(&touch_release); generator_->Dispatch(&touch_release);
ASSERT_EQ(1u, captured_events.size()); ASSERT_EQ(1u, captured_events.size());
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_release); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_release);
ClearCapturedEvents(); ClearCapturedEvents();
// The move and release from the second finger should get rewritten. // The move and release from the second finger should get rewritten.
generator.MoveTouchId(gfx::Point(13, 14), 2); generator_->MoveTouchId(gfx::Point(13, 14), 2);
generator.ReleaseTouchId(2); generator_->ReleaseTouchId(2);
ASSERT_EQ(2u, captured_events.size()); ASSERT_EQ(2u, captured_events.size());
EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[0]->type()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[0]->type());
EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type()); EXPECT_EQ(ui::ET_MOUSE_MOVED, captured_events[1]->type());
EXPECT_TRUE(IsInNoFingersDownState());
} }
TEST_F(TouchExplorationTest, TwoFingerTouch) { TEST_F(TouchExplorationTest, TwoFingerTouch) {
SwitchTouchExplorationMode(true); SwitchTouchExplorationMode(true);
aura::test::EventGenerator generator(root_window()); generator_->PressTouchId(1);
generator.PressTouchId(1);
ClearCapturedEvents(); ClearCapturedEvents();
// Confirm events from the second finger go through as is. // Confirm events from the second finger go through as is.
cursor_client()->ShowCursor(); ui::TouchEvent touch_press(
cursor_client()->DisableMouseEvents(); ui::ET_TOUCH_PRESSED,
ui::TouchEvent touch_press(ui::ET_TOUCH_PRESSED, gfx::Point(10, 11),
gfx::Point(10, 11), 2,
2, Now());
ui::EventTimeForNow()); generator_->Dispatch(&touch_press);
generator.Dispatch(&touch_press);
EXPECT_TRUE(cursor_client()->IsCursorVisible()); EXPECT_TRUE(cursor_client()->IsCursorVisible());
EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled()); EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled());
const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents(); const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents();
...@@ -285,11 +363,12 @@ TEST_F(TouchExplorationTest, TwoFingerTouch) { ...@@ -285,11 +363,12 @@ TEST_F(TouchExplorationTest, TwoFingerTouch) {
} }
EXPECT_NE(captured_events.end(), it); EXPECT_NE(captured_events.end(), it);
ClearCapturedEvents(); ClearCapturedEvents();
ui::TouchEvent touch_move(ui::ET_TOUCH_MOVED, ui::TouchEvent touch_move(
gfx::Point(20, 21), ui::ET_TOUCH_MOVED,
2, gfx::Point(20, 21),
ui::EventTimeForNow()); 2,
generator.Dispatch(&touch_move); Now());
generator_->Dispatch(&touch_move);
ASSERT_EQ(1u, captured_events.size()); ASSERT_EQ(1u, captured_events.size());
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_move); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_move);
ClearCapturedEvents(); ClearCapturedEvents();
...@@ -300,7 +379,7 @@ TEST_F(TouchExplorationTest, TwoFingerTouch) { ...@@ -300,7 +379,7 @@ TEST_F(TouchExplorationTest, TwoFingerTouch) {
gfx::Point(13, 14), gfx::Point(13, 14),
0, 0,
0); 0);
generator.Dispatch(&mouse_move); generator_->Dispatch(&mouse_move);
// TODO(mfomitchev): mouse enter/exit events // TODO(mfomitchev): mouse enter/exit events
// Ignore synthesized ET_MOUSE_ENTERED/ET_MOUSE_EXITED // Ignore synthesized ET_MOUSE_ENTERED/ET_MOUSE_EXITED
for (it = captured_events.begin(); it != captured_events.end(); ++it) { for (it = captured_events.begin(); it != captured_events.end(); ++it) {
...@@ -312,87 +391,71 @@ TEST_F(TouchExplorationTest, TwoFingerTouch) { ...@@ -312,87 +391,71 @@ TEST_F(TouchExplorationTest, TwoFingerTouch) {
EXPECT_NE(captured_events.end(), it); EXPECT_NE(captured_events.end(), it);
ClearCapturedEvents(); ClearCapturedEvents();
// Have some other fingers touch/move/release
generator.PressTouchId(3);
generator.PressTouchId(4);
generator.MoveTouchId(gfx::Point(30, 31), 3);
generator.ReleaseTouchId(3);
generator.ReleaseTouchId(4);
ClearCapturedEvents();
// Events from the first finger should not go through while the second finger // Events from the first finger should not go through while the second finger
// is touching. // is touching.
gfx::Point touch1_location = gfx::Point(15, 16); gfx::Point touch1_location = gfx::Point(15, 16);
generator.MoveTouchId(touch1_location, 1); generator_->MoveTouchId(touch1_location, 1);
EXPECT_EQ(0u, GetCapturedEvents().size()); EXPECT_EQ(0u, GetCapturedEvents().size());
EXPECT_TRUE(cursor_client()->IsCursorVisible()); EXPECT_TRUE(cursor_client()->IsCursorVisible());
EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled()); EXPECT_FALSE(cursor_client()->IsMouseEventsEnabled());
// A release of the second finger should go through, plus there should be a // A release of the second finger should be rewritten as a mouse move
// mouse move at |touch1_location| generated. // of that finger to the |touch1_location| and we stay in passthrough
ui::TouchEvent touch_release(ui::ET_TOUCH_RELEASED, // mode.
gfx::Point(25, 26), ui::TouchEvent touch_release(
2, ui::ET_TOUCH_RELEASED,
ui::EventTimeForNow()); gfx::Point(25, 26),
generator.Dispatch(&touch_release); 2,
EXPECT_TRUE(IsInTouchToMouseMode()); Now());
ASSERT_GE(captured_events.size(), 2u); generator_->Dispatch(&touch_release);
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch_release); EXPECT_FALSE(IsInTouchToMouseMode());
// TODO(mfomitchev): mouse enter/exit events ASSERT_EQ(captured_events.size(), 1u);
// Ignore synthesized ET_MOUSE_ENTERED/ET_MOUSE_EXITED EXPECT_EQ(touch1_location, captured_events[0]->location());
for (it = captured_events.begin(); it != captured_events.end(); ++it) {
if ((*it)->type() == ui::ET_MOUSE_MOVED) {
EXPECT_EQ(touch1_location, (*it)->location());
break;
}
}
EXPECT_NE(captured_events.end(), it);
} }
TEST_F(TouchExplorationTest, MultiFingerTouch) { TEST_F(TouchExplorationTest, MultiFingerTouch) {
SwitchTouchExplorationMode(true); SwitchTouchExplorationMode(true);
aura::test::EventGenerator generator(root_window()); generator_->PressTouchId(1);
generator.PressTouchId(1); generator_->PressTouchId(2);
generator.PressTouchId(2);
ClearCapturedEvents(); ClearCapturedEvents();
// Confirm events from other fingers go through as is. // Confirm events from other fingers go through as is.
ui::TouchEvent touch3_press(ui::ET_TOUCH_PRESSED, ui::TouchEvent touch3_press(ui::ET_TOUCH_PRESSED,
gfx::Point(10, 11), gfx::Point(10, 11),
3, 3,
ui::EventTimeForNow()); Now());
ui::TouchEvent touch3_move1(ui::ET_TOUCH_MOVED, ui::TouchEvent touch3_move1(ui::ET_TOUCH_MOVED,
gfx::Point(12, 13), gfx::Point(12, 13),
3, 3,
ui::EventTimeForNow()); Now());
ui::TouchEvent touch4_press(ui::ET_TOUCH_PRESSED, ui::TouchEvent touch4_press(ui::ET_TOUCH_PRESSED,
gfx::Point(20, 21), gfx::Point(20, 21),
4, 4,
ui::EventTimeForNow()); Now());
ui::TouchEvent touch3_move2(ui::ET_TOUCH_MOVED, ui::TouchEvent touch3_move2(ui::ET_TOUCH_MOVED,
gfx::Point(14, 15), gfx::Point(14, 15),
3, 3,
ui::EventTimeForNow()); Now());
ui::TouchEvent touch4_move(ui::ET_TOUCH_MOVED, ui::TouchEvent touch4_move(ui::ET_TOUCH_MOVED,
gfx::Point(22, 23), gfx::Point(22, 23),
4, 4,
ui::EventTimeForNow()); Now());
ui::TouchEvent touch3_release(ui::ET_TOUCH_RELEASED, ui::TouchEvent touch3_release(ui::ET_TOUCH_RELEASED,
gfx::Point(14, 15), gfx::Point(14, 15),
3, 3,
ui::EventTimeForNow()); Now());
ui::TouchEvent touch4_release(ui::ET_TOUCH_RELEASED, ui::TouchEvent touch4_release(ui::ET_TOUCH_RELEASED,
gfx::Point(22, 23), gfx::Point(22, 23),
4, 4,
ui::EventTimeForNow()); Now());
generator.Dispatch(&touch3_press); generator_->Dispatch(&touch3_press);
generator.Dispatch(&touch3_move1); generator_->Dispatch(&touch3_move1);
generator.Dispatch(&touch4_press); generator_->Dispatch(&touch4_press);
generator.Dispatch(&touch3_move2); generator_->Dispatch(&touch3_move2);
generator.Dispatch(&touch4_move); generator_->Dispatch(&touch4_move);
generator.Dispatch(&touch3_release); generator_->Dispatch(&touch3_release);
generator.Dispatch(&touch4_release); generator_->Dispatch(&touch4_release);
const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents(); const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents();
ASSERT_EQ(7u, captured_events.size()); ASSERT_EQ(7u, captured_events.size());
...@@ -401,59 +464,191 @@ TEST_F(TouchExplorationTest, MultiFingerTouch) { ...@@ -401,59 +464,191 @@ TEST_F(TouchExplorationTest, MultiFingerTouch) {
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[2], &touch4_press); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[2], &touch4_press);
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[3], &touch3_move2); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[3], &touch3_move2);
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[4], &touch4_move); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[4], &touch4_move);
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[5], &touch3_release);
// The release of finger 3 is rewritten as a move to the former location
// of finger 1.
EXPECT_EQ(ui::ET_TOUCH_MOVED, captured_events[5]->type());
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[6], &touch4_release); CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[6], &touch4_release);
} }
// Test the case when there are multiple fingers on the screen and the first // Test the case when there are multiple fingers on the screen and the first
// finger is released. This should be rewritten as a release of the second // finger is released. This should be ignored, but then the second finger
// finger. Additionally, if the second finger is the only finger left touching, // release should be passed through.
// we should enter a mouse move mode, and a mouse move event should be
// dispatched.
TEST_F(TouchExplorationTest, FirstFingerLifted) { TEST_F(TouchExplorationTest, FirstFingerLifted) {
SwitchTouchExplorationMode(true); SwitchTouchExplorationMode(true);
aura::test::EventGenerator generator(root_window()); generator_->PressTouchId(1);
generator.PressTouchId(1); generator_->PressTouchId(2);
generator.PressTouchId(2);
gfx::Point touch2_location(10, 11); gfx::Point touch2_location(10, 11);
generator.MoveTouchId(touch2_location, 2); generator_->MoveTouchId(touch2_location, 2);
generator.PressTouchId(3); generator_->PressTouchId(3);
gfx::Point touch3_location(20, 21); gfx::Point touch3_location(20, 21);
generator.MoveTouchId(touch3_location, 3); generator_->MoveTouchId(touch3_location, 3);
ClearCapturedEvents(); ClearCapturedEvents();
// Release of finger 1 should be rewritten as a release of finger 2. // Release of finger 1 should be ignored.
generator.ReleaseTouchId(1); generator_->ReleaseTouchId(1);
const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents(); const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents();
ASSERT_EQ(0u, captured_events.size());
// Move of finger 2 should be passed through.
gfx::Point touch2_new_location(20, 11);
generator_->MoveTouchId(touch2_new_location, 2);
ASSERT_EQ(1u, captured_events.size()); ASSERT_EQ(1u, captured_events.size());
EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[0]->type()); EXPECT_EQ(ui::ET_TOUCH_MOVED, captured_events[0]->type());
ui::TouchEvent* touch_event = EXPECT_EQ(touch2_new_location, captured_events[0]->location());
static_cast<ui::TouchEvent*>(captured_events[0]);
EXPECT_EQ(2, touch_event->touch_id());
EXPECT_EQ(touch2_location, touch_event->location());
ClearCapturedEvents(); ClearCapturedEvents();
// Release of finger 2 should be rewritten as a release of finger 3, plus // Release of finger 2 should be passed through.
// we should enter the mouse move mode and a mouse move event should be ui::TouchEvent touch2_release(
// dispatched. ui::ET_TOUCH_RELEASED,
cursor_client()->ShowCursor(); gfx::Point(14, 15),
cursor_client()->DisableMouseEvents(); 2,
generator.ReleaseTouchId(2); Now());
EXPECT_TRUE(IsInTouchToMouseMode()); generator_->Dispatch(&touch2_release);
ASSERT_GE(2u, captured_events.size()); ASSERT_EQ(1u, captured_events.size());
CONFIRM_EVENTS_ARE_TOUCH_AND_EQUAL(captured_events[0], &touch2_release);
}
// Test the case when there are multiple fingers on the screen and the
// second finger is released. This should be rewritten as a move to the
// location of the first finger.
TEST_F(TouchExplorationTest, SecondFingerLifted) {
SwitchTouchExplorationMode(true);
gfx::Point touch1_location(0, 11);
generator_->set_current_location(touch1_location);
generator_->PressTouchId(1);
generator_->PressTouchId(2);
gfx::Point touch2_location(10, 11);
generator_->MoveTouchId(touch2_location, 2);
generator_->PressTouchId(3);
gfx::Point touch3_location(20, 21);
generator_->MoveTouchId(touch3_location, 3);
ClearCapturedEvents();
// Release of finger 2 should be rewritten as a move to the location
// of the first finger.
generator_->ReleaseTouchId(2);
const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents();
ASSERT_EQ(1u, captured_events.size());
EXPECT_EQ(ui::ET_TOUCH_MOVED, captured_events[0]->type());
EXPECT_EQ(2, static_cast<ui::TouchEvent*>(captured_events[0])->touch_id());
EXPECT_EQ(touch1_location, captured_events[0]->location());
ClearCapturedEvents();
// Move of finger 1 should be rewritten as a move of finger 2.
gfx::Point touch1_new_location(0, 41);
generator_->MoveTouchId(touch1_new_location, 1);
ASSERT_EQ(1u, captured_events.size());
EXPECT_EQ(ui::ET_TOUCH_MOVED, captured_events[0]->type());
EXPECT_EQ(2, static_cast<ui::TouchEvent*>(captured_events[0])->touch_id());
EXPECT_EQ(touch1_new_location, captured_events[0]->location());
ClearCapturedEvents();
// Release of finger 1 should be rewritten as release of finger 2.
gfx::Point touch1_final_location(0, 41);
ui::TouchEvent touch1_release(
ui::ET_TOUCH_RELEASED,
touch1_final_location,
1,
Now());
generator_->Dispatch(&touch1_release);
ASSERT_EQ(1u, captured_events.size());
EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[0]->type()); EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[0]->type());
touch_event = static_cast<ui::TouchEvent*>(captured_events[0]); EXPECT_EQ(2, static_cast<ui::TouchEvent*>(captured_events[0])->touch_id());
EXPECT_EQ(3, touch_event->touch_id()); EXPECT_EQ(touch1_final_location, captured_events[0]->location());
EXPECT_EQ(touch3_location, touch_event->location()); }
// TODO(mfomitchev): mouse enter/exit events
ScopedVector<ui::LocatedEvent>::const_iterator it; // If an event is received after the double-tap timeout has elapsed, but
for (it = captured_events.begin(); it != captured_events.end(); ++it) { // before the timer has fired, a mouse move should still be generated.
if ((*it)->type() == ui::ET_MOUSE_MOVED) { TEST_F(TouchExplorationTest, TimerFiresLateDuringTouchExploration) {
EXPECT_EQ(touch3_location, (*it)->location()); SwitchTouchExplorationMode(true);
break;
} // Send a press, then add another finger after the double-tap timeout.
} generator_->PressTouchId(1);
EXPECT_NE(captured_events.end(), it); simulated_clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
generator_->PressTouchId(2);
std::vector<ui::LocatedEvent*> events =
GetCapturedEventsOfType(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);
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 =
GetCapturedEventsOfType(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(11, 12);
generator_->set_current_location(tap_location);
generator_->PressTouch();
generator_->ReleaseTouch();
AdvanceSimulatedTimePastTapDelay();
std::vector<ui::LocatedEvent*> events =
GetCapturedEventsOfType(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();
const ScopedVector<ui::LocatedEvent>& captured_events = GetCapturedEvents();
ASSERT_EQ(2U, captured_events.size());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type());
EXPECT_EQ(tap_location, captured_events[0]->location());
EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type());
EXPECT_EQ(tap_location, captured_events[1]->location());
EXPECT_TRUE(IsInNoFingersDownState());
} }
} // namespace ui } // namespace ui
...@@ -218,6 +218,7 @@ ...@@ -218,6 +218,7 @@
'../chromeos/chromeos.gyp:chromeos', '../chromeos/chromeos.gyp:chromeos',
'aura/aura.gyp:aura_test_support', 'aura/aura.gyp:aura_test_support',
'chromeos/ui_chromeos.gyp:ui_chromeos', 'chromeos/ui_chromeos.gyp:ui_chromeos',
'events/events.gyp:gesture_detection',
], ],
'sources': [ 'sources': [
'chromeos/touch_exploration_controller_unittest.cc' 'chromeos/touch_exploration_controller_unittest.cc'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment