base: Change DCHECK_IS_ON to a macro DCHECK_IS_ON().
[chromium-blink-merge.git] / ui / chromeos / touch_exploration_controller.cc
blob695142d91d4b25ecc6ce763871e45d3a1585d1e6
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.
5 #include "ui/chromeos/touch_exploration_controller.h"
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/time/default_tick_clock.h"
10 #include "ui/aura/client/cursor_client.h"
11 #include "ui/aura/window.h"
12 #include "ui/aura/window_event_dispatcher.h"
13 #include "ui/aura/window_tree_host.h"
14 #include "ui/events/event.h"
15 #include "ui/events/event_processor.h"
16 #include "ui/events/event_utils.h"
17 #include "ui/gfx/geometry/rect.h"
19 #define SET_STATE(state) SetState(state, __func__)
20 #define VLOG_EVENT(event) if (VLOG_IS_ON(0)) VlogEvent(event, __func__)
22 namespace ui {
24 namespace {
26 // Delay between adjustment sounds.
27 const base::TimeDelta kSoundDelay = base::TimeDelta::FromMilliseconds(150);
29 // Delay before corner passthrough activates.
30 const base::TimeDelta kCornerPassthroughDelay =
31 base::TimeDelta::FromMilliseconds(700);
33 // In ChromeOS, VKEY_LWIN is synonymous for the search key.
34 const ui::KeyboardCode kChromeOSSearchKey = ui::VKEY_LWIN;
35 } // namespace
37 TouchExplorationController::TouchExplorationController(
38 aura::Window* root_window,
39 TouchExplorationControllerDelegate* delegate)
40 : root_window_(root_window),
41 delegate_(delegate),
42 state_(NO_FINGERS_DOWN),
43 gesture_provider_(new GestureProviderAura(this)),
44 prev_state_(NO_FINGERS_DOWN),
45 VLOG_on_(true),
46 tick_clock_(NULL) {
47 CHECK(root_window);
48 root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
49 InitializeSwipeGestureMaps();
52 TouchExplorationController::~TouchExplorationController() {
53 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
56 ui::EventRewriteStatus TouchExplorationController::RewriteEvent(
57 const ui::Event& event,
58 scoped_ptr<ui::Event>* rewritten_event) {
59 if (!event.IsTouchEvent()) {
60 if (event.IsKeyEvent()) {
61 const ui::KeyEvent& key_event = static_cast<const ui::KeyEvent&>(event);
62 VLOG(0) << "\nKeyboard event: " << key_event.name()
63 << "\n Key code: " << key_event.key_code()
64 << ", Flags: " << key_event.flags()
65 << ", Is char: " << key_event.is_char();
67 return ui::EVENT_REWRITE_CONTINUE;
69 const ui::TouchEvent& touch_event = static_cast<const ui::TouchEvent&>(event);
71 // If the tap timer should have fired by now but hasn't, run it now and
72 // stop the timer. This is important so that behavior is consistent with
73 // the timestamps of the events, and not dependent on the granularity of
74 // the timer.
75 if (tap_timer_.IsRunning() &&
76 touch_event.time_stamp() - initial_press_->time_stamp() >
77 gesture_detector_config_.double_tap_timeout) {
78 tap_timer_.Stop();
79 OnTapTimerFired();
80 // Note: this may change the state. We should now continue and process
81 // this event under this new state.
84 if (passthrough_timer_.IsRunning() &&
85 event.time_stamp() - initial_press_->time_stamp() >
86 gesture_detector_config_.longpress_timeout) {
87 passthrough_timer_.Stop();
88 OnPassthroughTimerFired();
91 const ui::EventType type = touch_event.type();
92 const gfx::PointF& location = touch_event.location_f();
93 const int touch_id = touch_event.touch_id();
95 // Always update touch ids and touch locations, so we can use those
96 // no matter what state we're in.
97 if (type == ui::ET_TOUCH_PRESSED) {
98 current_touch_ids_.push_back(touch_id);
99 touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
100 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
101 std::vector<int>::iterator it = std::find(
102 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
104 // Can happen if touch exploration is enabled while fingers were down.
105 if (it == current_touch_ids_.end())
106 return ui::EVENT_REWRITE_CONTINUE;
108 current_touch_ids_.erase(it);
109 touch_locations_.erase(touch_id);
110 } else if (type == ui::ET_TOUCH_MOVED) {
111 std::vector<int>::iterator it = std::find(
112 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
114 // Can happen if touch exploration is enabled while fingers were down.
115 if (it == current_touch_ids_.end())
116 return ui::EVENT_REWRITE_CONTINUE;
118 touch_locations_[*it] = location;
119 } else {
120 NOTREACHED() << "Unexpected event type received: " << event.name();
121 return ui::EVENT_REWRITE_CONTINUE;
123 VLOG_EVENT(touch_event);
125 // In order to avoid accidentally double tapping when moving off the edge
126 // of the screen, the state will be rewritten to NoFingersDown.
127 if ((type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) &&
128 FindEdgesWithinBounds(touch_event.location(), kLeavingScreenEdge) !=
129 NO_EDGE) {
130 if (VLOG_on_)
131 VLOG(0) << "Leaving screen";
133 // Indicates to the user that they are leaving the screen.
134 delegate_->PlayExitScreenEarcon();
136 if (current_touch_ids_.size() == 0) {
137 SET_STATE(NO_FINGERS_DOWN);
138 if (VLOG_on_) {
139 VLOG(0) << "Reset to no fingers in Rewrite event because the touch "
140 "release or cancel was on the edge of the screen.";
142 return ui::EVENT_REWRITE_DISCARD;
146 // If the user is in a gesture state, or if there is a possiblity that the
147 // user will enter it in the future, we send the event to the gesture
148 // provider so it can keep track of the state of the fingers. When the user
149 // leaves one of these states, SET_STATE will set the gesture provider to
150 // NULL.
151 if (gesture_provider_.get()) {
152 ui::TouchEvent mutable_touch_event = touch_event;
153 if (gesture_provider_->OnTouchEvent(&mutable_touch_event)) {
154 gesture_provider_->OnSyncTouchEventAck(
155 mutable_touch_event.unique_event_id(), false);
157 ProcessGestureEvents();
160 // The rest of the processing depends on what state we're in.
161 switch (state_) {
162 case NO_FINGERS_DOWN:
163 return InNoFingersDown(touch_event, rewritten_event);
164 case SINGLE_TAP_PRESSED:
165 return InSingleTapPressed(touch_event, rewritten_event);
166 case SINGLE_TAP_RELEASED:
167 case TOUCH_EXPLORE_RELEASED:
168 return InSingleTapOrTouchExploreReleased(touch_event, rewritten_event);
169 case DOUBLE_TAP_PENDING:
170 return InDoubleTapPending(touch_event, rewritten_event);
171 case TOUCH_RELEASE_PENDING:
172 return InTouchReleasePending(touch_event, rewritten_event);
173 case TOUCH_EXPLORATION:
174 return InTouchExploration(touch_event, rewritten_event);
175 case GESTURE_IN_PROGRESS:
176 return InGestureInProgress(touch_event, rewritten_event);
177 case TOUCH_EXPLORE_SECOND_PRESS:
178 return InTouchExploreSecondPress(touch_event, rewritten_event);
179 case SLIDE_GESTURE:
180 return InSlideGesture(touch_event, rewritten_event);
181 case ONE_FINGER_PASSTHROUGH:
182 return InOneFingerPassthrough(touch_event, rewritten_event);
183 case CORNER_PASSTHROUGH:
184 return InCornerPassthrough(touch_event, rewritten_event);
185 case WAIT_FOR_NO_FINGERS:
186 return InWaitForNoFingers(touch_event, rewritten_event);
187 case TWO_FINGER_TAP:
188 return InTwoFingerTap(touch_event, rewritten_event);
190 NOTREACHED();
191 return ui::EVENT_REWRITE_CONTINUE;
194 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
195 const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) {
196 NOTREACHED();
197 return ui::EVENT_REWRITE_CONTINUE;
200 ui::EventRewriteStatus TouchExplorationController::InNoFingersDown(
201 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
202 const ui::EventType type = event.type();
203 if (type != ui::ET_TOUCH_PRESSED) {
204 NOTREACHED() << "Unexpected event type received: " << event.name();
205 return ui::EVENT_REWRITE_CONTINUE;
208 // If the user enters the screen from the edge then send an earcon.
209 int edge = FindEdgesWithinBounds(event.location(), kLeavingScreenEdge);
210 if (edge != NO_EDGE)
211 delegate_->PlayEnterScreenEarcon();
213 int location = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge);
214 // If the press was at a corner, the user might go into corner passthrough
215 // instead.
216 bool in_a_bottom_corner =
217 (BOTTOM_LEFT_CORNER == location) || (BOTTOM_RIGHT_CORNER == location);
218 if (in_a_bottom_corner) {
219 passthrough_timer_.Start(
220 FROM_HERE,
221 gesture_detector_config_.longpress_timeout,
222 this,
223 &TouchExplorationController::OnPassthroughTimerFired);
225 initial_press_.reset(new TouchEvent(event));
226 initial_presses_[event.touch_id()] = event.location();
227 last_unused_finger_event_.reset(new TouchEvent(event));
228 StartTapTimer();
229 SET_STATE(SINGLE_TAP_PRESSED);
230 return ui::EVENT_REWRITE_DISCARD;
233 ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed(
234 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
235 const ui::EventType type = event.type();
237 int location = FindEdgesWithinBounds(event.location(), kMaxDistanceFromEdge);
238 bool in_a_bottom_corner =
239 (location == BOTTOM_LEFT_CORNER) || (location == BOTTOM_RIGHT_CORNER);
240 // If the event is from the initial press and the location is no longer in the
241 // corner, then we are not waiting for a corner passthrough anymore.
242 if (event.touch_id() == initial_press_->touch_id() && !in_a_bottom_corner) {
243 if (passthrough_timer_.IsRunning()) {
244 passthrough_timer_.Stop();
245 // Since the long press timer has been running, it is possible that the
246 // tap timer has timed out before the long press timer has. If the tap
247 // timer timeout has elapsed, then fire the tap timer.
248 if (event.time_stamp() - initial_press_->time_stamp() >
249 gesture_detector_config_.double_tap_timeout) {
250 OnTapTimerFired();
255 if (type == ui::ET_TOUCH_PRESSED) {
256 initial_presses_[event.touch_id()] = event.location();
257 SET_STATE(TWO_FINGER_TAP);
258 return EVENT_REWRITE_DISCARD;
259 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
260 if (passthrough_timer_.IsRunning())
261 passthrough_timer_.Stop();
262 if (current_touch_ids_.size() == 0 &&
263 event.touch_id() == initial_press_->touch_id()) {
264 SET_STATE(SINGLE_TAP_RELEASED);
265 } else if (current_touch_ids_.size() == 0) {
266 SET_STATE(NO_FINGERS_DOWN);
268 return EVENT_REWRITE_DISCARD;
269 } else if (type == ui::ET_TOUCH_MOVED) {
270 float distance = (event.location() - initial_press_->location()).Length();
271 // If the user does not move far enough from the original position, then the
272 // resulting movement should not be considered to be a deliberate gesture or
273 // touch exploration.
274 if (distance <= gesture_detector_config_.touch_slop)
275 return EVENT_REWRITE_DISCARD;
277 float delta_time =
278 (event.time_stamp() - initial_press_->time_stamp()).InSecondsF();
279 float velocity = distance / delta_time;
280 if (VLOG_on_) {
281 VLOG(0) << "\n Delta time: " << delta_time << "\n Distance: " << distance
282 << "\n Velocity of click: " << velocity
283 << "\n Minimum swipe velocity: "
284 << gesture_detector_config_.minimum_swipe_velocity;
286 // Change to slide gesture if the slide occurred at the right edge.
287 int edge = FindEdgesWithinBounds(event.location(), kMaxDistanceFromEdge);
288 if (edge & RIGHT_EDGE && edge != BOTTOM_RIGHT_CORNER) {
289 SET_STATE(SLIDE_GESTURE);
290 return InSlideGesture(event, rewritten_event);
293 // If the user moves fast enough from the initial touch location, start
294 // gesture detection. Otherwise, jump to the touch exploration mode early.
295 if (velocity > gesture_detector_config_.minimum_swipe_velocity) {
296 SET_STATE(GESTURE_IN_PROGRESS);
297 return InGestureInProgress(event, rewritten_event);
299 EnterTouchToMouseMode();
300 SET_STATE(TOUCH_EXPLORATION);
301 return InTouchExploration(event, rewritten_event);
303 NOTREACHED();
304 return ui::EVENT_REWRITE_CONTINUE;
307 ui::EventRewriteStatus
308 TouchExplorationController::InSingleTapOrTouchExploreReleased(
309 const ui::TouchEvent& event,
310 scoped_ptr<ui::Event>* rewritten_event) {
311 const ui::EventType type = event.type();
312 // If there is more than one finger down, then discard to wait until no
313 // fingers are down.
314 if (current_touch_ids_.size() > 1) {
315 SET_STATE(WAIT_FOR_NO_FINGERS);
316 return ui::EVENT_REWRITE_DISCARD;
318 if (type == ui::ET_TOUCH_PRESSED) {
319 // If there is no touch exploration yet, we can't send a click, so discard.
320 if (!last_touch_exploration_) {
321 tap_timer_.Stop();
322 return ui::EVENT_REWRITE_DISCARD;
324 // This is the second tap in a double-tap (or double tap-hold).
325 // We set the tap timer. If it fires before the user lifts their finger,
326 // one-finger passthrough begins. Otherwise, there is a touch press and
327 // release at the location of the last touch exploration.
328 SET_STATE(DOUBLE_TAP_PENDING);
329 // The old tap timer (from the initial click) is stopped if it is still
330 // going, and the new one is set.
331 tap_timer_.Stop();
332 StartTapTimer();
333 // This will update as the finger moves before a possible passthrough, and
334 // will determine the offset.
335 last_unused_finger_event_.reset(new ui::TouchEvent(event));
336 return ui::EVENT_REWRITE_DISCARD;
337 } else if (type == ui::ET_TOUCH_RELEASED && !last_touch_exploration_) {
338 // If the previous press was discarded, we need to also handle its
339 // release.
340 if (current_touch_ids_.size() == 0) {
341 SET_STATE(NO_FINGERS_DOWN);
343 return ui::EVENT_REWRITE_DISCARD;
344 } else if (type == ui::ET_TOUCH_MOVED) {
345 return ui::EVENT_REWRITE_DISCARD;
347 NOTREACHED();
348 return ui::EVENT_REWRITE_CONTINUE;
351 ui::EventRewriteStatus TouchExplorationController::InDoubleTapPending(
352 const ui::TouchEvent& event,
353 scoped_ptr<ui::Event>* rewritten_event) {
354 const ui::EventType type = event.type();
355 if (type == ui::ET_TOUCH_PRESSED) {
356 return ui::EVENT_REWRITE_DISCARD;
357 } else if (type == ui::ET_TOUCH_MOVED) {
358 // If the user moves far enough from the initial touch location (outside
359 // the "slop" region, jump to passthrough mode early.
360 float delta = (event.location() - initial_press_->location()).Length();
361 if (delta > gesture_detector_config_.touch_slop) {
362 tap_timer_.Stop();
363 OnTapTimerFired();
365 return EVENT_REWRITE_DISCARD;
366 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
367 if (current_touch_ids_.size() != 0)
368 return EVENT_REWRITE_DISCARD;
370 scoped_ptr<ui::TouchEvent> touch_press;
371 touch_press.reset(new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
372 last_touch_exploration_->location(),
373 initial_press_->touch_id(),
374 event.time_stamp()));
375 DispatchEvent(touch_press.get());
377 rewritten_event->reset(
378 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
379 last_touch_exploration_->location(),
380 initial_press_->touch_id(),
381 event.time_stamp()));
382 (*rewritten_event)->set_flags(event.flags());
383 SET_STATE(NO_FINGERS_DOWN);
384 return ui::EVENT_REWRITE_REWRITTEN;
386 NOTREACHED();
387 return ui::EVENT_REWRITE_CONTINUE;
390 ui::EventRewriteStatus TouchExplorationController::InTouchReleasePending(
391 const ui::TouchEvent& event,
392 scoped_ptr<ui::Event>* rewritten_event) {
393 const ui::EventType type = event.type();
394 if (type == ui::ET_TOUCH_PRESSED || type == ui::ET_TOUCH_MOVED) {
395 return ui::EVENT_REWRITE_DISCARD;
396 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
397 if (current_touch_ids_.size() != 0)
398 return EVENT_REWRITE_DISCARD;
400 rewritten_event->reset(
401 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
402 last_touch_exploration_->location(),
403 initial_press_->touch_id(),
404 event.time_stamp()));
405 (*rewritten_event)->set_flags(event.flags());
406 SET_STATE(NO_FINGERS_DOWN);
407 return ui::EVENT_REWRITE_REWRITTEN;
409 NOTREACHED();
410 return ui::EVENT_REWRITE_CONTINUE;
413 ui::EventRewriteStatus TouchExplorationController::InTouchExploration(
414 const ui::TouchEvent& event,
415 scoped_ptr<ui::Event>* rewritten_event) {
416 const ui::EventType type = event.type();
417 if (type == ui::ET_TOUCH_PRESSED) {
418 // Handle split-tap.
419 initial_press_.reset(new TouchEvent(event));
420 tap_timer_.Stop();
421 rewritten_event->reset(
422 new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
423 last_touch_exploration_->location(),
424 event.touch_id(),
425 event.time_stamp()));
426 (*rewritten_event)->set_flags(event.flags());
427 SET_STATE(TOUCH_EXPLORE_SECOND_PRESS);
428 return ui::EVENT_REWRITE_REWRITTEN;
429 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
430 initial_press_.reset(new TouchEvent(event));
431 StartTapTimer();
432 SET_STATE(TOUCH_EXPLORE_RELEASED);
433 } else if (type != ui::ET_TOUCH_MOVED) {
434 NOTREACHED();
435 return ui::EVENT_REWRITE_CONTINUE;
438 // Rewrite as a mouse-move event.
439 *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags());
440 last_touch_exploration_.reset(new TouchEvent(event));
441 return ui::EVENT_REWRITE_REWRITTEN;
444 ui::EventRewriteStatus TouchExplorationController::InGestureInProgress(
445 const ui::TouchEvent& event,
446 scoped_ptr<ui::Event>* rewritten_event) {
447 // The events were sent to the gesture provider in RewriteEvent already.
448 // If no gesture is registered before the tap timer times out, the state
449 // will change to "wait for no fingers down" or "touch exploration" depending
450 // on the number of fingers down, and this function will stop being called.
451 if (current_touch_ids_.size() == 0) {
452 SET_STATE(NO_FINGERS_DOWN);
454 return ui::EVENT_REWRITE_DISCARD;
457 ui::EventRewriteStatus TouchExplorationController::InCornerPassthrough(
458 const ui::TouchEvent& event,
459 scoped_ptr<ui::Event>* rewritten_event) {
460 ui::EventType type = event.type();
462 // If the first finger has left the corner, then exit passthrough.
463 if (event.touch_id() == initial_press_->touch_id()) {
464 int edges = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge);
465 bool in_a_bottom_corner = (edges == BOTTOM_LEFT_CORNER) ||
466 (edges == BOTTOM_RIGHT_CORNER);
467 if (type == ui::ET_TOUCH_MOVED && in_a_bottom_corner)
468 return ui::EVENT_REWRITE_DISCARD;
470 if (current_touch_ids_.size() == 0) {
471 SET_STATE(NO_FINGERS_DOWN);
472 return ui::EVENT_REWRITE_DISCARD;
474 SET_STATE(WAIT_FOR_NO_FINGERS);
475 return ui::EVENT_REWRITE_DISCARD;
478 rewritten_event->reset(new ui::TouchEvent(
479 type, event.location(), event.touch_id(), event.time_stamp()));
480 (*rewritten_event)->set_flags(event.flags());
482 if (current_touch_ids_.size() == 0)
483 SET_STATE(NO_FINGERS_DOWN);
485 return ui::EVENT_REWRITE_REWRITTEN;
488 ui::EventRewriteStatus TouchExplorationController::InOneFingerPassthrough(
489 const ui::TouchEvent& event,
490 scoped_ptr<ui::Event>* rewritten_event) {
491 if (event.touch_id() != initial_press_->touch_id()) {
492 if (current_touch_ids_.size() == 0) {
493 SET_STATE(NO_FINGERS_DOWN);
495 return ui::EVENT_REWRITE_DISCARD;
497 rewritten_event->reset(
498 new ui::TouchEvent(event.type(),
499 event.location() - passthrough_offset_,
500 event.touch_id(),
501 event.time_stamp()));
503 (*rewritten_event)->set_flags(event.flags());
504 if (current_touch_ids_.size() == 0) {
505 SET_STATE(NO_FINGERS_DOWN);
507 return ui::EVENT_REWRITE_REWRITTEN;
510 ui::EventRewriteStatus TouchExplorationController::InTouchExploreSecondPress(
511 const ui::TouchEvent& event,
512 scoped_ptr<ui::Event>* rewritten_event) {
513 ui::EventType type = event.type();
514 gfx::PointF location = event.location_f();
515 if (type == ui::ET_TOUCH_PRESSED) {
516 // A third finger being pressed means that a split tap can no longer go
517 // through. The user enters the wait state, Since there has already been
518 // a press dispatched when split tap began, the touch needs to be
519 // cancelled.
520 rewritten_event->reset(
521 new ui::TouchEvent(ui::ET_TOUCH_CANCELLED,
522 last_touch_exploration_->location(),
523 initial_press_->touch_id(),
524 event.time_stamp()));
525 (*rewritten_event)->set_flags(event.flags());
526 SET_STATE(WAIT_FOR_NO_FINGERS);
527 return ui::EVENT_REWRITE_REWRITTEN;
528 } else if (type == ui::ET_TOUCH_MOVED) {
529 // If the fingers have moved too far from their original locations,
530 // the user can no longer split tap.
531 ui::TouchEvent* original_touch;
532 if (event.touch_id() == last_touch_exploration_->touch_id())
533 original_touch = last_touch_exploration_.get();
534 else if (event.touch_id() == initial_press_->touch_id())
535 original_touch = initial_press_.get();
536 else {
537 NOTREACHED();
538 SET_STATE(WAIT_FOR_NO_FINGERS);
539 return ui::EVENT_REWRITE_DISCARD;
541 // Check the distance between the current finger location and the original
542 // location. The slop for this is a bit more generous since keeping two
543 // fingers in place is a bit harder. If the user has left the slop, the
544 // split tap press (which was previous dispatched) is lifted with a touch
545 // cancelled, and the user enters the wait state.
546 if ((event.location() - original_touch->location()).Length() >
547 GetSplitTapTouchSlop()) {
548 rewritten_event->reset(
549 new ui::TouchEvent(ui::ET_TOUCH_CANCELLED,
550 last_touch_exploration_->location(),
551 initial_press_->touch_id(),
552 event.time_stamp()));
553 (*rewritten_event)->set_flags(event.flags());
554 SET_STATE(WAIT_FOR_NO_FINGERS);
555 return ui::EVENT_REWRITE_REWRITTEN;
557 return ui::EVENT_REWRITE_DISCARD;
558 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
559 // If the touch exploration finger is lifted, there is no option to return
560 // to touch explore anymore. The remaining finger acts as a pending
561 // tap or long tap for the last touch explore location.
562 if (event.touch_id() == last_touch_exploration_->touch_id()){
563 SET_STATE(TOUCH_RELEASE_PENDING);
564 return EVENT_REWRITE_DISCARD;
567 // Continue to release the touch only if the touch explore finger is the
568 // only finger remaining.
569 if (current_touch_ids_.size() != 1)
570 return EVENT_REWRITE_DISCARD;
572 // Rewrite at location of last touch exploration.
573 rewritten_event->reset(
574 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
575 last_touch_exploration_->location(),
576 initial_press_->touch_id(),
577 event.time_stamp()));
578 (*rewritten_event)->set_flags(event.flags());
579 SET_STATE(TOUCH_EXPLORATION);
580 EnterTouchToMouseMode();
581 return ui::EVENT_REWRITE_REWRITTEN;
583 NOTREACHED();
584 return ui::EVENT_REWRITE_CONTINUE;
587 ui::EventRewriteStatus TouchExplorationController::InWaitForNoFingers(
588 const ui::TouchEvent& event,
589 scoped_ptr<ui::Event>* rewritten_event) {
590 if (current_touch_ids_.size() == 0)
591 SET_STATE(NO_FINGERS_DOWN);
592 return EVENT_REWRITE_DISCARD;
595 void TouchExplorationController::PlaySoundForTimer() {
596 delegate_->PlayVolumeAdjustEarcon();
599 ui::EventRewriteStatus TouchExplorationController::InSlideGesture(
600 const ui::TouchEvent& event,
601 scoped_ptr<ui::Event>* rewritten_event) {
602 // The timer should not fire when sliding.
603 tap_timer_.Stop();
605 ui::EventType type = event.type();
606 // If additional fingers are added before a swipe gesture has been registered,
607 // then wait until all fingers have been lifted.
608 if (type == ui::ET_TOUCH_PRESSED ||
609 event.touch_id() != initial_press_->touch_id()) {
610 if (sound_timer_.IsRunning())
611 sound_timer_.Stop();
612 SET_STATE(WAIT_FOR_NO_FINGERS);
613 return EVENT_REWRITE_DISCARD;
616 // There should not be more than one finger down.
617 DCHECK(current_touch_ids_.size() <= 1);
619 // Allows user to return to the edge to adjust the sound if they have left the
620 // boundaries.
621 int edge = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge);
622 if (!(edge & RIGHT_EDGE) && (type != ui::ET_TOUCH_RELEASED)) {
623 if (sound_timer_.IsRunning()) {
624 sound_timer_.Stop();
626 return EVENT_REWRITE_DISCARD;
629 // This can occur if the user leaves the screen edge and then returns to it to
630 // continue adjusting the sound.
631 if (!sound_timer_.IsRunning()) {
632 sound_timer_.Start(FROM_HERE,
633 kSoundDelay,
634 this,
635 &ui::TouchExplorationController::PlaySoundForTimer);
636 delegate_->PlayVolumeAdjustEarcon();
639 if (current_touch_ids_.size() == 0) {
640 SET_STATE(NO_FINGERS_DOWN);
642 return ui::EVENT_REWRITE_DISCARD;
645 ui::EventRewriteStatus TouchExplorationController::InTwoFingerTap(
646 const ui::TouchEvent& event,
647 scoped_ptr<ui::Event>* rewritten_event) {
648 ui::EventType type = event.type();
649 if (type == ui::ET_TOUCH_PRESSED) {
650 // This is now a three finger gesture.
651 SET_STATE(GESTURE_IN_PROGRESS);
652 return ui::EVENT_REWRITE_DISCARD;
655 if (type == ui::ET_TOUCH_MOVED) {
656 // Determine if it was a swipe.
657 gfx::Point original_location = initial_presses_[event.touch_id()];
658 float distance = (event.location() - original_location).Length();
659 // If the user moves too far from the original position, consider the
660 // movement a swipe.
661 if (distance > gesture_detector_config_.touch_slop) {
662 SET_STATE(GESTURE_IN_PROGRESS);
664 return ui::EVENT_REWRITE_DISCARD;
667 if (current_touch_ids_.size() != 0)
668 return ui::EVENT_REWRITE_DISCARD;
670 if (type == ui::ET_TOUCH_RELEASED) {
671 // In ChromeVox, pressing control will stop ChromeVox from speaking.
672 ui::KeyEvent control_down(
673 ui::ET_KEY_PRESSED, ui::VKEY_CONTROL, ui::EF_CONTROL_DOWN);
674 ui::KeyEvent control_up(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL, ui::EF_NONE);
676 DispatchEvent(&control_down);
677 DispatchEvent(&control_up);
678 SET_STATE(NO_FINGERS_DOWN);
679 return ui::EVENT_REWRITE_DISCARD;
681 return ui::EVENT_REWRITE_DISCARD;
684 base::TimeDelta TouchExplorationController::Now() {
685 if (tick_clock_) {
686 // This is the same as what EventTimeForNow() does, but here we do it
687 // with a clock that can be replaced with a simulated clock for tests.
688 return base::TimeDelta::FromInternalValue(
689 tick_clock_->NowTicks().ToInternalValue());
691 return ui::EventTimeForNow();
694 void TouchExplorationController::StartTapTimer() {
695 tap_timer_.Start(FROM_HERE,
696 gesture_detector_config_.double_tap_timeout,
697 this,
698 &TouchExplorationController::OnTapTimerFired);
701 void TouchExplorationController::OnTapTimerFired() {
702 switch (state_) {
703 case SINGLE_TAP_RELEASED:
704 SET_STATE(NO_FINGERS_DOWN);
705 break;
706 case TOUCH_EXPLORE_RELEASED:
707 SET_STATE(NO_FINGERS_DOWN);
708 last_touch_exploration_.reset(new TouchEvent(*initial_press_));
709 return;
710 case DOUBLE_TAP_PENDING: {
711 SET_STATE(ONE_FINGER_PASSTHROUGH);
712 passthrough_offset_ = last_unused_finger_event_->location() -
713 last_touch_exploration_->location();
714 scoped_ptr<ui::TouchEvent> passthrough_press(
715 new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
716 last_touch_exploration_->location(),
717 last_unused_finger_event_->touch_id(),
718 Now()));
719 DispatchEvent(passthrough_press.get());
720 return;
722 case SINGLE_TAP_PRESSED:
723 if (passthrough_timer_.IsRunning())
724 return;
725 case GESTURE_IN_PROGRESS:
726 // If only one finger is down, go into touch exploration.
727 if (current_touch_ids_.size() == 1) {
728 EnterTouchToMouseMode();
729 SET_STATE(TOUCH_EXPLORATION);
730 break;
732 // Otherwise wait for all fingers to be lifted.
733 SET_STATE(WAIT_FOR_NO_FINGERS);
734 return;
735 case TWO_FINGER_TAP:
736 SET_STATE(WAIT_FOR_NO_FINGERS);
737 break;
738 default:
739 return;
741 EnterTouchToMouseMode();
742 scoped_ptr<ui::Event> mouse_move =
743 CreateMouseMoveEvent(initial_press_->location(), initial_press_->flags());
744 DispatchEvent(mouse_move.get());
745 last_touch_exploration_.reset(new TouchEvent(*initial_press_));
748 void TouchExplorationController::OnPassthroughTimerFired() {
749 // The passthrough timer will only fire if if the user has held a finger in
750 // one of the passthrough corners for the duration of the passthrough timeout.
752 // Check that initial press isn't null. Also a check that if the initial
753 // corner press was released, then it should not be in corner passthrough.
754 if (!initial_press_ ||
755 touch_locations_.find(initial_press_->touch_id()) !=
756 touch_locations_.end()) {
757 LOG(ERROR) << "No initial press or the initial press has been released.";
760 gfx::Point location =
761 ToRoundedPoint(touch_locations_[initial_press_->touch_id()]);
762 int corner = FindEdgesWithinBounds(location, kSlopDistanceFromEdge);
763 if (corner != BOTTOM_LEFT_CORNER && corner != BOTTOM_RIGHT_CORNER)
764 return;
766 if (sound_timer_.IsRunning())
767 sound_timer_.Stop();
768 delegate_->PlayPassthroughEarcon();
769 SET_STATE(CORNER_PASSTHROUGH);
770 return;
773 void TouchExplorationController::DispatchEvent(ui::Event* event) {
774 ignore_result(
775 root_window_->GetHost()->dispatcher()->OnEventFromSource(event));
778 // This is an override for a function that is only called for timer-based events
779 // like long press. Events that are created synchronously as a result of
780 // certain touch events are added to the vector accessible via
781 // GetAndResetPendingGestures(). We only care about swipes (which are created
782 // synchronously), so we ignore this callback.
783 void TouchExplorationController::OnGestureEvent(ui::GestureEvent* gesture) {
786 void TouchExplorationController::ProcessGestureEvents() {
787 scoped_ptr<ScopedVector<ui::GestureEvent> > gestures(
788 gesture_provider_->GetAndResetPendingGestures());
789 if (gestures) {
790 for (ScopedVector<GestureEvent>::iterator i = gestures->begin();
791 i != gestures->end();
792 ++i) {
793 if ((*i)->type() == ui::ET_GESTURE_SWIPE &&
794 state_ == GESTURE_IN_PROGRESS) {
795 OnSwipeEvent(*i);
796 // The tap timer to leave gesture state is ended, and we now wait for
797 // all fingers to be released.
798 tap_timer_.Stop();
799 SET_STATE(WAIT_FOR_NO_FINGERS);
800 return;
802 if (state_ == SLIDE_GESTURE && (*i)->IsScrollGestureEvent()) {
803 SideSlideControl(*i);
809 void TouchExplorationController::SideSlideControl(ui::GestureEvent* gesture) {
810 ui::EventType type = gesture->type();
812 if (type == ET_GESTURE_SCROLL_BEGIN) {
813 delegate_->PlayVolumeAdjustEarcon();
816 if (type == ET_GESTURE_SCROLL_END) {
817 if (sound_timer_.IsRunning())
818 sound_timer_.Stop();
819 delegate_->PlayVolumeAdjustEarcon();
822 // If the user is in the corner of the right side of the screen, the volume
823 // will be automatically set to 100% or muted depending on which corner they
824 // are in. Otherwise, the user will be able to adjust the volume by sliding
825 // their finger along the right side of the screen. Volume is relative to
826 // where they are on the right side of the screen.
827 gfx::Point location = gesture->location();
828 int edge = FindEdgesWithinBounds(location, kSlopDistanceFromEdge);
829 if (!(edge & RIGHT_EDGE))
830 return;
832 if (edge & TOP_EDGE) {
833 delegate_->SetOutputLevel(100);
834 return;
836 if (edge & BOTTOM_EDGE) {
837 delegate_->SetOutputLevel(0);
838 return;
841 location = gesture->location();
842 root_window_->GetHost()->ConvertPointFromNativeScreen(&location);
843 float volume_adjust_height =
844 root_window_->bounds().height() - 2 * kMaxDistanceFromEdge;
845 float ratio = (location.y() - kMaxDistanceFromEdge) / volume_adjust_height;
846 float volume = 100 - 100 * ratio;
847 if (VLOG_on_) {
848 VLOG(0) << "\n Volume = " << volume
849 << "\n Location = " << location.ToString()
850 << "\n Bounds = " << root_window_->bounds().right();
852 delegate_->SetOutputLevel(int(volume));
855 void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) {
856 // A swipe gesture contains details for the direction in which the swipe
857 // occurred. TODO(evy) : Research which swipe results users most want and
858 // remap these swipes to the best events. Hopefully in the near future
859 // there will also be a menu for users to pick custom mappings.
860 GestureEventDetails event_details = swipe_gesture->details();
861 int num_fingers = event_details.touch_points();
862 if(VLOG_on_)
863 VLOG(0) << "\nSwipe with " << num_fingers << " fingers.";
865 if (num_fingers > 4)
866 return;
868 if (event_details.swipe_left() &&
869 !left_swipe_gestures_[num_fingers].is_null()) {
870 left_swipe_gestures_[num_fingers].Run();
871 } else if (event_details.swipe_right() &&
872 !right_swipe_gestures_[num_fingers].is_null()) {
873 right_swipe_gestures_[num_fingers].Run();
874 } else if (event_details.swipe_up() &&
875 !up_swipe_gestures_[num_fingers].is_null()) {
876 up_swipe_gestures_[num_fingers].Run();
877 } else if (event_details.swipe_down() &&
878 !down_swipe_gestures_[num_fingers].is_null()) {
879 down_swipe_gestures_[num_fingers].Run();
883 int TouchExplorationController::FindEdgesWithinBounds(gfx::Point point,
884 float bounds) {
885 // Since GetBoundsInScreen is in DIPs but point is not, then point needs to be
886 // converted.
887 root_window_->GetHost()->ConvertPointFromNativeScreen(&point);
888 gfx::Rect window = root_window_->GetBoundsInScreen();
890 float left_edge_limit = window.x() + bounds;
891 float right_edge_limit = window.right() - bounds;
892 float top_edge_limit = window.y() + bounds;
893 float bottom_edge_limit = window.bottom() - bounds;
895 // Bitwise manipulation in order to determine where on the screen the point
896 // lies. If more than one bit is turned on, then it is a corner where the two
897 // bit/edges intersect. Otherwise, if no bits are turned on, the point must be
898 // in the center of the screen.
899 int result = NO_EDGE;
900 if (point.x() < left_edge_limit)
901 result |= LEFT_EDGE;
902 if (point.x() > right_edge_limit)
903 result |= RIGHT_EDGE;
904 if (point.y() < top_edge_limit)
905 result |= TOP_EDGE;
906 if (point.y() > bottom_edge_limit)
907 result |= BOTTOM_EDGE;
908 return result;
911 void TouchExplorationController::DispatchShiftSearchKeyEvent(
912 const ui::KeyboardCode third_key) {
913 // In order to activate the shortcut shift+search+<arrow key>
914 // three KeyPressed events must be dispatched in succession along
915 // with three KeyReleased events.
917 ui::KeyEvent shift_down(
918 ui::ET_KEY_PRESSED, ui::VKEY_SHIFT, ui::EF_SHIFT_DOWN);
919 ui::KeyEvent search_down(
920 ui::ET_KEY_PRESSED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN);
921 ui::KeyEvent third_key_down(ui::ET_KEY_PRESSED, third_key, ui::EF_SHIFT_DOWN);
923 ui::KeyEvent third_key_up(ui::ET_KEY_RELEASED, third_key, ui::EF_SHIFT_DOWN);
924 ui::KeyEvent search_up(
925 ui::ET_KEY_RELEASED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN);
926 ui ::KeyEvent shift_up(ui::ET_KEY_RELEASED, ui::VKEY_SHIFT, ui::EF_NONE);
928 DispatchEvent(&shift_down);
929 DispatchEvent(&search_down);
930 DispatchEvent(&third_key_down);
931 DispatchEvent(&third_key_up);
932 DispatchEvent(&search_up);
933 DispatchEvent(&shift_up);
936 base::Closure TouchExplorationController::BindShiftSearchKeyEvent(
937 const ui::KeyboardCode third_key) {
938 return base::Bind(&TouchExplorationController::DispatchShiftSearchKeyEvent,
939 base::Unretained(this),
940 third_key);
943 void TouchExplorationController::DispatchKeyWithFlags(
944 const ui::KeyboardCode key,
945 int flags) {
946 ui::KeyEvent key_down(ui::ET_KEY_PRESSED, key, flags);
947 ui::KeyEvent key_up(ui::ET_KEY_RELEASED, key, flags);
948 DispatchEvent(&key_down);
949 DispatchEvent(&key_up);
950 if(VLOG_on_) {
951 VLOG(0) << "\nKey down: key code : " << key_down.key_code()
952 << ", flags: " << key_down.flags()
953 << "\nKey up: key code : " << key_up.key_code()
954 << ", flags: " << key_up.flags();
958 base::Closure TouchExplorationController::BindKeyEventWithFlags(
959 const ui::KeyboardCode key,
960 int flags) {
961 return base::Bind(&TouchExplorationController::DispatchKeyWithFlags,
962 base::Unretained(this),
963 key,
964 flags);
967 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent(
968 const gfx::PointF& location,
969 int flags) {
970 // The "synthesized" flag should be set on all events that don't have a
971 // backing native event.
972 flags |= ui::EF_IS_SYNTHESIZED;
974 // This flag is used to identify mouse move events that were generated from
975 // touch exploration in Chrome code.
976 flags |= ui::EF_TOUCH_ACCESSIBILITY;
978 // TODO(dmazzoni) http://crbug.com/391008 - get rid of this hack.
979 // This is a short-term workaround for the limitation that we're using
980 // the ChromeVox content script to process touch exploration events, but
981 // ChromeVox needs a way to distinguish between a real mouse move and a
982 // mouse move generated from touch exploration, so we have touch exploration
983 // pretend that the command key was down (which becomes the "meta" key in
984 // JavaScript). We can remove this hack when the ChromeVox content script
985 // goes away and native accessibility code sends a touch exploration
986 // event to the new ChromeVox background page via the automation api.
987 flags |= ui::EF_COMMAND_DOWN;
989 return scoped_ptr<ui::Event>(
990 new ui::MouseEvent(ui::ET_MOUSE_MOVED, location, location, flags, 0));
993 void TouchExplorationController::EnterTouchToMouseMode() {
994 aura::client::CursorClient* cursor_client =
995 aura::client::GetCursorClient(root_window_);
996 if (cursor_client && !cursor_client->IsMouseEventsEnabled())
997 cursor_client->EnableMouseEvents();
998 if (cursor_client && cursor_client->IsCursorVisible())
999 cursor_client->HideCursor();
1002 void TouchExplorationController::SetState(State new_state,
1003 const char* function_name) {
1004 state_ = new_state;
1005 VlogState(function_name);
1006 // These are the states the user can be in that will never result in a
1007 // gesture before the user returns to NO_FINGERS_DOWN. Therefore, if the
1008 // gesture provider still exists, it's reset to NULL until the user returns
1009 // to NO_FINGERS_DOWN.
1010 switch (new_state) {
1011 case SINGLE_TAP_RELEASED:
1012 case TOUCH_EXPLORE_RELEASED:
1013 case DOUBLE_TAP_PENDING:
1014 case TOUCH_RELEASE_PENDING:
1015 case TOUCH_EXPLORATION:
1016 case TOUCH_EXPLORE_SECOND_PRESS:
1017 case ONE_FINGER_PASSTHROUGH:
1018 case CORNER_PASSTHROUGH:
1019 case WAIT_FOR_NO_FINGERS:
1020 if (gesture_provider_.get())
1021 gesture_provider_.reset(NULL);
1022 break;
1023 case NO_FINGERS_DOWN:
1024 gesture_provider_.reset(new GestureProviderAura(this));
1025 if (sound_timer_.IsRunning())
1026 sound_timer_.Stop();
1027 tap_timer_.Stop();
1028 break;
1029 case SINGLE_TAP_PRESSED:
1030 case GESTURE_IN_PROGRESS:
1031 case SLIDE_GESTURE:
1032 case TWO_FINGER_TAP:
1033 break;
1037 void TouchExplorationController::VlogState(const char* function_name) {
1038 if (!VLOG_on_)
1039 return;
1040 if (prev_state_ == state_)
1041 return;
1042 prev_state_ = state_;
1043 const char* state_string = EnumStateToString(state_);
1044 VLOG(0) << "\n Function name: " << function_name
1045 << "\n State: " << state_string;
1048 void TouchExplorationController::VlogEvent(const ui::TouchEvent& touch_event,
1049 const char* function_name) {
1050 if (!VLOG_on_)
1051 return;
1053 if (prev_event_ != NULL &&
1054 prev_event_->type() == touch_event.type() &&
1055 prev_event_->touch_id() == touch_event.touch_id()){
1056 return;
1058 // The above statement prevents events of the same type and id from being
1059 // printed in a row. However, if two fingers are down, they would both be
1060 // moving and alternating printing move events unless we check for this.
1061 if (prev_event_ != NULL &&
1062 prev_event_->type() == ET_TOUCH_MOVED &&
1063 touch_event.type() == ET_TOUCH_MOVED){
1064 return;
1067 const std::string& type = touch_event.name();
1068 const gfx::PointF& location = touch_event.location_f();
1069 const int touch_id = touch_event.touch_id();
1071 VLOG(0) << "\n Function name: " << function_name
1072 << "\n Event Type: " << type
1073 << "\n Location: " << location.ToString()
1074 << "\n Touch ID: " << touch_id;
1075 prev_event_.reset(new TouchEvent(touch_event));
1078 const char* TouchExplorationController::EnumStateToString(State state) {
1079 switch (state) {
1080 case NO_FINGERS_DOWN:
1081 return "NO_FINGERS_DOWN";
1082 case SINGLE_TAP_PRESSED:
1083 return "SINGLE_TAP_PRESSED";
1084 case SINGLE_TAP_RELEASED:
1085 return "SINGLE_TAP_RELEASED";
1086 case TOUCH_EXPLORE_RELEASED:
1087 return "TOUCH_EXPLORE_RELEASED";
1088 case DOUBLE_TAP_PENDING:
1089 return "DOUBLE_TAP_PENDING";
1090 case TOUCH_RELEASE_PENDING:
1091 return "TOUCH_RELEASE_PENDING";
1092 case TOUCH_EXPLORATION:
1093 return "TOUCH_EXPLORATION";
1094 case GESTURE_IN_PROGRESS:
1095 return "GESTURE_IN_PROGRESS";
1096 case TOUCH_EXPLORE_SECOND_PRESS:
1097 return "TOUCH_EXPLORE_SECOND_PRESS";
1098 case CORNER_PASSTHROUGH:
1099 return "CORNER_PASSTHROUGH";
1100 case SLIDE_GESTURE:
1101 return "SLIDE_GESTURE";
1102 case ONE_FINGER_PASSTHROUGH:
1103 return "ONE_FINGER_PASSTHROUGH";
1104 case WAIT_FOR_NO_FINGERS:
1105 return "WAIT_FOR_NO_FINGERS";
1106 case TWO_FINGER_TAP:
1107 return "TWO_FINGER_TAP";
1109 return "Not a state";
1112 // TODO(evy, lisayin) : Just call abstracted methods on the delegate (e.g.
1113 // Swipe(Direction direction, int num_fingers)), and add the DispatchXYZ
1114 // methods to the delegate. Avoid the middle step of dispatching keys at all,
1115 // and simply have ChromeVox/ChromeOS complete the required action.
1117 void TouchExplorationController::InitializeSwipeGestureMaps() {
1118 // Gestures with one finger are used for navigation.
1119 left_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_LEFT);
1120 right_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_RIGHT);
1121 up_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_UP);
1122 down_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_DOWN);
1124 // Gestures with two fingers.
1125 left_swipe_gestures_[2] =
1126 BindKeyEventWithFlags(ui::VKEY_BROWSER_BACK, ui::EF_NONE);
1127 right_swipe_gestures_[2] =
1128 BindKeyEventWithFlags(ui::VKEY_BROWSER_FORWARD, ui::EF_NONE);
1129 // Jump to top.
1130 up_swipe_gestures_[2] = BindShiftSearchKeyEvent(ui::VKEY_A);
1131 // Read from here.
1132 down_swipe_gestures_[2] = BindShiftSearchKeyEvent(ui::VKEY_R);
1134 // Gestures with three fingers switch tabs left/right and scroll up/down.
1135 left_swipe_gestures_[3] = BindKeyEventWithFlags(
1136 ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
1137 right_swipe_gestures_[3] =
1138 BindKeyEventWithFlags(ui::VKEY_TAB, ui::EF_CONTROL_DOWN);
1139 up_swipe_gestures_[3] = BindKeyEventWithFlags(ui::VKEY_NEXT, ui::EF_NONE);
1140 down_swipe_gestures_[3] = BindKeyEventWithFlags(ui::VKEY_PRIOR, ui::EF_NONE);
1142 // Gestures with four fingers should probably eventually be used for rare
1143 // needs that are hard to access through menus.
1144 // Note that brightness levels are here because they can be important for low
1145 // vision users. However, none of these mappings are permanent.
1146 left_swipe_gestures_[4] =
1147 BindKeyEventWithFlags(ui::VKEY_BRIGHTNESS_DOWN, ui::EF_NONE);
1148 right_swipe_gestures_[4] =
1149 BindKeyEventWithFlags(VKEY_BRIGHTNESS_UP, ui::EF_NONE);
1150 up_swipe_gestures_[4] = BindKeyEventWithFlags(VKEY_BROWSER_HOME, ui::EF_NONE);
1151 down_swipe_gestures_[4] =
1152 BindKeyEventWithFlags(VKEY_BROWSER_REFRESH, ui::EF_NONE);
1155 float TouchExplorationController::GetSplitTapTouchSlop() {
1156 return gesture_detector_config_.touch_slop * 3;
1159 } // namespace ui