• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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