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__)
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
;
37 TouchExplorationController::TouchExplorationController(
38 aura::Window
* root_window
,
39 TouchExplorationControllerDelegate
* delegate
)
40 : root_window_(root_window
),
42 state_(NO_FINGERS_DOWN
),
43 gesture_provider_(new GestureProviderAura(this)),
44 prev_state_(NO_FINGERS_DOWN
),
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
75 if (tap_timer_
.IsRunning() &&
76 touch_event
.time_stamp() - initial_press_
->time_stamp() >
77 gesture_detector_config_
.double_tap_timeout
) {
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
;
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
) !=
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
);
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
151 if (gesture_provider_
.get()) {
152 ui::TouchEvent mutable_touch_event
= touch_event
;
153 if (gesture_provider_
->OnTouchEvent(&mutable_touch_event
)) {
154 gesture_provider_
->OnTouchEventAck(mutable_touch_event
.unique_event_id(),
157 ProcessGestureEvents();
160 // The rest of the processing depends on what state we're in.
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
);
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
);
188 return InTwoFingerTap(touch_event
, rewritten_event
);
191 return ui::EVENT_REWRITE_CONTINUE
;
194 ui::EventRewriteStatus
TouchExplorationController::NextDispatchEvent(
195 const ui::Event
& last_event
, scoped_ptr
<ui::Event
>* new_event
) {
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
);
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
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(
221 gesture_detector_config_
.longpress_timeout
,
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
));
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
) {
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
;
278 (event
.time_stamp() - initial_press_
->time_stamp()).InSecondsF();
279 float velocity
= distance
/ delta_time
;
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
);
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
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_
) {
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.
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
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
;
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
) {
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
;
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
;
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
) {
419 initial_press_
.reset(new TouchEvent(event
));
421 rewritten_event
->reset(
422 new ui::TouchEvent(ui::ET_TOUCH_PRESSED
,
423 last_touch_exploration_
->location(),
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
));
432 SET_STATE(TOUCH_EXPLORE_RELEASED
);
433 } else if (type
!= ui::ET_TOUCH_MOVED
) {
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_
,
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
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();
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
;
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.
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())
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
621 int edge
= FindEdgesWithinBounds(event
.location(), kSlopDistanceFromEdge
);
622 if (!(edge
& RIGHT_EDGE
) && (type
!= ui::ET_TOUCH_RELEASED
)) {
623 if (sound_timer_
.IsRunning()) {
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
,
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
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() {
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
,
698 &TouchExplorationController::OnTapTimerFired
);
701 void TouchExplorationController::OnTapTimerFired() {
703 case SINGLE_TAP_RELEASED
:
704 SET_STATE(NO_FINGERS_DOWN
);
706 case TOUCH_EXPLORE_RELEASED
:
707 SET_STATE(NO_FINGERS_DOWN
);
708 last_touch_exploration_
.reset(new TouchEvent(*initial_press_
));
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(),
719 DispatchEvent(passthrough_press
.get());
722 case SINGLE_TAP_PRESSED
:
723 if (passthrough_timer_
.IsRunning())
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
);
732 // Otherwise wait for all fingers to be lifted.
733 SET_STATE(WAIT_FOR_NO_FINGERS
);
736 SET_STATE(WAIT_FOR_NO_FINGERS
);
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
)
766 if (sound_timer_
.IsRunning())
768 delegate_
->PlayPassthroughEarcon();
769 SET_STATE(CORNER_PASSTHROUGH
);
773 void TouchExplorationController::DispatchEvent(ui::Event
* event
) {
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());
790 for (ScopedVector
<GestureEvent
>::iterator i
= gestures
->begin();
791 i
!= gestures
->end();
793 if ((*i
)->type() == ui::ET_GESTURE_SWIPE
&&
794 state_
== GESTURE_IN_PROGRESS
) {
796 // The tap timer to leave gesture state is ended, and we now wait for
797 // all fingers to be released.
799 SET_STATE(WAIT_FOR_NO_FINGERS
);
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())
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
))
832 if (edge
& TOP_EDGE
) {
833 delegate_
->SetOutputLevel(100);
836 if (edge
& BOTTOM_EDGE
) {
837 delegate_
->SetOutputLevel(0);
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
;
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();
863 VLOG(0) << "\nSwipe with " << num_fingers
<< " fingers.";
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
,
885 // Since GetBoundsInScreen is in DIPs but point is not, then point needs to be
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
)
902 if (point
.x() > right_edge_limit
)
903 result
|= RIGHT_EDGE
;
904 if (point
.y() < top_edge_limit
)
906 if (point
.y() > bottom_edge_limit
)
907 result
|= BOTTOM_EDGE
;
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),
943 void TouchExplorationController::DispatchKeyWithFlags(
944 const ui::KeyboardCode key
,
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
);
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
,
961 return base::Bind(&TouchExplorationController::DispatchKeyWithFlags
,
962 base::Unretained(this),
967 scoped_ptr
<ui::Event
> TouchExplorationController::CreateMouseMoveEvent(
968 const gfx::PointF
& location
,
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 make_scoped_ptr(new ui::MouseEvent(
990 ui::ET_MOUSE_MOVED
, location
, location
, ui::EventTimeForNow(), 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
) {
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
);
1023 case NO_FINGERS_DOWN
:
1024 gesture_provider_
.reset(new GestureProviderAura(this));
1025 if (sound_timer_
.IsRunning())
1026 sound_timer_
.Stop();
1029 case SINGLE_TAP_PRESSED
:
1030 case GESTURE_IN_PROGRESS
:
1032 case TWO_FINGER_TAP
:
1037 void TouchExplorationController::VlogState(const char* function_name
) {
1040 if (prev_state_
== state_
)
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
) {
1053 if (prev_event_
!= NULL
&&
1054 prev_event_
->type() == touch_event
.type() &&
1055 prev_event_
->touch_id() == touch_event
.touch_id()){
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
){
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
) {
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";
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
);
1130 up_swipe_gestures_
[2] = BindShiftSearchKeyEvent(ui::VKEY_A
);
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;