1 // Copyright 2018 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 // Copied with modifications from //ash/accessibility, refactored for use in
6 // chromecast.
7 
8 #include "chromecast/browser/accessibility/touch_exploration_controller.h"
9 
10 #include <algorithm>
11 #include <string>
12 #include <utility>
13 
14 #include "base/bind.h"
15 #include "base/logging.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "chromecast/base/cast_features.h"
18 #include "chromecast/base/chromecast_switches.h"
19 #include "ui/aura/client/cursor_client.h"
20 #include "ui/aura/window.h"
21 #include "ui/aura/window_event_dispatcher.h"
22 #include "ui/aura/window_tree_host.h"
23 #include "ui/events/event.h"
24 #include "ui/events/event_processor.h"
25 #include "ui/events/event_utils.h"
26 #include "ui/gfx/geometry/rect.h"
27 
28 #define SET_STATE(state) SetState(state, __func__)
29 #define DVLOG_EVENT(event)             \
30   if (DCHECK_IS_ON() && VLOG_IS_ON(1)) \
31   VlogEvent(event, __func__)
32 
33 namespace chromecast {
34 
35 namespace {
36 // TODO(rmrossi): Unify with identical values in SideSwipeDetector.
37 
38 // The number of pixels from the very left or right of the screen to consider as
39 // a valid origin for the left or right swipe gesture.
40 constexpr int kDefaultSideGestureStartWidth = 35;
41 
42 // The number of pixels from the very top or bottom of the screen to consider as
43 // a valid origin for the top or bottom swipe gesture.
44 constexpr int kDefaultSideGestureStartHeight = 35;
45 
46 }  // namespace
47 
48 namespace shell {
49 
SetTouchAccessibilityFlag(ui::Event * event)50 void SetTouchAccessibilityFlag(ui::Event* event) {
51   // This flag is used to identify mouse move events that were generated from
52   // touch exploration in Chrome code.
53   event->set_flags(event->flags() | ui::EF_TOUCH_ACCESSIBILITY);
54 }
55 
TouchExplorationController(aura::Window * root_window,TouchExplorationControllerDelegate * delegate,AccessibilitySoundPlayer * accessibility_sound_player)56 TouchExplorationController::TouchExplorationController(
57     aura::Window* root_window,
58     TouchExplorationControllerDelegate* delegate,
59     AccessibilitySoundPlayer* accessibility_sound_player)
60     : root_window_(root_window),
61       delegate_(delegate),
62       accessibility_sound_player_(accessibility_sound_player),
63       state_(NO_FINGERS_DOWN),
64       anchor_point_state_(ANCHOR_POINT_NONE),
65       gesture_provider_(new ui::GestureProviderAura(this, this)),
66       prev_state_(NO_FINGERS_DOWN),
67       DVLOG_on_(true),
68       gesture_start_width_(GetSwitchValueInt(switches::kSystemGestureStartWidth,
69                                              kDefaultSideGestureStartWidth)),
70       gesture_start_height_(
71           GetSwitchValueInt(switches::kSystemGestureStartHeight,
72                             kDefaultSideGestureStartHeight)),
73       side_gesture_pass_through_(
74           chromecast::IsFeatureEnabled(kEnableSideGesturePassThrough)) {}
75 
~TouchExplorationController()76 TouchExplorationController::~TouchExplorationController() {}
77 
SetTouchAccessibilityAnchorPoint(const gfx::Point & anchor_point_dip)78 void TouchExplorationController::SetTouchAccessibilityAnchorPoint(
79     const gfx::Point& anchor_point_dip) {
80   gfx::Point native_point = anchor_point_dip;
81   anchor_point_dip_ = gfx::PointF(native_point.x(), native_point.y());
82   anchor_point_state_ = ANCHOR_POINT_EXPLICITLY_SET;
83 }
84 
SetExcludeBounds(const gfx::Rect & bounds)85 void TouchExplorationController::SetExcludeBounds(const gfx::Rect& bounds) {
86   exclude_bounds_ = bounds;
87 }
88 
RewriteEvent(const ui::Event & event,const Continuation continuation)89 ui::EventDispatchDetails TouchExplorationController::RewriteEvent(
90     const ui::Event& event,
91     const Continuation continuation) {
92   if (!event.IsTouchEvent()) {
93     if (event.IsKeyEvent()) {
94       const ui::KeyEvent& key_event = static_cast<const ui::KeyEvent&>(event);
95       DVLOG(1) << "\nKeyboard event: " << key_event.GetName()
96                << "\n Key code: " << key_event.key_code()
97                << ", Flags: " << key_event.flags()
98                << ", Is char: " << key_event.is_char();
99     }
100     return SendEvent(continuation, &event);
101   }
102   const ui::TouchEvent& touch_event = static_cast<const ui::TouchEvent&>(event);
103 
104   if (event.type() == ui::ET_TOUCH_PRESSED)
105     seen_press_ = true;
106 
107   // Touch events come through in screen pixels, but untransformed. This is the
108   // raw coordinate not yet mapped to the root window's coordinate system or the
109   // screen. Convert it into the root window's coordinate system, in DIP which
110   // is what the rest of this class expects.
111   gfx::Point location = touch_event.location();
112   gfx::Point root_location = touch_event.root_location();
113   root_window_->GetHost()->ConvertPixelsToDIP(&location);
114   root_window_->GetHost()->ConvertPixelsToDIP(&root_location);
115 
116   if (!exclude_bounds_.IsEmpty()) {
117     bool in_exclude_area = exclude_bounds_.Contains(location);
118     if (in_exclude_area) {
119       if (state_ == NO_FINGERS_DOWN)
120         return SendEvent(continuation, &event);
121       if (touch_event.type() == ui::ET_TOUCH_MOVED ||
122           touch_event.type() == ui::ET_TOUCH_PRESSED) {
123         return DiscardEvent(continuation);
124       }
125       // Otherwise, continue handling events. Basically, we want to let
126       // CANCELLED or RELEASE events through so this can get back to
127       // the NO_FINGERS_DOWN state.
128     }
129   }
130 
131   // If the tap timer should have fired by now but hasn't, run it now and
132   // stop the timer. This is important so that behavior is consistent with
133   // the timestamps of the events, and not dependent on the granularity of
134   // the timer.
135   if (tap_timer_.IsRunning() &&
136       touch_event.time_stamp() - most_recent_press_timestamp_ >
137           gesture_detector_config_.double_tap_timeout) {
138     tap_timer_.Stop();
139     OnTapTimerFired();
140     // Note: this may change the state. We should now continue and process
141     // this event under this new state.
142   }
143 
144   const ui::EventType type = touch_event.type();
145   const int touch_id = touch_event.pointer_details().id;
146 
147   // Always update touch ids and touch locations, so we can use those
148   // no matter what state we're in.
149   if (type == ui::ET_TOUCH_PRESSED) {
150     current_touch_ids_.push_back(touch_id);
151     touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
152   } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
153     std::vector<int>::iterator it = std::find(
154         current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
155 
156     // Can happen if touch exploration is enabled while fingers were down
157     // or if an additional press occurred within the exclusion bounds.
158     if (it == current_touch_ids_.end()) {
159       // If we get a RELEASE event and we've never seen a PRESS event
160       // since TouchExplorationController was instantiated, cancel the
161       // event so that touch gestures that enable spoken feedback
162       // don't accidentally trigger other behaviors on release.
163       if (!seen_press_) {
164         std::unique_ptr<ui::TouchEvent> new_event(new ui::TouchEvent(
165             ui::ET_TOUCH_CANCELLED, gfx::Point(), touch_event.time_stamp(),
166             touch_event.pointer_details()));
167         new_event->set_location(location);
168         new_event->set_root_location(root_location);
169         new_event->set_flags(touch_event.flags());
170         return SendEventFinally(continuation, new_event.get());
171       }
172 
173       // Otherwise just pass it through.
174       return SendEvent(continuation, &event);
175     }
176 
177     current_touch_ids_.erase(it);
178     touch_locations_.erase(touch_id);
179   } else if (type == ui::ET_TOUCH_MOVED) {
180     std::vector<int>::iterator it = std::find(
181         current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
182 
183     // Can happen if touch exploration is enabled while fingers were down.
184     if (it == current_touch_ids_.end())
185       return SendEvent(continuation, &event);
186 
187     touch_locations_[*it] = gfx::PointF(location);
188   } else {
189     NOTREACHED() << "Unexpected event type received: " << event.GetName();
190     return SendEvent(continuation, &event);
191   }
192   DVLOG_EVENT(touch_event);
193 
194   // Enter pass through mode when side gestures are set to pass-through.
195   if (side_gesture_pass_through_ && type == ui::ET_TOUCH_PRESSED &&
196       FindEdgesWithinInset(location, gesture_start_width_,
197                            gesture_start_height_) != NO_EDGE) {
198     // If we are already in pass-through, ignore additional presses
199     // or the other fingers will clobber our initial press.
200     if (state_ == ONE_FINGER_PASSTHROUGH) {
201       return DiscardEvent(continuation);
202     }
203 
204     SET_STATE(ONE_FINGER_PASSTHROUGH);
205     initial_press_ = std::make_unique<ui::TouchEvent>(touch_event);
206     passthrough_offset_ = gfx::Vector2dF(0, 0);
207     return SendEvent(continuation, &event);
208   }
209 
210   // In order to avoid accidentally double tapping when moving off the edge
211   // of the screen, the state will be rewritten to NoFingersDown.
212   if ((type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) &&
213       FindEdgesWithinInset(location, kLeavingScreenEdge, kLeavingScreenEdge) !=
214           NO_EDGE) {
215     if (DVLOG_on_)
216       DVLOG(1) << "Leaving screen";
217 
218     if (current_touch_ids_.size() == 0) {
219       SET_STATE(NO_FINGERS_DOWN);
220       if (DVLOG_on_) {
221         DVLOG(1) << "Reset to no fingers in Rewrite event because the touch  "
222                     "release or cancel was on the edge of the screen.";
223       }
224       if (side_gesture_pass_through_) {
225         // Don't discard event when side gesture pass through is enabled. It
226         // interferes with gesture detector logic further down the processing
227         // stack.
228         return SendEvent(continuation, &event);
229       }
230       return DiscardEvent(continuation);
231     }
232   }
233 
234   // We now need a TouchEvent that has its coordinates mapped into root window
235   // DIP.
236   ui::TouchEvent touch_event_dip = touch_event;
237   touch_event_dip.set_location(location);
238 
239   // If the user is in a gesture state, or if there is a possiblity that the
240   // user will enter it in the future, we send the event to the gesture
241   // provider so it can keep track of the state of the fingers. When the user
242   // leaves one of these states, SET_STATE will set the gesture provider to
243   // NULL.
244   if (gesture_provider_.get()) {
245     if (gesture_provider_->OnTouchEvent(&touch_event_dip)) {
246       gesture_provider_->OnTouchEventAck(
247           touch_event_dip.unique_event_id(), false /* event_consumed */,
248           false /* is_source_touch_event_set_blocking */);
249     }
250     ProcessGestureEvents();
251   }
252 
253   // The rest of the processing depends on what state we're in.
254   switch (state_) {
255     case NO_FINGERS_DOWN:
256       return InNoFingersDown(touch_event_dip, continuation);
257     case SINGLE_TAP_PRESSED:
258       return InSingleTapPressed(touch_event_dip, continuation);
259     case SINGLE_TAP_RELEASED:
260     case TOUCH_EXPLORE_RELEASED:
261       return InSingleTapOrTouchExploreReleased(touch_event_dip, continuation);
262     case DOUBLE_TAP_PENDING:
263       return InDoubleTapPending(touch_event_dip, continuation);
264     case TOUCH_RELEASE_PENDING:
265       return InTouchReleasePending(touch_event_dip, continuation);
266     case TOUCH_EXPLORATION:
267       return InTouchExploration(touch_event_dip, continuation);
268     case GESTURE_IN_PROGRESS:
269       return InGestureInProgress(touch_event_dip, continuation);
270     case TOUCH_EXPLORE_SECOND_PRESS:
271       return InTouchExploreSecondPress(touch_event_dip, continuation);
272     case ONE_FINGER_PASSTHROUGH:
273       return InOneFingerPassthrough(touch_event_dip, continuation);
274     case WAIT_FOR_NO_FINGERS:
275       return InWaitForNoFingers(touch_event_dip, continuation);
276     case TWO_FINGER_TAP:
277       return InTwoFingerTap(touch_event_dip, continuation);
278   }
279   NOTREACHED();
280   return SendEvent(continuation, &event);
281 }
282 
InNoFingersDown(const ui::TouchEvent & event,const Continuation continuation)283 ui::EventDispatchDetails TouchExplorationController::InNoFingersDown(
284     const ui::TouchEvent& event,
285     const Continuation continuation) {
286   const ui::EventType type = event.type();
287   if (type != ui::ET_TOUCH_PRESSED) {
288     NOTREACHED() << "Unexpected event type received: " << event.GetName();
289     return SendEvent(continuation, &event);
290   }
291 
292   initial_press_ = std::make_unique<ui::TouchEvent>(event);
293   initial_press_continuation_ = continuation;
294   most_recent_press_timestamp_ = initial_press_->time_stamp();
295   initial_presses_[event.pointer_details().id] = event.location();
296   last_unused_finger_event_ = std::make_unique<ui::TouchEvent>(event);
297   last_unused_finger_continuation_ = continuation;
298   StartTapTimer();
299   SET_STATE(SINGLE_TAP_PRESSED);
300   return DiscardEvent(continuation);
301 }
302 
InSingleTapPressed(const ui::TouchEvent & event,const Continuation continuation)303 ui::EventDispatchDetails TouchExplorationController::InSingleTapPressed(
304     const ui::TouchEvent& event,
305     const Continuation continuation) {
306   const ui::EventType type = event.type();
307 
308   if (type == ui::ET_TOUCH_PRESSED) {
309     initial_presses_[event.pointer_details().id] = event.location();
310     SET_STATE(TWO_FINGER_TAP);
311     return DiscardEvent(continuation);
312   }
313   if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
314     if (current_touch_ids_.size() == 0 &&
315         event.pointer_details().id == initial_press_->pointer_details().id) {
316       MaybeSendSimulatedTapInLiftActivationBounds(event, continuation);
317       SET_STATE(SINGLE_TAP_RELEASED);
318     } else if (current_touch_ids_.size() == 0) {
319       SET_STATE(NO_FINGERS_DOWN);
320     }
321     return DiscardEvent(continuation);
322   }
323   if (type == ui::ET_TOUCH_MOVED) {
324     float distance = (event.location() - initial_press_->location()).Length();
325     // If the user does not move far enough from the original position, then the
326     // resulting movement should not be considered to be a deliberate gesture or
327     // touch exploration.
328     if (distance <= gesture_detector_config_.touch_slop)
329       return DiscardEvent(continuation);
330 
331     float delta_time =
332         (event.time_stamp() - most_recent_press_timestamp_).InSecondsF();
333     float velocity = distance / delta_time;
334     if (DVLOG_on_) {
335       DVLOG(1) << "\n Delta time: " << delta_time << "\n Distance: " << distance
336                << "\n Velocity of click: " << velocity
337                << "\n Minimum swipe velocity: "
338                << gesture_detector_config_.minimum_swipe_velocity;
339     }
340 
341     // If the user moves fast enough from the initial touch location, start
342     // gesture detection. Otherwise, jump to the touch exploration mode early.
343     if (velocity > gesture_detector_config_.minimum_swipe_velocity) {
344       SET_STATE(GESTURE_IN_PROGRESS);
345       return InGestureInProgress(event, continuation);
346     }
347     anchor_point_state_ = ANCHOR_POINT_FROM_TOUCH_EXPLORATION;
348     EnterTouchToMouseMode();
349     SET_STATE(TOUCH_EXPLORATION);
350     return InTouchExploration(event, continuation);
351   }
352   NOTREACHED();
353   return SendEvent(continuation, &event);
354 }
355 
356 ui::EventDispatchDetails
InSingleTapOrTouchExploreReleased(const ui::TouchEvent & event,const Continuation continuation)357 TouchExplorationController::InSingleTapOrTouchExploreReleased(
358     const ui::TouchEvent& event,
359     const Continuation continuation) {
360   const ui::EventType type = event.type();
361   // If there is more than one finger down, then discard to wait until no
362   // fingers are down.
363   if (current_touch_ids_.size() > 1) {
364     SET_STATE(WAIT_FOR_NO_FINGERS);
365     return DiscardEvent(continuation);
366   }
367   if (type == ui::ET_TOUCH_PRESSED) {
368     // If there is no anchor point for synthesized events because the
369     // user hasn't touch-explored or focused anything yet, we can't
370     // send a click, so discard.
371     if (anchor_point_state_ == ANCHOR_POINT_NONE) {
372       tap_timer_.Stop();
373       return DiscardEvent(continuation);
374     }
375     // This is the second tap in a double-tap (or double tap-hold).
376     // We set the tap timer. If it fires before the user lifts their finger,
377     // one-finger passthrough begins. Otherwise, there is a touch press and
378     // release at the location of the last touch exploration.
379     SET_STATE(DOUBLE_TAP_PENDING);
380     // The old tap timer (from the initial click) is stopped if it is still
381     // going, and the new one is set.
382     tap_timer_.Stop();
383     StartTapTimer();
384     most_recent_press_timestamp_ = event.time_stamp();
385     // This will update as the finger moves before a possible passthrough, and
386     // will determine the offset.
387     last_unused_finger_event_.reset(new ui::TouchEvent(event));
388     last_unused_finger_continuation_ = continuation;
389     return DiscardEvent(continuation);
390   }
391   if (type == ui::ET_TOUCH_RELEASED &&
392       anchor_point_state_ == ANCHOR_POINT_NONE) {
393     // If the previous press was discarded, we need to also handle its
394     // release.
395     if (current_touch_ids_.size() == 0) {
396       SET_STATE(NO_FINGERS_DOWN);
397     }
398     return DiscardEvent(continuation);
399   }
400   if (type == ui::ET_TOUCH_MOVED) {
401     return DiscardEvent(continuation);
402   }
403   NOTREACHED();
404   return SendEvent(continuation, &event);
405 }
406 
InDoubleTapPending(const ui::TouchEvent & event,const Continuation continuation)407 ui::EventDispatchDetails TouchExplorationController::InDoubleTapPending(
408     const ui::TouchEvent& event,
409     const Continuation continuation) {
410   const ui::EventType type = event.type();
411   if (type == ui::ET_TOUCH_PRESSED) {
412     return DiscardEvent(continuation);
413   }
414   if (type == ui::ET_TOUCH_MOVED) {
415     // If the user moves far enough from the initial touch location (outside
416     // the "slop" region, jump to passthrough mode early.
417     float delta = (event.location() - initial_press_->location()).Length();
418     if (delta > gesture_detector_config_.double_tap_slop) {
419       tap_timer_.Stop();
420       OnTapTimerFired();
421     }
422     return DiscardEvent(continuation);
423   }
424   if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
425     if (current_touch_ids_.size() != 0)
426       return DiscardEvent(continuation);
427 
428     SendSimulatedClick(continuation);
429 
430     SET_STATE(NO_FINGERS_DOWN);
431     return DiscardEvent(continuation);
432   }
433   NOTREACHED();
434   return SendEvent(continuation, &event);
435 }
436 
InTouchReleasePending(const ui::TouchEvent & event,const Continuation continuation)437 ui::EventDispatchDetails TouchExplorationController::InTouchReleasePending(
438     const ui::TouchEvent& event,
439     const Continuation continuation) {
440   const ui::EventType type = event.type();
441   if (type == ui::ET_TOUCH_PRESSED || type == ui::ET_TOUCH_MOVED) {
442     return DiscardEvent(continuation);
443   }
444   if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
445     if (current_touch_ids_.size() != 0)
446       return DiscardEvent(continuation);
447 
448     SendSimulatedClick(continuation);
449     SET_STATE(NO_FINGERS_DOWN);
450     return DiscardEvent(continuation);
451   }
452   NOTREACHED();
453   return SendEvent(continuation, &event);
454 }
455 
InTouchExploration(const ui::TouchEvent & event,const Continuation continuation)456 ui::EventDispatchDetails TouchExplorationController::InTouchExploration(
457     const ui::TouchEvent& event,
458     const Continuation continuation) {
459   const ui::EventType type = event.type();
460   if (type == ui::ET_TOUCH_PRESSED) {
461     // Enter split-tap mode.
462     initial_press_ = std::make_unique<ui::TouchEvent>(event);
463     tap_timer_.Stop();
464     initial_press_continuation_ = continuation;
465     SET_STATE(TOUCH_EXPLORE_SECOND_PRESS);
466     return DiscardEvent(continuation);
467   }
468   if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
469     initial_press_ = std::make_unique<ui::TouchEvent>(event);
470     initial_press_continuation_ = continuation;
471     StartTapTimer();
472     most_recent_press_timestamp_ = event.time_stamp();
473     MaybeSendSimulatedTapInLiftActivationBounds(event, continuation);
474     SET_STATE(TOUCH_EXPLORE_RELEASED);
475   } else if (type != ui::ET_TOUCH_MOVED) {
476     NOTREACHED();
477     return SendEvent(continuation, &event);
478   }
479 
480   // Rewrite as a mouse-move event.
481   // |event| locations are in DIP; see |RewriteEvent|. We need to dispatch
482   // |screen coords.
483   gfx::PointF location_f(ConvertDIPToScreenInPixels(event.location_f()));
484   std::unique_ptr<ui::Event> new_event =
485       CreateMouseMoveEvent(location_f, event.flags());
486   SetTouchAccessibilityFlag(new_event.get());
487   last_touch_exploration_ = std::make_unique<ui::TouchEvent>(event);
488   if (anchor_point_state_ != ANCHOR_POINT_EXPLICITLY_SET)
489     anchor_point_dip_ = last_touch_exploration_->location_f();
490   return SendEventFinally(continuation, new_event.get());
491 }
492 
InGestureInProgress(const ui::TouchEvent & event,const Continuation continuation)493 ui::EventDispatchDetails TouchExplorationController::InGestureInProgress(
494     const ui::TouchEvent& event,
495     const Continuation continuation) {
496   // The events were sent to the gesture provider in RewriteEvent already.
497   // If no gesture is registered before the tap timer times out, the state
498   // will change to "wait for no fingers down" or "touch exploration" depending
499   // on the number of fingers down, and this function will stop being called.
500   if (current_touch_ids_.size() == 0) {
501     SET_STATE(NO_FINGERS_DOWN);
502   }
503   return DiscardEvent(continuation);
504 }
505 
InOneFingerPassthrough(const ui::TouchEvent & event,const Continuation continuation)506 ui::EventDispatchDetails TouchExplorationController::InOneFingerPassthrough(
507     const ui::TouchEvent& event,
508     const Continuation continuation) {
509   if (event.pointer_details().id != initial_press_->pointer_details().id) {
510     if (current_touch_ids_.size() == 0) {
511       SET_STATE(NO_FINGERS_DOWN);
512     }
513     return DiscardEvent(continuation);
514   }
515   // |event| locations are in DIP; see |RewriteEvent|. We need to dispatch
516   // screen coordinates.
517   gfx::PointF location_f(
518       ConvertDIPToScreenInPixels(event.location_f() - passthrough_offset_));
519   ui::TouchEvent new_event(event.type(), gfx::Point(), event.time_stamp(),
520                            event.pointer_details(), event.flags());
521   new_event.set_location_f(location_f);
522   new_event.set_root_location_f(location_f);
523   SetTouchAccessibilityFlag(&new_event);
524   if (current_touch_ids_.size() == 0) {
525     SET_STATE(NO_FINGERS_DOWN);
526   }
527   return SendEventFinally(continuation, &new_event);
528 }
529 
InTouchExploreSecondPress(const ui::TouchEvent & event,const Continuation continuation)530 ui::EventDispatchDetails TouchExplorationController::InTouchExploreSecondPress(
531     const ui::TouchEvent& event,
532     const Continuation continuation) {
533   ui::EventType type = event.type();
534   if (type == ui::ET_TOUCH_PRESSED) {
535     // A third finger being pressed means that a split tap can no longer go
536     // through. The user enters the wait state, Since there has already been
537     // a press dispatched when split tap began, the touch needs to be
538     // cancelled.
539     ui::TouchEvent new_event(ui::ET_TOUCH_CANCELLED, gfx::Point(),
540                              event.time_stamp(),
541                              initial_press_->pointer_details(), event.flags());
542     // TODO(dmazzoni): fix for multiple displays. http://crbug.com/616793
543     // |event| locations are in DIP; see |RewriteEvent|. We need to dispatch
544     // screen coordinates.
545     gfx::PointF location_f(ConvertDIPToScreenInPixels(anchor_point_dip_));
546     new_event.set_location_f(location_f);
547     new_event.set_root_location_f(location_f);
548     SetTouchAccessibilityFlag(&new_event);
549     SET_STATE(WAIT_FOR_NO_FINGERS);
550     return SendEventFinally(continuation, &new_event);
551   }
552   if (type == ui::ET_TOUCH_MOVED) {
553     // If the fingers have moved too far from their original locations,
554     // the user can no longer split tap.
555     ui::TouchEvent* original_touch;
556     if (event.pointer_details().id ==
557         last_touch_exploration_->pointer_details().id) {
558       original_touch = last_touch_exploration_.get();
559     } else if (event.pointer_details().id ==
560                initial_press_->pointer_details().id) {
561       original_touch = initial_press_.get();
562     } else {
563       NOTREACHED();
564       SET_STATE(WAIT_FOR_NO_FINGERS);
565       return DiscardEvent(continuation);
566     }
567     // Check the distance between the current finger location and the original
568     // location. The slop for this is a bit more generous since keeping two
569     // fingers in place is a bit harder. If the user has left the slop, the
570     // user enters the wait state.
571     if ((event.location_f() - original_touch->location_f()).Length() >
572         GetSplitTapTouchSlop()) {
573       SET_STATE(WAIT_FOR_NO_FINGERS);
574     }
575     return DiscardEvent(continuation);
576   }
577   if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
578     // If the touch exploration finger is lifted, there is no option to return
579     // to touch explore anymore. The remaining finger acts as a pending
580     // tap or long tap for the last touch explore location.
581     if (event.pointer_details().id ==
582         last_touch_exploration_->pointer_details().id) {
583       SET_STATE(TOUCH_RELEASE_PENDING);
584       return DiscardEvent(continuation);
585     }
586 
587     // Continue to release the touch only if the touch explore finger is the
588     // only finger remaining.
589     if (current_touch_ids_.size() != 1) {
590       return DiscardEvent(continuation);
591     }
592 
593     SendSimulatedClick(continuation);
594 
595     SET_STATE(TOUCH_EXPLORATION);
596     EnterTouchToMouseMode();
597     return DiscardEvent(continuation);
598   }
599   NOTREACHED();
600   return SendEvent(continuation, &event);
601 }
602 
InWaitForNoFingers(const ui::TouchEvent & event,const Continuation continuation)603 ui::EventDispatchDetails TouchExplorationController::InWaitForNoFingers(
604     const ui::TouchEvent& event,
605     const Continuation continuation) {
606   if (current_touch_ids_.size() == 0)
607     SET_STATE(NO_FINGERS_DOWN);
608   return DiscardEvent(continuation);
609 }
610 
SendSimulatedClick(const Continuation continuation)611 void TouchExplorationController::SendSimulatedClick(
612     const Continuation continuation) {
613   const gfx::Point location;
614   // For Chromecast, always send a simulated tap. NOTE: This differs
615   // from chromeos's touch exploration controller which always send an
616   // accessibility gesture.
617   delegate_->HandleTap(location);
618   SendSimulatedTap(continuation);
619 }
620 
SendSimulatedTap(const Continuation continuation)621 void TouchExplorationController::SendSimulatedTap(
622     const Continuation continuation) {
623   std::unique_ptr<ui::TouchEvent> touch_press;
624   touch_press.reset(new ui::TouchEvent(ui::ET_TOUCH_PRESSED, gfx::Point(),
625                                        Now(),
626                                        initial_press_->pointer_details()));
627   touch_press->set_location_f(anchor_point_dip_);
628   touch_press->set_root_location_f(anchor_point_dip_);
629   DispatchEvent(touch_press.get(), continuation);
630 
631   std::unique_ptr<ui::TouchEvent> touch_release;
632   touch_release.reset(new ui::TouchEvent(ui::ET_TOUCH_RELEASED, gfx::Point(),
633                                          Now(),
634                                          initial_press_->pointer_details()));
635   touch_release->set_location_f(anchor_point_dip_);
636   touch_release->set_root_location_f(anchor_point_dip_);
637   DispatchEvent(touch_release.get(), continuation);
638 }
639 
MaybeSendSimulatedTapInLiftActivationBounds(const ui::TouchEvent & event,const Continuation continuation)640 void TouchExplorationController::MaybeSendSimulatedTapInLiftActivationBounds(
641     const ui::TouchEvent& event,
642     const Continuation continuation) {
643   gfx::Point location = event.location();
644   gfx::Point anchor_location(anchor_point_dip_.x(), anchor_point_dip_.y());
645   if (lift_activation_bounds_.Contains(anchor_location.x(),
646                                        anchor_location.y()) &&
647       lift_activation_bounds_.Contains(location)) {
648     accessibility_sound_player_->PlayTouchTypeEarcon();
649     SendSimulatedTap(continuation);
650   }
651 }
652 
InTwoFingerTap(const ui::TouchEvent & event,const Continuation continuation)653 ui::EventDispatchDetails TouchExplorationController::InTwoFingerTap(
654     const ui::TouchEvent& event,
655     const Continuation continuation) {
656   ui::EventType type = event.type();
657   if (type == ui::ET_TOUCH_PRESSED) {
658     // This is now a three finger gesture.
659     SET_STATE(GESTURE_IN_PROGRESS);
660     return DiscardEvent(continuation);
661   }
662 
663   if (type == ui::ET_TOUCH_MOVED) {
664     // Determine if it was a swipe.
665     gfx::Point original_location = initial_presses_[event.pointer_details().id];
666     float distance = (event.location() - original_location).Length();
667     // If the user moves too far from the original position, consider the
668     // movement a swipe.
669     if (distance > gesture_detector_config_.touch_slop) {
670       SET_STATE(GESTURE_IN_PROGRESS);
671     }
672     return DiscardEvent(continuation);
673   }
674 
675   if (current_touch_ids_.size() != 0) {
676     return DiscardEvent(continuation);
677   }
678 
679   if (type == ui::ET_TOUCH_RELEASED) {
680     delegate_->HandleAccessibilityGesture(ax::mojom::Gesture::kTap2);
681     SET_STATE(NO_FINGERS_DOWN);
682     return DiscardEvent(continuation);
683   }
684   return DiscardEvent(continuation);
685 }
686 
Now()687 base::TimeTicks TouchExplorationController::Now() {
688   return ui::EventTimeForNow();
689 }
690 
StartTapTimer()691 void TouchExplorationController::StartTapTimer() {
692   tap_timer_.Start(FROM_HERE, gesture_detector_config_.double_tap_timeout, this,
693                    &TouchExplorationController::OnTapTimerFired);
694 }
695 
OnTapTimerFired()696 void TouchExplorationController::OnTapTimerFired() {
697   switch (state_) {
698     case SINGLE_TAP_RELEASED:
699       SET_STATE(NO_FINGERS_DOWN);
700       break;
701     case TOUCH_EXPLORE_RELEASED:
702       SET_STATE(NO_FINGERS_DOWN);
703       last_touch_exploration_ =
704           std::make_unique<ui::TouchEvent>(*initial_press_);
705       anchor_point_dip_ = last_touch_exploration_->location_f();
706       anchor_point_state_ = ANCHOR_POINT_FROM_TOUCH_EXPLORATION;
707       return;
708     case DOUBLE_TAP_PENDING: {
709       SET_STATE(ONE_FINGER_PASSTHROUGH);
710       passthrough_offset_ =
711           last_unused_finger_event_->location_f() - anchor_point_dip_;
712       std::unique_ptr<ui::TouchEvent> passthrough_press(
713           new ui::TouchEvent(ui::ET_TOUCH_PRESSED, gfx::Point(), Now(),
714                              last_unused_finger_event_->pointer_details()));
715       passthrough_press->set_location_f(anchor_point_dip_);
716       passthrough_press->set_root_location_f(anchor_point_dip_);
717       DispatchEvent(passthrough_press.get(), last_unused_finger_continuation_);
718       return;
719     }
720     case SINGLE_TAP_PRESSED:
721     case GESTURE_IN_PROGRESS:
722       // If only one finger is down, go into touch exploration.
723       if (current_touch_ids_.size() == 1) {
724         anchor_point_state_ = ANCHOR_POINT_FROM_TOUCH_EXPLORATION;
725         EnterTouchToMouseMode();
726         SET_STATE(TOUCH_EXPLORATION);
727         break;
728       }
729       // Otherwise wait for all fingers to be lifted.
730       SET_STATE(WAIT_FOR_NO_FINGERS);
731       return;
732     case TWO_FINGER_TAP:
733       SET_STATE(WAIT_FOR_NO_FINGERS);
734       break;
735     default:
736       return;
737   }
738   EnterTouchToMouseMode();
739   std::unique_ptr<ui::Event> mouse_move = CreateMouseMoveEvent(
740       initial_press_->location_f(), initial_press_->flags());
741   DispatchEvent(mouse_move.get(), initial_press_continuation_);
742   last_touch_exploration_ = std::make_unique<ui::TouchEvent>(*initial_press_);
743   anchor_point_dip_ = last_touch_exploration_->location_f();
744   anchor_point_state_ = ANCHOR_POINT_FROM_TOUCH_EXPLORATION;
745 }
746 
DispatchEvent(ui::Event * event,const Continuation continuation)747 void TouchExplorationController::DispatchEvent(
748     ui::Event* event,
749     const Continuation continuation) {
750   SetTouchAccessibilityFlag(event);
751   if (event->IsLocatedEvent()) {
752     ui::LocatedEvent* located_event = event->AsLocatedEvent();
753     gfx::PointF screen_point(
754         ConvertDIPToScreenInPixels(located_event->location_f()));
755     located_event->set_location_f(screen_point);
756     located_event->set_root_location_f(screen_point);
757   }
758   if (SendEventFinally(continuation, event).dispatcher_destroyed) {
759     VLOG(0) << "Undispatched event due to destroyed dispatcher.";
760   }
761 }
762 
763 // This is an override for a function that is only called for timer-based events
764 // like long press. Events that are created synchronously as a result of
765 // certain touch events are added to the vector accessible via
766 // GetAndResetPendingGestures(). We only care about swipes (which are created
767 // synchronously), so we ignore this callback.
OnGestureEvent(ui::GestureConsumer * consumer,ui::GestureEvent * gesture)768 void TouchExplorationController::OnGestureEvent(ui::GestureConsumer* consumer,
769                                                 ui::GestureEvent* gesture) {}
770 
ProcessGestureEvents()771 void TouchExplorationController::ProcessGestureEvents() {
772   std::vector<std::unique_ptr<ui::GestureEvent>> gestures =
773       gesture_provider_->GetAndResetPendingGestures();
774   bool resolved_gesture = false;
775   max_gesture_touch_points_ =
776       std::max(max_gesture_touch_points_, current_touch_ids_.size());
777   for (const auto& gesture : gestures) {
778     if (gesture->type() == ui::ET_GESTURE_SWIPE &&
779         state_ == GESTURE_IN_PROGRESS) {
780       OnSwipeEvent(gesture.get());
781       // The tap timer to leave gesture state is ended, and we now wait for
782       // all fingers to be released.
783       tap_timer_.Stop();
784       SET_STATE(WAIT_FOR_NO_FINGERS);
785       return;
786     }
787   }
788 
789   if (resolved_gesture)
790     return;
791 
792   if (current_touch_ids_.size() == 0) {
793     switch (max_gesture_touch_points_) {
794       case 3:
795         delegate_->HandleAccessibilityGesture(ax::mojom::Gesture::kTap3);
796         break;
797       case 4:
798         delegate_->HandleAccessibilityGesture(ax::mojom::Gesture::kTap4);
799         break;
800       default:
801         break;
802     }
803     max_gesture_touch_points_ = 0;
804   }
805 }
806 
OnSwipeEvent(ui::GestureEvent * swipe_gesture)807 void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) {
808   // A swipe gesture contains details for the direction in which the swipe
809   // occurred. TODO(evy) : Research which swipe results users most want and
810   // remap these swipes to the best events. Hopefully in the near future
811   // there will also be a menu for users to pick custom mappings.
812   ui::GestureEventDetails event_details = swipe_gesture->details();
813   int num_fingers = event_details.touch_points();
814   if (DVLOG_on_)
815     DVLOG(1) << "\nSwipe with " << num_fingers << " fingers.";
816 
817   ax::mojom::Gesture gesture = ax::mojom::Gesture::kNone;
818   if (event_details.swipe_left()) {
819     switch (num_fingers) {
820       case 1:
821         gesture = ax::mojom::Gesture::kSwipeLeft1;
822         break;
823       case 2:
824         gesture = ax::mojom::Gesture::kSwipeLeft2;
825         break;
826       case 3:
827         gesture = ax::mojom::Gesture::kSwipeLeft3;
828         break;
829       case 4:
830         gesture = ax::mojom::Gesture::kSwipeLeft4;
831         break;
832       default:
833         break;
834     }
835   } else if (event_details.swipe_up()) {
836     switch (num_fingers) {
837       case 1:
838         gesture = ax::mojom::Gesture::kSwipeUp1;
839         break;
840       case 2:
841         gesture = ax::mojom::Gesture::kSwipeUp2;
842         break;
843       case 3:
844         gesture = ax::mojom::Gesture::kSwipeUp3;
845         break;
846       case 4:
847         gesture = ax::mojom::Gesture::kSwipeUp4;
848         break;
849       default:
850         break;
851     }
852   } else if (event_details.swipe_right()) {
853     switch (num_fingers) {
854       case 1:
855         gesture = ax::mojom::Gesture::kSwipeRight1;
856         break;
857       case 2:
858         gesture = ax::mojom::Gesture::kSwipeRight2;
859         break;
860       case 3:
861         gesture = ax::mojom::Gesture::kSwipeRight3;
862         break;
863       case 4:
864         gesture = ax::mojom::Gesture::kSwipeRight4;
865         break;
866       default:
867         break;
868     }
869   } else if (event_details.swipe_down()) {
870     switch (num_fingers) {
871       case 1:
872         gesture = ax::mojom::Gesture::kSwipeDown1;
873         break;
874       case 2:
875         gesture = ax::mojom::Gesture::kSwipeDown2;
876         break;
877       case 3:
878         gesture = ax::mojom::Gesture::kSwipeDown3;
879         break;
880       case 4:
881         gesture = ax::mojom::Gesture::kSwipeDown4;
882         break;
883       default:
884         break;
885     }
886   }
887 
888   if (gesture != ax::mojom::Gesture::kNone)
889     delegate_->HandleAccessibilityGesture(gesture);
890 }
891 
FindEdgesWithinInset(gfx::Point point_dip,float horiz_inset,float vert_inset)892 int TouchExplorationController::FindEdgesWithinInset(gfx::Point point_dip,
893                                                      float horiz_inset,
894                                                      float vert_inset) {
895   gfx::RectF inner_bounds_dip(root_window_->bounds());
896   inner_bounds_dip.Inset(horiz_inset, vert_inset);
897 
898   // Bitwise manipulation in order to determine where on the screen the point
899   // lies. If more than one bit is turned on, then it is a corner where the two
900   // bit/edges intersect. Otherwise, if no bits are turned on, the point must be
901   // in the center of the screen.
902   int result = NO_EDGE;
903   if (point_dip.x() < inner_bounds_dip.x())
904     result |= LEFT_EDGE;
905   if (point_dip.x() > inner_bounds_dip.right())
906     result |= RIGHT_EDGE;
907   if (point_dip.y() < inner_bounds_dip.y())
908     result |= TOP_EDGE;
909   if (point_dip.y() > inner_bounds_dip.bottom())
910     result |= BOTTOM_EDGE;
911   return result;
912 }
913 
DispatchKeyWithFlags(const ui::KeyboardCode key,int flags,const Continuation continuation)914 void TouchExplorationController::DispatchKeyWithFlags(
915     const ui::KeyboardCode key,
916     int flags,
917     const Continuation continuation) {
918   ui::KeyEvent key_down(ui::ET_KEY_PRESSED, key, flags);
919   ui::KeyEvent key_up(ui::ET_KEY_RELEASED, key, flags);
920   DispatchEvent(&key_down, continuation);
921   DispatchEvent(&key_up, continuation);
922   if (DVLOG_on_) {
923     DVLOG(1) << "\nKey down: key code : " << key_down.key_code()
924              << ", flags: " << key_down.flags()
925              << "\nKey up: key code : " << key_up.key_code()
926              << ", flags: " << key_up.flags();
927   }
928 }
929 
BindKeyEventWithFlags(const ui::KeyboardCode key,int flags,const Continuation continuation)930 base::OnceClosure TouchExplorationController::BindKeyEventWithFlags(
931     const ui::KeyboardCode key,
932     int flags,
933     const Continuation continuation) {
934   return base::BindOnce(&TouchExplorationController::DispatchKeyWithFlags,
935                         base::Unretained(this), key, flags, continuation);
936 }
937 
938 std::unique_ptr<ui::MouseEvent>
CreateMouseMoveEvent(const gfx::PointF & location,int flags)939 TouchExplorationController::CreateMouseMoveEvent(const gfx::PointF& location,
940                                                  int flags) {
941   // The "synthesized" flag should be set on all events that don't have a
942   // backing native event.
943   flags |= ui::EF_IS_SYNTHESIZED;
944 
945   // TODO(dmazzoni) http://crbug.com/391008 - get rid of this hack.
946   // This is a short-term workaround for the limitation that we're using
947   // the ChromeVox content script to process touch exploration events, but
948   // ChromeVox needs a way to distinguish between a real mouse move and a
949   // mouse move generated from touch exploration, so we have touch exploration
950   // pretend that the command key was down (which becomes the "meta" key in
951   // JavaScript). We can remove this hack when the ChromeVox content script
952   // goes away and native accessibility code sends a touch exploration
953   // event to the new ChromeVox background page via the automation api.
954   flags |= ui::EF_COMMAND_DOWN;
955 
956   std::unique_ptr<ui::MouseEvent> event(new ui::MouseEvent(
957       ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), Now(), flags, 0));
958   event->set_location_f(location);
959   event->set_root_location_f(location);
960   return event;
961 }
962 
EnterTouchToMouseMode()963 void TouchExplorationController::EnterTouchToMouseMode() {
964   aura::client::CursorClient* cursor_client =
965       aura::client::GetCursorClient(root_window_);
966   if (cursor_client && !cursor_client->IsMouseEventsEnabled())
967     cursor_client->EnableMouseEvents();
968   if (cursor_client && cursor_client->IsCursorVisible())
969     cursor_client->HideCursor();
970 }
971 
SetState(State new_state,const char * function_name)972 void TouchExplorationController::SetState(State new_state,
973                                           const char* function_name) {
974   state_ = new_state;
975   VlogState(function_name);
976   // These are the states the user can be in that will never result in a
977   // gesture before the user returns to NO_FINGERS_DOWN. Therefore, if the
978   // gesture provider still exists, it's reset to NULL until the user returns
979   // to NO_FINGERS_DOWN.
980   switch (new_state) {
981     case SINGLE_TAP_RELEASED:
982     case TOUCH_EXPLORE_RELEASED:
983     case DOUBLE_TAP_PENDING:
984     case TOUCH_RELEASE_PENDING:
985     case TOUCH_EXPLORATION:
986     case TOUCH_EXPLORE_SECOND_PRESS:
987     case ONE_FINGER_PASSTHROUGH:
988     case WAIT_FOR_NO_FINGERS:
989       if (gesture_provider_.get())
990         gesture_provider_.reset(NULL);
991       max_gesture_touch_points_ = 0;
992       break;
993     case NO_FINGERS_DOWN:
994       gesture_provider_ = std::make_unique<ui::GestureProviderAura>(this, this);
995       if (sound_timer_.IsRunning())
996         sound_timer_.Stop();
997       tap_timer_.Stop();
998       break;
999     case SINGLE_TAP_PRESSED:
1000     case GESTURE_IN_PROGRESS:
1001     case TWO_FINGER_TAP:
1002       break;
1003   }
1004 }
1005 
VlogState(const char * function_name)1006 void TouchExplorationController::VlogState(const char* function_name) {
1007   if (!DVLOG_on_)
1008     return;
1009   if (prev_state_ == state_)
1010     return;
1011   prev_state_ = state_;
1012   const char* state_string = EnumStateToString(state_);
1013   DVLOG(1) << "\n Function name: " << function_name
1014            << "\n State: " << state_string;
1015 }
1016 
VlogEvent(const ui::TouchEvent & touch_event,const char * function_name)1017 void TouchExplorationController::VlogEvent(const ui::TouchEvent& touch_event,
1018                                            const char* function_name) {
1019   if (!DVLOG_on_)
1020     return;
1021 
1022   if (prev_event_ && prev_event_->type() == touch_event.type() &&
1023       prev_event_->pointer_details().id == touch_event.pointer_details().id) {
1024     return;
1025   }
1026   // The above statement prevents events of the same type and id from being
1027   // printed in a row. However, if two fingers are down, they would both be
1028   // moving and alternating printing move events unless we check for this.
1029   if (prev_event_ && prev_event_->type() == ui::ET_TOUCH_MOVED &&
1030       touch_event.type() == ui::ET_TOUCH_MOVED) {
1031     return;
1032   }
1033 
1034   const std::string& type = touch_event.GetName();
1035   const gfx::PointF& location = touch_event.location_f();
1036   const int touch_id = touch_event.pointer_details().id;
1037 
1038   DVLOG(1) << "\n Function name: " << function_name << "\n Event Type: " << type
1039            << "\n Location: " << location.ToString()
1040            << "\n Touch ID: " << touch_id;
1041   prev_event_ = std::make_unique<ui::TouchEvent>(touch_event);
1042 }
1043 
EnumStateToString(State state)1044 const char* TouchExplorationController::EnumStateToString(State state) {
1045   switch (state) {
1046     case NO_FINGERS_DOWN:
1047       return "NO_FINGERS_DOWN";
1048     case SINGLE_TAP_PRESSED:
1049       return "SINGLE_TAP_PRESSED";
1050     case SINGLE_TAP_RELEASED:
1051       return "SINGLE_TAP_RELEASED";
1052     case TOUCH_EXPLORE_RELEASED:
1053       return "TOUCH_EXPLORE_RELEASED";
1054     case DOUBLE_TAP_PENDING:
1055       return "DOUBLE_TAP_PENDING";
1056     case TOUCH_RELEASE_PENDING:
1057       return "TOUCH_RELEASE_PENDING";
1058     case TOUCH_EXPLORATION:
1059       return "TOUCH_EXPLORATION";
1060     case GESTURE_IN_PROGRESS:
1061       return "GESTURE_IN_PROGRESS";
1062     case TOUCH_EXPLORE_SECOND_PRESS:
1063       return "TOUCH_EXPLORE_SECOND_PRESS";
1064     case ONE_FINGER_PASSTHROUGH:
1065       return "ONE_FINGER_PASSTHROUGH";
1066     case WAIT_FOR_NO_FINGERS:
1067       return "WAIT_FOR_NO_FINGERS";
1068     case TWO_FINGER_TAP:
1069       return "TWO_FINGER_TAP";
1070   }
1071   return "Not a state";
1072 }
1073 
GetSplitTapTouchSlop()1074 float TouchExplorationController::GetSplitTapTouchSlop() {
1075   return gesture_detector_config_.touch_slop * 3;
1076 }
1077 
ConvertDIPToScreenInPixels(const gfx::PointF & location_f)1078 gfx::PointF TouchExplorationController::ConvertDIPToScreenInPixels(
1079     const gfx::PointF& location_f) {
1080   gfx::Point location(gfx::ToFlooredPoint(location_f));
1081   root_window_->GetHost()->ConvertDIPToScreenInPixels(&location);
1082   return gfx::PointF(location);
1083 }
1084 
1085 }  // namespace shell
1086 }  // namespace chromecast
1087