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 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.
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
);
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
);
185 return InTwoFingerTap(touch_event
, rewritten_event
);
188 return ui::EVENT_REWRITE_CONTINUE
;
191 ui::EventRewriteStatus
TouchExplorationController::NextDispatchEvent(
192 const ui::Event
& last_event
, scoped_ptr
<ui::Event
>* new_event
) {
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
);
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
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(
218 gesture_detector_config_
.longpress_timeout
,
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
));
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
) {
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
;
275 (event
.time_stamp() - initial_press_
->time_stamp()).InSecondsF();
276 float velocity
= distance
/ delta_time
;
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
);
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
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_
) {
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.
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
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
;
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
) {
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
;
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
;
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
) {
416 initial_press_
.reset(new TouchEvent(event
));
418 rewritten_event
->reset(
419 new ui::TouchEvent(ui::ET_TOUCH_PRESSED
,
420 last_touch_exploration_
->location(),
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
));
429 SET_STATE(TOUCH_EXPLORE_RELEASED
);
430 } else if (type
!= ui::ET_TOUCH_MOVED
) {
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_
,
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
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();
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
;
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.
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())
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
618 int edge
= FindEdgesWithinBounds(event
.location(), kSlopDistanceFromEdge
);
619 if (!(edge
& RIGHT_EDGE
) && (type
!= ui::ET_TOUCH_RELEASED
)) {
620 if (sound_timer_
.IsRunning()) {
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
,
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
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() {
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
,
695 &TouchExplorationController::OnTapTimerFired
);
698 void TouchExplorationController::OnTapTimerFired() {
700 case SINGLE_TAP_RELEASED
:
701 SET_STATE(NO_FINGERS_DOWN
);
703 case TOUCH_EXPLORE_RELEASED
:
704 SET_STATE(NO_FINGERS_DOWN
);
705 last_touch_exploration_
.reset(new TouchEvent(*initial_press_
));
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(),
716 DispatchEvent(passthrough_press
.get());
719 case SINGLE_TAP_PRESSED
:
720 if (passthrough_timer_
.IsRunning())
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
);
729 // Otherwise wait for all fingers to be lifted.
730 SET_STATE(WAIT_FOR_NO_FINGERS
);
733 SET_STATE(WAIT_FOR_NO_FINGERS
);
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
)
763 if (sound_timer_
.IsRunning())
765 delegate_
->PlayPassthroughEarcon();
766 SET_STATE(CORNER_PASSTHROUGH
);
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());
787 for (ScopedVector
<GestureEvent
>::iterator i
= gestures
->begin();
788 i
!= gestures
->end();
790 if ((*i
)->type() == ui::ET_GESTURE_SWIPE
&&
791 state_
== GESTURE_IN_PROGRESS
) {
793 // The tap timer to leave gesture state is ended, and we now wait for
794 // all fingers to be released.
796 SET_STATE(WAIT_FOR_NO_FINGERS
);
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())
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
))
829 if (edge
& TOP_EDGE
) {
830 delegate_
->SetOutputLevel(100);
833 if (edge
& BOTTOM_EDGE
) {
834 delegate_
->SetOutputLevel(0);
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
;
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();
860 VLOG(0) << "\nSwipe with " << num_fingers
<< " fingers.";
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
,
882 // Since GetBoundsInScreen is in DIPs but point is not, then point needs to be
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
)
899 if (point
.x() > right_edge_limit
)
900 result
|= RIGHT_EDGE
;
901 if (point
.y() < top_edge_limit
)
903 if (point
.y() > bottom_edge_limit
)
904 result
|= BOTTOM_EDGE
;
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),
940 void TouchExplorationController::DispatchKeyWithFlags(
941 const ui::KeyboardCode key
,
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
);
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
,
958 return base::Bind(&TouchExplorationController::DispatchKeyWithFlags
,
959 base::Unretained(this),
964 scoped_ptr
<ui::Event
> TouchExplorationController::CreateMouseMoveEvent(
965 const gfx::PointF
& location
,
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
) {
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
);
1020 case NO_FINGERS_DOWN
:
1021 gesture_provider_
.reset(new GestureProviderAura(this));
1022 if (sound_timer_
.IsRunning())
1023 sound_timer_
.Stop();
1026 case SINGLE_TAP_PRESSED
:
1027 case GESTURE_IN_PROGRESS
:
1029 case TWO_FINGER_TAP
:
1034 void TouchExplorationController::VlogState(const char* function_name
) {
1037 if (prev_state_
== state_
)
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
) {
1050 if (prev_event_
!= NULL
&&
1051 prev_event_
->type() == touch_event
.type() &&
1052 prev_event_
->touch_id() == touch_event
.touch_id()){
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
){
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
) {
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";
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
);
1127 up_swipe_gestures_
[2] = BindShiftSearchKeyEvent(ui::VKEY_A
);
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;