Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / chromeos / touch_exploration_controller.cc
blob3aed357bbc1664d30764e7be48d2c624ea11afe0
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 gesture_provider_->OnTouchEvent(touch_event);
153 gesture_provider_->OnTouchEventAck(false);
154 ProcessGestureEvents();
157 // The rest of the processing depends on what state we're in.
158 switch (state_) {
159 case NO_FINGERS_DOWN:
160 return InNoFingersDown(touch_event, rewritten_event);
161 case SINGLE_TAP_PRESSED:
162 return InSingleTapPressed(touch_event, rewritten_event);
163 case SINGLE_TAP_RELEASED:
164 case TOUCH_EXPLORE_RELEASED:
165 return InSingleTapOrTouchExploreReleased(touch_event, rewritten_event);
166 case DOUBLE_TAP_PENDING:
167 return InDoubleTapPending(touch_event, rewritten_event);
168 case TOUCH_RELEASE_PENDING:
169 return InTouchReleasePending(touch_event, rewritten_event);
170 case TOUCH_EXPLORATION:
171 return InTouchExploration(touch_event, rewritten_event);
172 case GESTURE_IN_PROGRESS:
173 return InGestureInProgress(touch_event, rewritten_event);
174 case TOUCH_EXPLORE_SECOND_PRESS:
175 return InTouchExploreSecondPress(touch_event, rewritten_event);
176 case SLIDE_GESTURE:
177 return InSlideGesture(touch_event, rewritten_event);
178 case ONE_FINGER_PASSTHROUGH:
179 return InOneFingerPassthrough(touch_event, rewritten_event);
180 case CORNER_PASSTHROUGH:
181 return InCornerPassthrough(touch_event, rewritten_event);
182 case WAIT_FOR_NO_FINGERS:
183 return InWaitForNoFingers(touch_event, rewritten_event);
184 case TWO_FINGER_TAP:
185 return InTwoFingerTap(touch_event, rewritten_event);
187 NOTREACHED();
188 return ui::EVENT_REWRITE_CONTINUE;
191 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
192 const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) {
193 NOTREACHED();
194 return ui::EVENT_REWRITE_CONTINUE;
197 ui::EventRewriteStatus TouchExplorationController::InNoFingersDown(
198 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
199 const ui::EventType type = event.type();
200 if (type != ui::ET_TOUCH_PRESSED) {
201 NOTREACHED() << "Unexpected event type received: " << event.name();
202 return ui::EVENT_REWRITE_CONTINUE;
205 // If the user enters the screen from the edge then send an earcon.
206 int edge = FindEdgesWithinBounds(event.location(), kLeavingScreenEdge);
207 if (edge != NO_EDGE)
208 delegate_->PlayEnterScreenEarcon();
210 int location = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge);
211 // If the press was at a corner, the user might go into corner passthrough
212 // instead.
213 bool in_a_bottom_corner =
214 (BOTTOM_LEFT_CORNER == location) || (BOTTOM_RIGHT_CORNER == location);
215 if (in_a_bottom_corner) {
216 passthrough_timer_.Start(
217 FROM_HERE,
218 gesture_detector_config_.longpress_timeout,
219 this,
220 &TouchExplorationController::OnPassthroughTimerFired);
222 initial_press_.reset(new TouchEvent(event));
223 initial_presses_[event.touch_id()] = event.location();
224 last_unused_finger_event_.reset(new TouchEvent(event));
225 StartTapTimer();
226 SET_STATE(SINGLE_TAP_PRESSED);
227 return ui::EVENT_REWRITE_DISCARD;
230 ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed(
231 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
232 const ui::EventType type = event.type();
234 int location = FindEdgesWithinBounds(event.location(), kMaxDistanceFromEdge);
235 bool in_a_bottom_corner =
236 (location == BOTTOM_LEFT_CORNER) || (location == BOTTOM_RIGHT_CORNER);
237 // If the event is from the initial press and the location is no longer in the
238 // corner, then we are not waiting for a corner passthrough anymore.
239 if (event.touch_id() == initial_press_->touch_id() && !in_a_bottom_corner) {
240 if (passthrough_timer_.IsRunning()) {
241 passthrough_timer_.Stop();
242 // Since the long press timer has been running, it is possible that the
243 // tap timer has timed out before the long press timer has. If the tap
244 // timer timeout has elapsed, then fire the tap timer.
245 if (event.time_stamp() - initial_press_->time_stamp() >
246 gesture_detector_config_.double_tap_timeout) {
247 OnTapTimerFired();
252 if (type == ui::ET_TOUCH_PRESSED) {
253 initial_presses_[event.touch_id()] = event.location();
254 SET_STATE(TWO_FINGER_TAP);
255 return EVENT_REWRITE_DISCARD;
256 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
257 if (passthrough_timer_.IsRunning())
258 passthrough_timer_.Stop();
259 if (current_touch_ids_.size() == 0 &&
260 event.touch_id() == initial_press_->touch_id()) {
261 SET_STATE(SINGLE_TAP_RELEASED);
262 } else if (current_touch_ids_.size() == 0) {
263 SET_STATE(NO_FINGERS_DOWN);
265 return EVENT_REWRITE_DISCARD;
266 } else if (type == ui::ET_TOUCH_MOVED) {
267 float distance = (event.location() - initial_press_->location()).Length();
268 // If the user does not move far enough from the original position, then the
269 // resulting movement should not be considered to be a deliberate gesture or
270 // touch exploration.
271 if (distance <= gesture_detector_config_.touch_slop)
272 return EVENT_REWRITE_DISCARD;
274 float delta_time =
275 (event.time_stamp() - initial_press_->time_stamp()).InSecondsF();
276 float velocity = distance / delta_time;
277 if (VLOG_on_) {
278 VLOG(0) << "\n Delta time: " << delta_time << "\n Distance: " << distance
279 << "\n Velocity of click: " << velocity
280 << "\n Minimum swipe velocity: "
281 << gesture_detector_config_.minimum_swipe_velocity;
283 // Change to slide gesture if the slide occurred at the right edge.
284 int edge = FindEdgesWithinBounds(event.location(), kMaxDistanceFromEdge);
285 if (edge & RIGHT_EDGE && edge != BOTTOM_RIGHT_CORNER) {
286 SET_STATE(SLIDE_GESTURE);
287 return InSlideGesture(event, rewritten_event);
290 // If the user moves fast enough from the initial touch location, start
291 // gesture detection. Otherwise, jump to the touch exploration mode early.
292 if (velocity > gesture_detector_config_.minimum_swipe_velocity) {
293 SET_STATE(GESTURE_IN_PROGRESS);
294 return InGestureInProgress(event, rewritten_event);
296 EnterTouchToMouseMode();
297 SET_STATE(TOUCH_EXPLORATION);
298 return InTouchExploration(event, rewritten_event);
300 NOTREACHED();
301 return ui::EVENT_REWRITE_CONTINUE;
304 ui::EventRewriteStatus
305 TouchExplorationController::InSingleTapOrTouchExploreReleased(
306 const ui::TouchEvent& event,
307 scoped_ptr<ui::Event>* rewritten_event) {
308 const ui::EventType type = event.type();
309 // If there is more than one finger down, then discard to wait until no
310 // fingers are down.
311 if (current_touch_ids_.size() > 1) {
312 SET_STATE(WAIT_FOR_NO_FINGERS);
313 return ui::EVENT_REWRITE_DISCARD;
315 if (type == ui::ET_TOUCH_PRESSED) {
316 // If there is no touch exploration yet, we can't send a click, so discard.
317 if (!last_touch_exploration_) {
318 tap_timer_.Stop();
319 return ui::EVENT_REWRITE_DISCARD;
321 // This is the second tap in a double-tap (or double tap-hold).
322 // We set the tap timer. If it fires before the user lifts their finger,
323 // one-finger passthrough begins. Otherwise, there is a touch press and
324 // release at the location of the last touch exploration.
325 SET_STATE(DOUBLE_TAP_PENDING);
326 // The old tap timer (from the initial click) is stopped if it is still
327 // going, and the new one is set.
328 tap_timer_.Stop();
329 StartTapTimer();
330 // This will update as the finger moves before a possible passthrough, and
331 // will determine the offset.
332 last_unused_finger_event_.reset(new ui::TouchEvent(event));
333 return ui::EVENT_REWRITE_DISCARD;
334 } else if (type == ui::ET_TOUCH_RELEASED && !last_touch_exploration_) {
335 // If the previous press was discarded, we need to also handle its
336 // release.
337 if (current_touch_ids_.size() == 0) {
338 SET_STATE(NO_FINGERS_DOWN);
340 return ui::EVENT_REWRITE_DISCARD;
341 } else if (type == ui::ET_TOUCH_MOVED) {
342 return ui::EVENT_REWRITE_DISCARD;
344 NOTREACHED();
345 return ui::EVENT_REWRITE_CONTINUE;
348 ui::EventRewriteStatus TouchExplorationController::InDoubleTapPending(
349 const ui::TouchEvent& event,
350 scoped_ptr<ui::Event>* rewritten_event) {
351 const ui::EventType type = event.type();
352 if (type == ui::ET_TOUCH_PRESSED) {
353 return ui::EVENT_REWRITE_DISCARD;
354 } else if (type == ui::ET_TOUCH_MOVED) {
355 // If the user moves far enough from the initial touch location (outside
356 // the "slop" region, jump to passthrough mode early.
357 float delta = (event.location() - initial_press_->location()).Length();
358 if (delta > gesture_detector_config_.touch_slop) {
359 tap_timer_.Stop();
360 OnTapTimerFired();
362 return EVENT_REWRITE_DISCARD;
363 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
364 if (current_touch_ids_.size() != 0)
365 return EVENT_REWRITE_DISCARD;
367 scoped_ptr<ui::TouchEvent> touch_press;
368 touch_press.reset(new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
369 last_touch_exploration_->location(),
370 initial_press_->touch_id(),
371 event.time_stamp()));
372 DispatchEvent(touch_press.get());
374 rewritten_event->reset(
375 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
376 last_touch_exploration_->location(),
377 initial_press_->touch_id(),
378 event.time_stamp()));
379 (*rewritten_event)->set_flags(event.flags());
380 SET_STATE(NO_FINGERS_DOWN);
381 return ui::EVENT_REWRITE_REWRITTEN;
383 NOTREACHED();
384 return ui::EVENT_REWRITE_CONTINUE;
387 ui::EventRewriteStatus TouchExplorationController::InTouchReleasePending(
388 const ui::TouchEvent& event,
389 scoped_ptr<ui::Event>* rewritten_event) {
390 const ui::EventType type = event.type();
391 if (type == ui::ET_TOUCH_PRESSED || type == ui::ET_TOUCH_MOVED) {
392 return ui::EVENT_REWRITE_DISCARD;
393 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
394 if (current_touch_ids_.size() != 0)
395 return EVENT_REWRITE_DISCARD;
397 rewritten_event->reset(
398 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
399 last_touch_exploration_->location(),
400 initial_press_->touch_id(),
401 event.time_stamp()));
402 (*rewritten_event)->set_flags(event.flags());
403 SET_STATE(NO_FINGERS_DOWN);
404 return ui::EVENT_REWRITE_REWRITTEN;
406 NOTREACHED();
407 return ui::EVENT_REWRITE_CONTINUE;
410 ui::EventRewriteStatus TouchExplorationController::InTouchExploration(
411 const ui::TouchEvent& event,
412 scoped_ptr<ui::Event>* rewritten_event) {
413 const ui::EventType type = event.type();
414 if (type == ui::ET_TOUCH_PRESSED) {
415 // Handle split-tap.
416 initial_press_.reset(new TouchEvent(event));
417 tap_timer_.Stop();
418 rewritten_event->reset(
419 new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
420 last_touch_exploration_->location(),
421 event.touch_id(),
422 event.time_stamp()));
423 (*rewritten_event)->set_flags(event.flags());
424 SET_STATE(TOUCH_EXPLORE_SECOND_PRESS);
425 return ui::EVENT_REWRITE_REWRITTEN;
426 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
427 initial_press_.reset(new TouchEvent(event));
428 StartTapTimer();
429 SET_STATE(TOUCH_EXPLORE_RELEASED);
430 } else if (type != ui::ET_TOUCH_MOVED) {
431 NOTREACHED();
432 return ui::EVENT_REWRITE_CONTINUE;
435 // Rewrite as a mouse-move event.
436 *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags());
437 last_touch_exploration_.reset(new TouchEvent(event));
438 return ui::EVENT_REWRITE_REWRITTEN;
441 ui::EventRewriteStatus TouchExplorationController::InGestureInProgress(
442 const ui::TouchEvent& event,
443 scoped_ptr<ui::Event>* rewritten_event) {
444 // The events were sent to the gesture provider in RewriteEvent already.
445 // If no gesture is registered before the tap timer times out, the state
446 // will change to "wait for no fingers down" or "touch exploration" depending
447 // on the number of fingers down, and this function will stop being called.
448 if (current_touch_ids_.size() == 0) {
449 SET_STATE(NO_FINGERS_DOWN);
451 return ui::EVENT_REWRITE_DISCARD;
454 ui::EventRewriteStatus TouchExplorationController::InCornerPassthrough(
455 const ui::TouchEvent& event,
456 scoped_ptr<ui::Event>* rewritten_event) {
457 ui::EventType type = event.type();
459 // If the first finger has left the corner, then exit passthrough.
460 if (event.touch_id() == initial_press_->touch_id()) {
461 int edges = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge);
462 bool in_a_bottom_corner = (edges == BOTTOM_LEFT_CORNER) ||
463 (edges == BOTTOM_RIGHT_CORNER);
464 if (type == ui::ET_TOUCH_MOVED && in_a_bottom_corner)
465 return ui::EVENT_REWRITE_DISCARD;
467 if (current_touch_ids_.size() == 0) {
468 SET_STATE(NO_FINGERS_DOWN);
469 return ui::EVENT_REWRITE_DISCARD;
471 SET_STATE(WAIT_FOR_NO_FINGERS);
472 return ui::EVENT_REWRITE_DISCARD;
475 rewritten_event->reset(new ui::TouchEvent(
476 type, event.location(), event.touch_id(), event.time_stamp()));
477 (*rewritten_event)->set_flags(event.flags());
479 if (current_touch_ids_.size() == 0)
480 SET_STATE(NO_FINGERS_DOWN);
482 return ui::EVENT_REWRITE_REWRITTEN;
485 ui::EventRewriteStatus TouchExplorationController::InOneFingerPassthrough(
486 const ui::TouchEvent& event,
487 scoped_ptr<ui::Event>* rewritten_event) {
488 if (event.touch_id() != initial_press_->touch_id()) {
489 if (current_touch_ids_.size() == 0) {
490 SET_STATE(NO_FINGERS_DOWN);
492 return ui::EVENT_REWRITE_DISCARD;
494 rewritten_event->reset(
495 new ui::TouchEvent(event.type(),
496 event.location() - passthrough_offset_,
497 event.touch_id(),
498 event.time_stamp()));
500 (*rewritten_event)->set_flags(event.flags());
501 if (current_touch_ids_.size() == 0) {
502 SET_STATE(NO_FINGERS_DOWN);
504 return ui::EVENT_REWRITE_REWRITTEN;
507 ui::EventRewriteStatus TouchExplorationController::InTouchExploreSecondPress(
508 const ui::TouchEvent& event,
509 scoped_ptr<ui::Event>* rewritten_event) {
510 ui::EventType type = event.type();
511 gfx::PointF location = event.location_f();
512 if (type == ui::ET_TOUCH_PRESSED) {
513 // A third finger being pressed means that a split tap can no longer go
514 // through. The user enters the wait state, Since there has already been
515 // a press dispatched when split tap began, the touch needs to be
516 // cancelled.
517 rewritten_event->reset(
518 new ui::TouchEvent(ui::ET_TOUCH_CANCELLED,
519 last_touch_exploration_->location(),
520 initial_press_->touch_id(),
521 event.time_stamp()));
522 (*rewritten_event)->set_flags(event.flags());
523 SET_STATE(WAIT_FOR_NO_FINGERS);
524 return ui::EVENT_REWRITE_REWRITTEN;
525 } else if (type == ui::ET_TOUCH_MOVED) {
526 // If the fingers have moved too far from their original locations,
527 // the user can no longer split tap.
528 ui::TouchEvent* original_touch;
529 if (event.touch_id() == last_touch_exploration_->touch_id())
530 original_touch = last_touch_exploration_.get();
531 else if (event.touch_id() == initial_press_->touch_id())
532 original_touch = initial_press_.get();
533 else {
534 NOTREACHED();
535 SET_STATE(WAIT_FOR_NO_FINGERS);
536 return ui::EVENT_REWRITE_DISCARD;
538 // Check the distance between the current finger location and the original
539 // location. The slop for this is a bit more generous since keeping two
540 // fingers in place is a bit harder. If the user has left the slop, the
541 // split tap press (which was previous dispatched) is lifted with a touch
542 // cancelled, and the user enters the wait state.
543 if ((event.location() - original_touch->location()).Length() >
544 GetSplitTapTouchSlop()) {
545 rewritten_event->reset(
546 new ui::TouchEvent(ui::ET_TOUCH_CANCELLED,
547 last_touch_exploration_->location(),
548 initial_press_->touch_id(),
549 event.time_stamp()));
550 (*rewritten_event)->set_flags(event.flags());
551 SET_STATE(WAIT_FOR_NO_FINGERS);
552 return ui::EVENT_REWRITE_REWRITTEN;
554 return ui::EVENT_REWRITE_DISCARD;
555 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
556 // If the touch exploration finger is lifted, there is no option to return
557 // to touch explore anymore. The remaining finger acts as a pending
558 // tap or long tap for the last touch explore location.
559 if (event.touch_id() == last_touch_exploration_->touch_id()){
560 SET_STATE(TOUCH_RELEASE_PENDING);
561 return EVENT_REWRITE_DISCARD;
564 // Continue to release the touch only if the touch explore finger is the
565 // only finger remaining.
566 if (current_touch_ids_.size() != 1)
567 return EVENT_REWRITE_DISCARD;
569 // Rewrite at location of last touch exploration.
570 rewritten_event->reset(
571 new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
572 last_touch_exploration_->location(),
573 initial_press_->touch_id(),
574 event.time_stamp()));
575 (*rewritten_event)->set_flags(event.flags());
576 SET_STATE(TOUCH_EXPLORATION);
577 EnterTouchToMouseMode();
578 return ui::EVENT_REWRITE_REWRITTEN;
580 NOTREACHED();
581 return ui::EVENT_REWRITE_CONTINUE;
584 ui::EventRewriteStatus TouchExplorationController::InWaitForNoFingers(
585 const ui::TouchEvent& event,
586 scoped_ptr<ui::Event>* rewritten_event) {
587 if (current_touch_ids_.size() == 0)
588 SET_STATE(NO_FINGERS_DOWN);
589 return EVENT_REWRITE_DISCARD;
592 void TouchExplorationController::PlaySoundForTimer() {
593 delegate_->PlayVolumeAdjustEarcon();
596 ui::EventRewriteStatus TouchExplorationController::InSlideGesture(
597 const ui::TouchEvent& event,
598 scoped_ptr<ui::Event>* rewritten_event) {
599 // The timer should not fire when sliding.
600 tap_timer_.Stop();
602 ui::EventType type = event.type();
603 // If additional fingers are added before a swipe gesture has been registered,
604 // then wait until all fingers have been lifted.
605 if (type == ui::ET_TOUCH_PRESSED ||
606 event.touch_id() != initial_press_->touch_id()) {
607 if (sound_timer_.IsRunning())
608 sound_timer_.Stop();
609 SET_STATE(WAIT_FOR_NO_FINGERS);
610 return EVENT_REWRITE_DISCARD;
613 // There should not be more than one finger down.
614 DCHECK(current_touch_ids_.size() <= 1);
616 // Allows user to return to the edge to adjust the sound if they have left the
617 // boundaries.
618 int edge = FindEdgesWithinBounds(event.location(), kSlopDistanceFromEdge);
619 if (!(edge & RIGHT_EDGE) && (type != ui::ET_TOUCH_RELEASED)) {
620 if (sound_timer_.IsRunning()) {
621 sound_timer_.Stop();
623 return EVENT_REWRITE_DISCARD;
626 // This can occur if the user leaves the screen edge and then returns to it to
627 // continue adjusting the sound.
628 if (!sound_timer_.IsRunning()) {
629 sound_timer_.Start(FROM_HERE,
630 kSoundDelay,
631 this,
632 &ui::TouchExplorationController::PlaySoundForTimer);
633 delegate_->PlayVolumeAdjustEarcon();
636 if (current_touch_ids_.size() == 0) {
637 SET_STATE(NO_FINGERS_DOWN);
639 return ui::EVENT_REWRITE_DISCARD;
642 ui::EventRewriteStatus TouchExplorationController::InTwoFingerTap(
643 const ui::TouchEvent& event,
644 scoped_ptr<ui::Event>* rewritten_event) {
645 ui::EventType type = event.type();
646 if (type == ui::ET_TOUCH_PRESSED) {
647 // This is now a three finger gesture.
648 SET_STATE(GESTURE_IN_PROGRESS);
649 return ui::EVENT_REWRITE_DISCARD;
652 if (type == ui::ET_TOUCH_MOVED) {
653 // Determine if it was a swipe.
654 gfx::Point original_location = initial_presses_[event.touch_id()];
655 float distance = (event.location() - original_location).Length();
656 // If the user moves too far from the original position, consider the
657 // movement a swipe.
658 if (distance > gesture_detector_config_.touch_slop) {
659 SET_STATE(GESTURE_IN_PROGRESS);
661 return ui::EVENT_REWRITE_DISCARD;
664 if (current_touch_ids_.size() != 0)
665 return ui::EVENT_REWRITE_DISCARD;
667 if (type == ui::ET_TOUCH_RELEASED) {
668 // In ChromeVox, pressing control will stop ChromeVox from speaking.
669 ui::KeyEvent control_down(
670 ui::ET_KEY_PRESSED, ui::VKEY_CONTROL, ui::EF_CONTROL_DOWN);
671 ui::KeyEvent control_up(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL, ui::EF_NONE);
673 DispatchEvent(&control_down);
674 DispatchEvent(&control_up);
675 SET_STATE(NO_FINGERS_DOWN);
676 return ui::EVENT_REWRITE_DISCARD;
678 return ui::EVENT_REWRITE_DISCARD;
681 base::TimeDelta TouchExplorationController::Now() {
682 if (tick_clock_) {
683 // This is the same as what EventTimeForNow() does, but here we do it
684 // with a clock that can be replaced with a simulated clock for tests.
685 return base::TimeDelta::FromInternalValue(
686 tick_clock_->NowTicks().ToInternalValue());
688 return ui::EventTimeForNow();
691 void TouchExplorationController::StartTapTimer() {
692 tap_timer_.Start(FROM_HERE,
693 gesture_detector_config_.double_tap_timeout,
694 this,
695 &TouchExplorationController::OnTapTimerFired);
698 void TouchExplorationController::OnTapTimerFired() {
699 switch (state_) {
700 case SINGLE_TAP_RELEASED:
701 SET_STATE(NO_FINGERS_DOWN);
702 break;
703 case TOUCH_EXPLORE_RELEASED:
704 SET_STATE(NO_FINGERS_DOWN);
705 last_touch_exploration_.reset(new TouchEvent(*initial_press_));
706 return;
707 case DOUBLE_TAP_PENDING: {
708 SET_STATE(ONE_FINGER_PASSTHROUGH);
709 passthrough_offset_ = last_unused_finger_event_->location() -
710 last_touch_exploration_->location();
711 scoped_ptr<ui::TouchEvent> passthrough_press(
712 new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
713 last_touch_exploration_->location(),
714 last_unused_finger_event_->touch_id(),
715 Now()));
716 DispatchEvent(passthrough_press.get());
717 return;
719 case SINGLE_TAP_PRESSED:
720 if (passthrough_timer_.IsRunning())
721 return;
722 case GESTURE_IN_PROGRESS:
723 // If only one finger is down, go into touch exploration.
724 if (current_touch_ids_.size() == 1) {
725 EnterTouchToMouseMode();
726 SET_STATE(TOUCH_EXPLORATION);
727 break;
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;
738 EnterTouchToMouseMode();
739 scoped_ptr<ui::Event> mouse_move =
740 CreateMouseMoveEvent(initial_press_->location(), initial_press_->flags());
741 DispatchEvent(mouse_move.get());
742 last_touch_exploration_.reset(new TouchEvent(*initial_press_));
745 void TouchExplorationController::OnPassthroughTimerFired() {
746 // The passthrough timer will only fire if if the user has held a finger in
747 // one of the passthrough corners for the duration of the passthrough timeout.
749 // Check that initial press isn't null. Also a check that if the initial
750 // corner press was released, then it should not be in corner passthrough.
751 if (!initial_press_ ||
752 touch_locations_.find(initial_press_->touch_id()) !=
753 touch_locations_.end()) {
754 LOG(ERROR) << "No initial press or the initial press has been released.";
757 gfx::Point location =
758 ToRoundedPoint(touch_locations_[initial_press_->touch_id()]);
759 int corner = FindEdgesWithinBounds(location, kSlopDistanceFromEdge);
760 if (corner != BOTTOM_LEFT_CORNER && corner != BOTTOM_RIGHT_CORNER)
761 return;
763 if (sound_timer_.IsRunning())
764 sound_timer_.Stop();
765 delegate_->PlayPassthroughEarcon();
766 SET_STATE(CORNER_PASSTHROUGH);
767 return;
770 void TouchExplorationController::DispatchEvent(ui::Event* event) {
771 ui::EventDispatchDetails result ALLOW_UNUSED =
772 root_window_->GetHost()->dispatcher()->OnEventFromSource(event);
775 // This is an override for a function that is only called for timer-based events
776 // like long press. Events that are created synchronously as a result of
777 // certain touch events are added to the vector accessible via
778 // GetAndResetPendingGestures(). We only care about swipes (which are created
779 // synchronously), so we ignore this callback.
780 void TouchExplorationController::OnGestureEvent(ui::GestureEvent* gesture) {
783 void TouchExplorationController::ProcessGestureEvents() {
784 scoped_ptr<ScopedVector<ui::GestureEvent> > gestures(
785 gesture_provider_->GetAndResetPendingGestures());
786 if (gestures) {
787 for (ScopedVector<GestureEvent>::iterator i = gestures->begin();
788 i != gestures->end();
789 ++i) {
790 if ((*i)->type() == ui::ET_GESTURE_SWIPE &&
791 state_ == GESTURE_IN_PROGRESS) {
792 OnSwipeEvent(*i);
793 // The tap timer to leave gesture state is ended, and we now wait for
794 // all fingers to be released.
795 tap_timer_.Stop();
796 SET_STATE(WAIT_FOR_NO_FINGERS);
797 return;
799 if (state_ == SLIDE_GESTURE && (*i)->IsScrollGestureEvent()) {
800 SideSlideControl(*i);
806 void TouchExplorationController::SideSlideControl(ui::GestureEvent* gesture) {
807 ui::EventType type = gesture->type();
809 if (type == ET_GESTURE_SCROLL_BEGIN) {
810 delegate_->PlayVolumeAdjustEarcon();
813 if (type == ET_GESTURE_SCROLL_END) {
814 if (sound_timer_.IsRunning())
815 sound_timer_.Stop();
816 delegate_->PlayVolumeAdjustEarcon();
819 // If the user is in the corner of the right side of the screen, the volume
820 // will be automatically set to 100% or muted depending on which corner they
821 // are in. Otherwise, the user will be able to adjust the volume by sliding
822 // their finger along the right side of the screen. Volume is relative to
823 // where they are on the right side of the screen.
824 gfx::Point location = gesture->location();
825 int edge = FindEdgesWithinBounds(location, kSlopDistanceFromEdge);
826 if (!(edge & RIGHT_EDGE))
827 return;
829 if (edge & TOP_EDGE) {
830 delegate_->SetOutputLevel(100);
831 return;
833 if (edge & BOTTOM_EDGE) {
834 delegate_->SetOutputLevel(0);
835 return;
838 location = gesture->location();
839 root_window_->GetHost()->ConvertPointFromNativeScreen(&location);
840 float volume_adjust_height =
841 root_window_->bounds().height() - 2 * kMaxDistanceFromEdge;
842 float ratio = (location.y() - kMaxDistanceFromEdge) / volume_adjust_height;
843 float volume = 100 - 100 * ratio;
844 if (VLOG_on_) {
845 VLOG(0) << "\n Volume = " << volume
846 << "\n Location = " << location.ToString()
847 << "\n Bounds = " << root_window_->bounds().right();
849 delegate_->SetOutputLevel(int(volume));
852 void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) {
853 // A swipe gesture contains details for the direction in which the swipe
854 // occurred. TODO(evy) : Research which swipe results users most want and
855 // remap these swipes to the best events. Hopefully in the near future
856 // there will also be a menu for users to pick custom mappings.
857 GestureEventDetails event_details = swipe_gesture->details();
858 int num_fingers = event_details.touch_points();
859 if(VLOG_on_)
860 VLOG(0) << "\nSwipe with " << num_fingers << " fingers.";
862 if (num_fingers > 4)
863 return;
865 if (event_details.swipe_left() &&
866 !left_swipe_gestures_[num_fingers].is_null()) {
867 left_swipe_gestures_[num_fingers].Run();
868 } else if (event_details.swipe_right() &&
869 !right_swipe_gestures_[num_fingers].is_null()) {
870 right_swipe_gestures_[num_fingers].Run();
871 } else if (event_details.swipe_up() &&
872 !up_swipe_gestures_[num_fingers].is_null()) {
873 up_swipe_gestures_[num_fingers].Run();
874 } else if (event_details.swipe_down() &&
875 !down_swipe_gestures_[num_fingers].is_null()) {
876 down_swipe_gestures_[num_fingers].Run();
880 int TouchExplorationController::FindEdgesWithinBounds(gfx::Point point,
881 float bounds) {
882 // Since GetBoundsInScreen is in DIPs but point is not, then point needs to be
883 // converted.
884 root_window_->GetHost()->ConvertPointFromNativeScreen(&point);
885 gfx::Rect window = root_window_->GetBoundsInScreen();
887 float left_edge_limit = window.x() + bounds;
888 float right_edge_limit = window.right() - bounds;
889 float top_edge_limit = window.y() + bounds;
890 float bottom_edge_limit = window.bottom() - bounds;
892 // Bitwise manipulation in order to determine where on the screen the point
893 // lies. If more than one bit is turned on, then it is a corner where the two
894 // bit/edges intersect. Otherwise, if no bits are turned on, the point must be
895 // in the center of the screen.
896 int result = NO_EDGE;
897 if (point.x() < left_edge_limit)
898 result |= LEFT_EDGE;
899 if (point.x() > right_edge_limit)
900 result |= RIGHT_EDGE;
901 if (point.y() < top_edge_limit)
902 result |= TOP_EDGE;
903 if (point.y() > bottom_edge_limit)
904 result |= BOTTOM_EDGE;
905 return result;
908 void TouchExplorationController::DispatchShiftSearchKeyEvent(
909 const ui::KeyboardCode third_key) {
910 // In order to activate the shortcut shift+search+<arrow key>
911 // three KeyPressed events must be dispatched in succession along
912 // with three KeyReleased events.
914 ui::KeyEvent shift_down(
915 ui::ET_KEY_PRESSED, ui::VKEY_SHIFT, ui::EF_SHIFT_DOWN);
916 ui::KeyEvent search_down(
917 ui::ET_KEY_PRESSED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN);
918 ui::KeyEvent third_key_down(ui::ET_KEY_PRESSED, third_key, ui::EF_SHIFT_DOWN);
920 ui::KeyEvent third_key_up(ui::ET_KEY_RELEASED, third_key, ui::EF_SHIFT_DOWN);
921 ui::KeyEvent search_up(
922 ui::ET_KEY_RELEASED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN);
923 ui ::KeyEvent shift_up(ui::ET_KEY_RELEASED, ui::VKEY_SHIFT, ui::EF_NONE);
925 DispatchEvent(&shift_down);
926 DispatchEvent(&search_down);
927 DispatchEvent(&third_key_down);
928 DispatchEvent(&third_key_up);
929 DispatchEvent(&search_up);
930 DispatchEvent(&shift_up);
933 base::Closure TouchExplorationController::BindShiftSearchKeyEvent(
934 const ui::KeyboardCode third_key) {
935 return base::Bind(&TouchExplorationController::DispatchShiftSearchKeyEvent,
936 base::Unretained(this),
937 third_key);
940 void TouchExplorationController::DispatchKeyWithFlags(
941 const ui::KeyboardCode key,
942 int flags) {
943 ui::KeyEvent key_down(ui::ET_KEY_PRESSED, key, flags);
944 ui::KeyEvent key_up(ui::ET_KEY_RELEASED, key, flags);
945 DispatchEvent(&key_down);
946 DispatchEvent(&key_up);
947 if(VLOG_on_) {
948 VLOG(0) << "\nKey down: key code : " << key_down.key_code()
949 << ", flags: " << key_down.flags()
950 << "\nKey up: key code : " << key_up.key_code()
951 << ", flags: " << key_up.flags();
955 base::Closure TouchExplorationController::BindKeyEventWithFlags(
956 const ui::KeyboardCode key,
957 int flags) {
958 return base::Bind(&TouchExplorationController::DispatchKeyWithFlags,
959 base::Unretained(this),
960 key,
961 flags);
964 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent(
965 const gfx::PointF& location,
966 int flags) {
967 // The "synthesized" flag should be set on all events that don't have a
968 // backing native event.
969 flags |= ui::EF_IS_SYNTHESIZED;
971 // This flag is used to identify mouse move events that were generated from
972 // touch exploration in Chrome code.
973 flags |= ui::EF_TOUCH_ACCESSIBILITY;
975 // TODO(dmazzoni) http://crbug.com/391008 - get rid of this hack.
976 // This is a short-term workaround for the limitation that we're using
977 // the ChromeVox content script to process touch exploration events, but
978 // ChromeVox needs a way to distinguish between a real mouse move and a
979 // mouse move generated from touch exploration, so we have touch exploration
980 // pretend that the command key was down (which becomes the "meta" key in
981 // JavaScript). We can remove this hack when the ChromeVox content script
982 // goes away and native accessibility code sends a touch exploration
983 // event to the new ChromeVox background page via the automation api.
984 flags |= ui::EF_COMMAND_DOWN;
986 return scoped_ptr<ui::Event>(
987 new ui::MouseEvent(ui::ET_MOUSE_MOVED, location, location, flags, 0));
990 void TouchExplorationController::EnterTouchToMouseMode() {
991 aura::client::CursorClient* cursor_client =
992 aura::client::GetCursorClient(root_window_);
993 if (cursor_client && !cursor_client->IsMouseEventsEnabled())
994 cursor_client->EnableMouseEvents();
995 if (cursor_client && cursor_client->IsCursorVisible())
996 cursor_client->HideCursor();
999 void TouchExplorationController::SetState(State new_state,
1000 const char* function_name) {
1001 state_ = new_state;
1002 VlogState(function_name);
1003 // These are the states the user can be in that will never result in a
1004 // gesture before the user returns to NO_FINGERS_DOWN. Therefore, if the
1005 // gesture provider still exists, it's reset to NULL until the user returns
1006 // to NO_FINGERS_DOWN.
1007 switch (new_state) {
1008 case SINGLE_TAP_RELEASED:
1009 case TOUCH_EXPLORE_RELEASED:
1010 case DOUBLE_TAP_PENDING:
1011 case TOUCH_RELEASE_PENDING:
1012 case TOUCH_EXPLORATION:
1013 case TOUCH_EXPLORE_SECOND_PRESS:
1014 case ONE_FINGER_PASSTHROUGH:
1015 case CORNER_PASSTHROUGH:
1016 case WAIT_FOR_NO_FINGERS:
1017 if (gesture_provider_.get())
1018 gesture_provider_.reset(NULL);
1019 break;
1020 case NO_FINGERS_DOWN:
1021 gesture_provider_.reset(new GestureProviderAura(this));
1022 if (sound_timer_.IsRunning())
1023 sound_timer_.Stop();
1024 tap_timer_.Stop();
1025 break;
1026 case SINGLE_TAP_PRESSED:
1027 case GESTURE_IN_PROGRESS:
1028 case SLIDE_GESTURE:
1029 case TWO_FINGER_TAP:
1030 break;
1034 void TouchExplorationController::VlogState(const char* function_name) {
1035 if (!VLOG_on_)
1036 return;
1037 if (prev_state_ == state_)
1038 return;
1039 prev_state_ = state_;
1040 const char* state_string = EnumStateToString(state_);
1041 VLOG(0) << "\n Function name: " << function_name
1042 << "\n State: " << state_string;
1045 void TouchExplorationController::VlogEvent(const ui::TouchEvent& touch_event,
1046 const char* function_name) {
1047 if (!VLOG_on_)
1048 return;
1050 if (prev_event_ != NULL &&
1051 prev_event_->type() == touch_event.type() &&
1052 prev_event_->touch_id() == touch_event.touch_id()){
1053 return;
1055 // The above statement prevents events of the same type and id from being
1056 // printed in a row. However, if two fingers are down, they would both be
1057 // moving and alternating printing move events unless we check for this.
1058 if (prev_event_ != NULL &&
1059 prev_event_->type() == ET_TOUCH_MOVED &&
1060 touch_event.type() == ET_TOUCH_MOVED){
1061 return;
1064 const std::string& type = touch_event.name();
1065 const gfx::PointF& location = touch_event.location_f();
1066 const int touch_id = touch_event.touch_id();
1068 VLOG(0) << "\n Function name: " << function_name
1069 << "\n Event Type: " << type
1070 << "\n Location: " << location.ToString()
1071 << "\n Touch ID: " << touch_id;
1072 prev_event_.reset(new TouchEvent(touch_event));
1075 const char* TouchExplorationController::EnumStateToString(State state) {
1076 switch (state) {
1077 case NO_FINGERS_DOWN:
1078 return "NO_FINGERS_DOWN";
1079 case SINGLE_TAP_PRESSED:
1080 return "SINGLE_TAP_PRESSED";
1081 case SINGLE_TAP_RELEASED:
1082 return "SINGLE_TAP_RELEASED";
1083 case TOUCH_EXPLORE_RELEASED:
1084 return "TOUCH_EXPLORE_RELEASED";
1085 case DOUBLE_TAP_PENDING:
1086 return "DOUBLE_TAP_PENDING";
1087 case TOUCH_RELEASE_PENDING:
1088 return "TOUCH_RELEASE_PENDING";
1089 case TOUCH_EXPLORATION:
1090 return "TOUCH_EXPLORATION";
1091 case GESTURE_IN_PROGRESS:
1092 return "GESTURE_IN_PROGRESS";
1093 case TOUCH_EXPLORE_SECOND_PRESS:
1094 return "TOUCH_EXPLORE_SECOND_PRESS";
1095 case CORNER_PASSTHROUGH:
1096 return "CORNER_PASSTHROUGH";
1097 case SLIDE_GESTURE:
1098 return "SLIDE_GESTURE";
1099 case ONE_FINGER_PASSTHROUGH:
1100 return "ONE_FINGER_PASSTHROUGH";
1101 case WAIT_FOR_NO_FINGERS:
1102 return "WAIT_FOR_NO_FINGERS";
1103 case TWO_FINGER_TAP:
1104 return "TWO_FINGER_TAP";
1106 return "Not a state";
1109 // TODO(evy, lisayin) : Just call abstracted methods on the delegate (e.g.
1110 // Swipe(Direction direction, int num_fingers)), and add the DispatchXYZ
1111 // methods to the delegate. Avoid the middle step of dispatching keys at all,
1112 // and simply have ChromeVox/ChromeOS complete the required action.
1114 void TouchExplorationController::InitializeSwipeGestureMaps() {
1115 // Gestures with one finger are used for navigation.
1116 left_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_LEFT);
1117 right_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_RIGHT);
1118 up_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_UP);
1119 down_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_DOWN);
1121 // Gestures with two fingers.
1122 left_swipe_gestures_[2] =
1123 BindKeyEventWithFlags(ui::VKEY_BROWSER_BACK, ui::EF_NONE);
1124 right_swipe_gestures_[2] =
1125 BindKeyEventWithFlags(ui::VKEY_BROWSER_FORWARD, ui::EF_NONE);
1126 // Jump to top.
1127 up_swipe_gestures_[2] = BindShiftSearchKeyEvent(ui::VKEY_A);
1128 // Read from here.
1129 down_swipe_gestures_[2] = BindShiftSearchKeyEvent(ui::VKEY_R);
1131 // Gestures with three fingers switch tabs left/right and scroll up/down.
1132 left_swipe_gestures_[3] = BindKeyEventWithFlags(
1133 ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
1134 right_swipe_gestures_[3] =
1135 BindKeyEventWithFlags(ui::VKEY_TAB, ui::EF_CONTROL_DOWN);
1136 up_swipe_gestures_[3] = BindKeyEventWithFlags(ui::VKEY_NEXT, ui::EF_NONE);
1137 down_swipe_gestures_[3] = BindKeyEventWithFlags(ui::VKEY_PRIOR, ui::EF_NONE);
1139 // Gestures with four fingers should probably eventually be used for rare
1140 // needs that are hard to access through menus.
1141 // Note that brightness levels are here because they can be important for low
1142 // vision users. However, none of these mappings are permanent.
1143 left_swipe_gestures_[4] =
1144 BindKeyEventWithFlags(ui::VKEY_BRIGHTNESS_DOWN, ui::EF_NONE);
1145 right_swipe_gestures_[4] =
1146 BindKeyEventWithFlags(VKEY_BRIGHTNESS_UP, ui::EF_NONE);
1147 up_swipe_gestures_[4] = BindKeyEventWithFlags(VKEY_BROWSER_HOME, ui::EF_NONE);
1148 down_swipe_gestures_[4] =
1149 BindKeyEventWithFlags(VKEY_BROWSER_REFRESH, ui::EF_NONE);
1152 const float TouchExplorationController::GetSplitTapTouchSlop() {
1153 return gesture_detector_config_.touch_slop * 3;
1156 } // namespace ui