1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/chromeos/touch_exploration_controller.h"
6
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "ui/aura/client/cursor_client.h"
10 #include "ui/aura/window.h"
11 #include "ui/aura/window_event_dispatcher.h"
12 #include "ui/aura/window_tree_host.h"
13 #include "ui/events/event.h"
14 #include "ui/events/event_processor.h"
15
16 #define VLOG_STATE() if (VLOG_IS_ON(0)) VlogState(__func__)
17 #define VLOG_EVENT(event) if (VLOG_IS_ON(0)) VlogEvent(event, __func__)
18
19 namespace ui {
20
21 namespace {
22 // The default value for initial_touch_id_passthrough_mapping_ used
23 // when the user has not yet released any fingers yet, so there's no
24 // touch id remapping yet.
25 const int kTouchIdUnassigned = 0;
26
27 // The value for initial_touch_id_passthrough_mapping_ if the user has
28 // released the first finger but some other fingers are held down. In this
29 // state we don't do any touch id remapping, but we distinguish it from the
30 // kTouchIdUnassigned state because we don't want to assign
31 // initial_touch_id_passthrough_mapping_ a touch id anymore,
32 // until all fingers are released.
33 const int kTouchIdNone = -1;
34 } // namespace
35
TouchExplorationController(aura::Window * root_window)36 TouchExplorationController::TouchExplorationController(
37 aura::Window* root_window)
38 : root_window_(root_window),
39 initial_touch_id_passthrough_mapping_(kTouchIdUnassigned),
40 state_(NO_FINGERS_DOWN),
41 event_handler_for_testing_(NULL),
42 prev_state_(NO_FINGERS_DOWN) {
43 CHECK(root_window);
44 root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
45 }
46
~TouchExplorationController()47 TouchExplorationController::~TouchExplorationController() {
48 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
49 }
50
CallTapTimerNowForTesting()51 void TouchExplorationController::CallTapTimerNowForTesting() {
52 DCHECK(tap_timer_.IsRunning());
53 tap_timer_.Stop();
54 OnTapTimerFired();
55 }
56
SetEventHandlerForTesting(ui::EventHandler * event_handler_for_testing)57 void TouchExplorationController::SetEventHandlerForTesting(
58 ui::EventHandler* event_handler_for_testing) {
59 event_handler_for_testing_ = event_handler_for_testing;
60 }
61
IsInNoFingersDownStateForTesting() const62 bool TouchExplorationController::IsInNoFingersDownStateForTesting() const {
63 return state_ == NO_FINGERS_DOWN;
64 }
65
RewriteEvent(const ui::Event & event,scoped_ptr<ui::Event> * rewritten_event)66 ui::EventRewriteStatus TouchExplorationController::RewriteEvent(
67 const ui::Event& event,
68 scoped_ptr<ui::Event>* rewritten_event) {
69 if (!event.IsTouchEvent())
70 return ui::EVENT_REWRITE_CONTINUE;
71 const ui::TouchEvent& touch_event =
72 static_cast<const ui::TouchEvent&>(event);
73
74 // If the tap timer should have fired by now but hasn't, run it now and
75 // stop the timer. This is important so that behavior is consistent with
76 // the timestamps of the events, and not dependent on the granularity of
77 // the timer.
78 if (tap_timer_.IsRunning() &&
79 touch_event.time_stamp() - initial_press_->time_stamp() >
80 gesture_detector_config_.double_tap_timeout) {
81 tap_timer_.Stop();
82 OnTapTimerFired();
83 // Note: this may change the state. We should now continue and process
84 // this event under this new state.
85 }
86
87 const ui::EventType type = touch_event.type();
88 const gfx::PointF& location = touch_event.location_f();
89 const int touch_id = touch_event.touch_id();
90
91 // Always update touch ids and touch locations, so we can use those
92 // no matter what state we're in.
93 if (type == ui::ET_TOUCH_PRESSED) {
94 current_touch_ids_.push_back(touch_id);
95 touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
96 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
97 std::vector<int>::iterator it = std::find(
98 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
99
100 // Can happen if touch exploration is enabled while fingers were down.
101 if (it == current_touch_ids_.end())
102 return ui::EVENT_REWRITE_CONTINUE;
103
104 current_touch_ids_.erase(it);
105 touch_locations_.erase(touch_id);
106 } else if (type == ui::ET_TOUCH_MOVED) {
107 std::vector<int>::iterator it = std::find(
108 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
109
110 // Can happen if touch exploration is enabled while fingers were down.
111 if (it == current_touch_ids_.end())
112 return ui::EVENT_REWRITE_CONTINUE;
113
114 touch_locations_[*it] = location;
115 }
116 VLOG_STATE();
117 VLOG_EVENT(touch_event);
118 // The rest of the processing depends on what state we're in.
119 switch(state_) {
120 case NO_FINGERS_DOWN:
121 return InNoFingersDown(touch_event, rewritten_event);
122 case SINGLE_TAP_PRESSED:
123 return InSingleTapPressed(touch_event, rewritten_event);
124 case SINGLE_TAP_RELEASED:
125 return InSingleTapReleased(touch_event, rewritten_event);
126 case DOUBLE_TAP_PRESSED:
127 return InDoubleTapPressed(touch_event, rewritten_event);
128 case TOUCH_EXPLORATION:
129 return InTouchExploration(touch_event, rewritten_event);
130 case PASSTHROUGH_MINUS_ONE:
131 return InPassthroughMinusOne(touch_event, rewritten_event);
132 case TOUCH_EXPLORE_SECOND_PRESS:
133 return InTouchExploreSecondPress(touch_event, rewritten_event);
134 }
135
136 NOTREACHED();
137 return ui::EVENT_REWRITE_CONTINUE;
138 }
139
NextDispatchEvent(const ui::Event & last_event,scoped_ptr<ui::Event> * new_event)140 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
141 const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) {
142 NOTREACHED();
143 return ui::EVENT_REWRITE_CONTINUE;
144 }
145
InNoFingersDown(const ui::TouchEvent & event,scoped_ptr<ui::Event> * rewritten_event)146 ui::EventRewriteStatus TouchExplorationController::InNoFingersDown(
147 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
148 const ui::EventType type = event.type();
149 if (type == ui::ET_TOUCH_PRESSED) {
150 initial_press_.reset(new TouchEvent(event));
151 tap_timer_.Start(FROM_HERE,
152 gesture_detector_config_.double_tap_timeout,
153 this,
154 &TouchExplorationController::OnTapTimerFired);
155 state_ = SINGLE_TAP_PRESSED;
156 VLOG_STATE();
157 return ui::EVENT_REWRITE_DISCARD;
158 }
159
160 NOTREACHED();
161 return ui::EVENT_REWRITE_CONTINUE;
162 }
163
InSingleTapPressed(const ui::TouchEvent & event,scoped_ptr<ui::Event> * rewritten_event)164 ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed(
165 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
166 const ui::EventType type = event.type();
167
168 if (type == ui::ET_TOUCH_PRESSED) {
169 // Adding a second finger within the timeout period switches to
170 // passthrough.
171 state_ = PASSTHROUGH_MINUS_ONE;
172 return InPassthroughMinusOne(event, rewritten_event);
173 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
174 DCHECK_EQ(0U, current_touch_ids_.size());
175 state_ = SINGLE_TAP_RELEASED;
176 VLOG_STATE();
177 return EVENT_REWRITE_DISCARD;
178 } else if (type == ui::ET_TOUCH_MOVED) {
179 // If the user moves far enough from the initial touch location (outside
180 // the "slop" region, jump to the touch exploration mode early.
181 // TODO(evy, lisayin): Add gesture recognition here instead -
182 // we should probably jump to gesture mode here if the velocity is
183 // high enough, and touch exploration if the velocity is lower.
184 float delta = (event.location() - initial_press_->location()).Length();
185 if (delta > gesture_detector_config_.touch_slop) {
186 EnterTouchToMouseMode();
187 state_ = TOUCH_EXPLORATION;
188 VLOG_STATE();
189 return InTouchExploration(event, rewritten_event);
190 }
191
192 return EVENT_REWRITE_DISCARD;
193 }
194 NOTREACHED() << "Unexpected event type received.";
195 return ui::EVENT_REWRITE_CONTINUE;
196 }
197
InSingleTapReleased(const ui::TouchEvent & event,scoped_ptr<ui::Event> * rewritten_event)198 ui::EventRewriteStatus TouchExplorationController::InSingleTapReleased(
199 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
200 const ui::EventType type = event.type();
201 if (type == ui::ET_TOUCH_PRESSED) {
202 // This is the second tap in a double-tap (or double tap-hold).
203 // Rewrite at location of last touch exploration.
204 // If there is no touch exploration yet, discard instead.
205 if (!last_touch_exploration_) {
206 return ui::EVENT_REWRITE_DISCARD;
207 }
208 rewritten_event->reset(
209 new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
210 last_touch_exploration_->location(),
211 event.touch_id(),
212 event.time_stamp()));
213 (*rewritten_event)->set_flags(event.flags());
214 state_ = DOUBLE_TAP_PRESSED;
215 VLOG_STATE();
216 return ui::EVENT_REWRITE_REWRITTEN;
217 }
218 // If the previous press was discarded, we need to also handle its release.
219 if (type == ui::ET_TOUCH_RELEASED && !last_touch_exploration_) {
220 if (current_touch_ids_.size() == 0) {
221 state_ = NO_FINGERS_DOWN;
222 }
223 return ui::EVENT_REWRITE_DISCARD;
224 }
225 NOTREACHED();
226 return ui::EVENT_REWRITE_CONTINUE;
227 }
228
InDoubleTapPressed(const ui::TouchEvent & event,scoped_ptr<ui::Event> * rewritten_event)229 ui::EventRewriteStatus TouchExplorationController::InDoubleTapPressed(
230 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
231 const ui::EventType type = event.type();
232 if (type == ui::ET_TOUCH_PRESSED) {
233 return ui::EVENT_REWRITE_DISCARD;
234 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
235 if (current_touch_ids_.size() != 0)
236 return EVENT_REWRITE_DISCARD;
237
238 // Rewrite release at location of last touch exploration with the same
239 // id as the prevoius press.
240 rewritten_event->reset(
241 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
242 last_touch_exploration_->location(),
243 initial_press_->touch_id(),
244 event.time_stamp()));
245 (*rewritten_event)->set_flags(event.flags());
246 ResetToNoFingersDown();
247 return ui::EVENT_REWRITE_REWRITTEN;
248 } else if (type == ui::ET_TOUCH_MOVED) {
249 return ui::EVENT_REWRITE_DISCARD;
250 }
251 NOTREACHED() << "Unexpected event type received.";
252 return ui::EVENT_REWRITE_CONTINUE;
253 }
254
InTouchExploration(const ui::TouchEvent & event,scoped_ptr<ui::Event> * rewritten_event)255 ui::EventRewriteStatus TouchExplorationController::InTouchExploration(
256 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
257 const ui::EventType type = event.type();
258 if (type == ui::ET_TOUCH_PRESSED) {
259 // Handle split-tap.
260 initial_press_.reset(new TouchEvent(event));
261 if (tap_timer_.IsRunning())
262 tap_timer_.Stop();
263 rewritten_event->reset(
264 new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
265 last_touch_exploration_->location(),
266 event.touch_id(),
267 event.time_stamp()));
268 (*rewritten_event)->set_flags(event.flags());
269 state_ = TOUCH_EXPLORE_SECOND_PRESS;
270 VLOG_STATE();
271 return ui::EVENT_REWRITE_REWRITTEN;
272 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
273 if (current_touch_ids_.size() == 0)
274 ResetToNoFingersDown();
275 } else if (type != ui::ET_TOUCH_MOVED) {
276 NOTREACHED() << "Unexpected event type received.";
277 return ui::EVENT_REWRITE_CONTINUE;
278 }
279
280 // Rewrite as a mouse-move event.
281 *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags());
282 last_touch_exploration_.reset(new TouchEvent(event));
283 return ui::EVENT_REWRITE_REWRITTEN;
284 }
285
InPassthroughMinusOne(const ui::TouchEvent & event,scoped_ptr<ui::Event> * rewritten_event)286 ui::EventRewriteStatus TouchExplorationController::InPassthroughMinusOne(
287 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
288 ui::EventType type = event.type();
289 gfx::PointF location = event.location_f();
290
291 if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
292 if (current_touch_ids_.size() == 0)
293 ResetToNoFingersDown();
294
295 if (initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
296 if (event.touch_id() == initial_press_->touch_id()) {
297 initial_touch_id_passthrough_mapping_ = kTouchIdNone;
298 } else {
299 // If the only finger now remaining is the first finger,
300 // rewrite as a move to the location of the first finger.
301 initial_touch_id_passthrough_mapping_ = event.touch_id();
302 rewritten_event->reset(
303 new ui::TouchEvent(ui::ET_TOUCH_MOVED,
304 touch_locations_[initial_press_->touch_id()],
305 initial_touch_id_passthrough_mapping_,
306 event.time_stamp()));
307 (*rewritten_event)->set_flags(event.flags());
308 return ui::EVENT_REWRITE_REWRITTEN;
309 }
310 }
311 }
312
313 if (event.touch_id() == initial_press_->touch_id()) {
314 if (initial_touch_id_passthrough_mapping_ == kTouchIdNone ||
315 initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
316 return ui::EVENT_REWRITE_DISCARD;
317 }
318
319 rewritten_event->reset(
320 new ui::TouchEvent(type,
321 location,
322 initial_touch_id_passthrough_mapping_,
323 event.time_stamp()));
324 (*rewritten_event)->set_flags(event.flags());
325 return ui::EVENT_REWRITE_REWRITTEN;
326 }
327
328 return ui::EVENT_REWRITE_CONTINUE;
329 }
330
InTouchExploreSecondPress(const ui::TouchEvent & event,scoped_ptr<ui::Event> * rewritten_event)331 ui::EventRewriteStatus TouchExplorationController::InTouchExploreSecondPress(
332 const ui::TouchEvent& event,
333 scoped_ptr<ui::Event>* rewritten_event) {
334 ui::EventType type = event.type();
335 gfx::PointF location = event.location_f();
336 if (type == ui::ET_TOUCH_PRESSED) {
337 return ui::EVENT_REWRITE_DISCARD;
338 } else if (type == ui::ET_TOUCH_MOVED) {
339 // Currently this is a discard, but could be something like rotor
340 // in the future.
341 return ui::EVENT_REWRITE_DISCARD;
342 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
343 // If the touch exploration finger is lifted, there is no option to return
344 // to touch explore anymore. The remaining finger acts as a pending
345 // tap or long tap for the last touch explore location.
346 if (event.touch_id() == last_touch_exploration_->touch_id()){
347 state_ = DOUBLE_TAP_PRESSED;
348 VLOG_STATE();
349 return EVENT_REWRITE_DISCARD;
350 }
351
352 // Continue to release the touch only if the touch explore finger is the
353 // only finger remaining.
354 if (current_touch_ids_.size() != 1)
355 return EVENT_REWRITE_DISCARD;
356
357 // Continue to release the touch only if the touch explore finger is the
358 // only finger remaining.
359 if (current_touch_ids_.size() != 1)
360 return EVENT_REWRITE_DISCARD;
361
362 // Rewrite at location of last touch exploration.
363 rewritten_event->reset(
364 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
365 last_touch_exploration_->location(),
366 initial_press_->touch_id(),
367 event.time_stamp()));
368 (*rewritten_event)->set_flags(event.flags());
369 state_ = TOUCH_EXPLORATION;
370 VLOG_STATE();
371 return ui::EVENT_REWRITE_REWRITTEN;
372 }
373 NOTREACHED() << "Unexpected event type received.";
374 return ui::EVENT_REWRITE_CONTINUE;
375 }
376
OnTapTimerFired()377 void TouchExplorationController::OnTapTimerFired() {
378 if (state_ != SINGLE_TAP_RELEASED && state_ != SINGLE_TAP_PRESSED)
379 return;
380
381 if (state_ == SINGLE_TAP_RELEASED) {
382 ResetToNoFingersDown();
383 } else {
384 EnterTouchToMouseMode();
385 state_ = TOUCH_EXPLORATION;
386 VLOG_STATE();
387 }
388
389 scoped_ptr<ui::Event> mouse_move = CreateMouseMoveEvent(
390 initial_press_->location(), initial_press_->flags());
391 DispatchEvent(mouse_move.get());
392 last_touch_exploration_.reset(new TouchEvent(*initial_press_));
393 }
394
DispatchEvent(ui::Event * event)395 void TouchExplorationController::DispatchEvent(ui::Event* event) {
396 if (event_handler_for_testing_) {
397 event_handler_for_testing_->OnEvent(event);
398 return;
399 }
400
401 ui::EventDispatchDetails result ALLOW_UNUSED =
402 root_window_->GetHost()->dispatcher()->OnEventFromSource(event);
403 }
404
CreateMouseMoveEvent(const gfx::PointF & location,int flags)405 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent(
406 const gfx::PointF& location,
407 int flags) {
408 return scoped_ptr<ui::Event>(
409 new ui::MouseEvent(
410 ui::ET_MOUSE_MOVED,
411 location,
412 location,
413 flags | ui::EF_IS_SYNTHESIZED | ui::EF_TOUCH_ACCESSIBILITY,
414 0));
415 }
416
EnterTouchToMouseMode()417 void TouchExplorationController::EnterTouchToMouseMode() {
418 aura::client::CursorClient* cursor_client =
419 aura::client::GetCursorClient(root_window_);
420 if (cursor_client && !cursor_client->IsMouseEventsEnabled())
421 cursor_client->EnableMouseEvents();
422 if (cursor_client && cursor_client->IsCursorVisible())
423 cursor_client->HideCursor();
424 }
425
ResetToNoFingersDown()426 void TouchExplorationController::ResetToNoFingersDown() {
427 state_ = NO_FINGERS_DOWN;
428 initial_touch_id_passthrough_mapping_ = kTouchIdUnassigned;
429 VLOG_STATE();
430 if (tap_timer_.IsRunning())
431 tap_timer_.Stop();
432 }
433
VlogState(const char * function_name)434 void TouchExplorationController::VlogState(const char* function_name) {
435 if (prev_state_ == state_)
436 return;
437 prev_state_ = state_;
438 const char* state_string = EnumStateToString(state_);
439 VLOG(0) << "\n Function name: " << function_name
440 << "\n State: " << state_string;
441 }
442
VlogEvent(const ui::TouchEvent & touch_event,const char * function_name)443 void TouchExplorationController::VlogEvent(const ui::TouchEvent& touch_event,
444 const char* function_name) {
445 CHECK(touch_event.IsTouchEvent());
446 if (prev_event_ == NULL || prev_event_->type() != touch_event.type() ||
447 prev_event_->touch_id() != touch_event.touch_id()) {
448 const std::string type = EnumEventTypeToString(touch_event.type());
449 const gfx::PointF& location = touch_event.location_f();
450 const int touch_id = touch_event.touch_id();
451
452 VLOG(0) << "\n Function name: " << function_name
453 << "\n Event Type: " << type
454 << "\n Location: " << location.ToString()
455 << "\n Touch ID: " << touch_id
456 << "\n Number of fingers down: " << current_touch_ids_.size();
457 prev_event_.reset(new TouchEvent(touch_event));
458 }
459 }
460
EnumStateToString(State state)461 const char* TouchExplorationController::EnumStateToString(State state) {
462 switch (state) {
463 case NO_FINGERS_DOWN:
464 return "NO_FINGERS_DOWN";
465 case SINGLE_TAP_PRESSED:
466 return "SINGLE_TAP_PRESSED";
467 case SINGLE_TAP_RELEASED:
468 return "SINGLE_TAP_RELEASED";
469 case DOUBLE_TAP_PRESSED:
470 return "DOUBLE_TAP_PRESSED";
471 case TOUCH_EXPLORATION:
472 return "TOUCH_EXPLORATION";
473 case PASSTHROUGH_MINUS_ONE:
474 return "PASSTHROUGH_MINUS_ONE";
475 case TOUCH_EXPLORE_SECOND_PRESS:
476 return "TOUCH_EXPLORE_SECOND_PRESS";
477 }
478 return "Not a state";
479 }
480
EnumEventTypeToString(ui::EventType type)481 std::string TouchExplorationController::EnumEventTypeToString(
482 ui::EventType type) {
483 // Add more cases later. For now, these are the most frequently seen
484 // event types.
485 switch (type) {
486 case ET_TOUCH_RELEASED:
487 return "ET_TOUCH_RELEASED";
488 case ET_TOUCH_PRESSED:
489 return "ET_TOUCH_PRESSED";
490 case ET_TOUCH_MOVED:
491 return "ET_TOUCH_MOVED";
492 case ET_TOUCH_CANCELLED:
493 return "ET_TOUCH_CANCELLED";
494 default:
495 return base::IntToString(type);
496 }
497 }
498
499 } // namespace ui
500