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