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/events/gesture_detection/gesture_provider.h"
9 #include "base/auto_reset.h"
10 #include "base/trace_event/trace_event.h"
11 #include "ui/events/event_constants.h"
12 #include "ui/events/gesture_detection/gesture_event_data.h"
13 #include "ui/events/gesture_detection/gesture_listeners.h"
14 #include "ui/events/gesture_detection/motion_event.h"
15 #include "ui/events/gesture_detection/motion_event_generic.h"
16 #include "ui/events/gesture_detection/scale_gesture_listeners.h"
17 #include "ui/gfx/geometry/point_f.h"
22 // Double-tap drag zoom sensitivity (speed).
23 const float kDoubleTapDragZoomSpeed
= 0.005f
;
25 const char* GetMotionEventActionName(MotionEvent::Action action
) {
27 case MotionEvent::ACTION_POINTER_DOWN
:
28 return "ACTION_POINTER_DOWN";
29 case MotionEvent::ACTION_POINTER_UP
:
30 return "ACTION_POINTER_UP";
31 case MotionEvent::ACTION_DOWN
:
33 case MotionEvent::ACTION_UP
:
35 case MotionEvent::ACTION_CANCEL
:
36 return "ACTION_CANCEL";
37 case MotionEvent::ACTION_MOVE
:
43 gfx::RectF
ClampBoundingBox(const gfx::RectF
& bounds
,
46 float width
= bounds
.width();
47 float height
= bounds
.height();
49 width
= std::max(min_length
, width
);
50 height
= std::max(min_length
, height
);
53 width
= std::min(max_length
, width
);
54 height
= std::min(max_length
, height
);
56 const gfx::PointF center
= bounds
.CenterPoint();
58 center
.x() - width
/ 2.f
, center
.y() - height
/ 2.f
, width
, height
);
63 // GestureProvider:::Config
65 GestureProvider::Config::Config()
66 : display(gfx::Display::kInvalidDisplayID
, gfx::Rect(1, 1)),
67 disable_click_delay(false),
68 double_tap_support_for_platform_enabled(true),
69 gesture_begin_end_types_enabled(false),
70 min_gesture_bounds_length(0),
71 max_gesture_bounds_length(0) {
74 GestureProvider::Config::~Config() {
77 // GestureProvider::GestureListener
79 class GestureProvider::GestureListenerImpl
: public ScaleGestureListener
,
80 public GestureListener
,
81 public DoubleTapListener
{
83 GestureListenerImpl(const GestureProvider::Config
& config
,
84 GestureProviderClient
* client
)
87 gesture_detector_(config
.gesture_detector_config
, this, this),
88 scale_gesture_detector_(config
.scale_gesture_detector_config
, this),
89 snap_scroll_controller_(config
.gesture_detector_config
.touch_slop
,
90 config
.display
.size()),
91 ignore_multitouch_zoom_events_(false),
92 ignore_single_tap_(false),
93 pinch_event_sent_(false),
94 scroll_event_sent_(false),
95 max_diameter_before_show_press_(0),
96 show_press_event_sent_(false) {}
98 void OnTouchEvent(const MotionEvent
& event
) {
99 const bool in_scale_gesture
= IsScaleGestureDetectionInProgress();
100 snap_scroll_controller_
.SetSnapScrollMode(event
, in_scale_gesture
);
101 if (in_scale_gesture
)
102 SetIgnoreSingleTap(true);
104 const MotionEvent::Action action
= event
.GetAction();
105 if (action
== MotionEvent::ACTION_DOWN
) {
106 current_down_time_
= event
.GetEventTime();
107 current_longpress_time_
= base::TimeTicks();
108 ignore_single_tap_
= false;
109 scroll_event_sent_
= false;
110 pinch_event_sent_
= false;
111 show_press_event_sent_
= false;
112 gesture_detector_
.set_longpress_enabled(true);
113 tap_down_point_
= gfx::PointF(event
.GetX(), event
.GetY());
114 max_diameter_before_show_press_
= event
.GetTouchMajor();
117 gesture_detector_
.OnTouchEvent(event
);
118 scale_gesture_detector_
.OnTouchEvent(event
);
120 if (action
== MotionEvent::ACTION_UP
||
121 action
== MotionEvent::ACTION_CANCEL
) {
122 // Note: This call will have no effect if a fling was just generated, as
123 // |Fling()| will have already signalled an end to touch-scrolling.
124 if (scroll_event_sent_
)
125 Send(CreateGesture(ET_GESTURE_SCROLL_END
, event
));
126 current_down_time_
= base::TimeTicks();
127 } else if (action
== MotionEvent::ACTION_MOVE
) {
128 if (!show_press_event_sent_
&& !scroll_event_sent_
) {
129 max_diameter_before_show_press_
=
130 std::max(max_diameter_before_show_press_
, event
.GetTouchMajor());
135 void Send(GestureEventData gesture
) {
136 DCHECK(!gesture
.time
.is_null());
137 // The only valid events that should be sent without an active touch
138 // sequence are SHOW_PRESS and TAP, potentially triggered by the double-tap
140 DCHECK(!current_down_time_
.is_null() || gesture
.type() == ET_GESTURE_TAP
||
141 gesture
.type() == ET_GESTURE_SHOW_PRESS
||
142 gesture
.type() == ET_GESTURE_BEGIN
||
143 gesture
.type() == ET_GESTURE_END
);
145 if (gesture
.primary_tool_type
== MotionEvent::TOOL_TYPE_UNKNOWN
||
146 gesture
.primary_tool_type
== MotionEvent::TOOL_TYPE_FINGER
) {
147 gesture
.details
.set_bounding_box(
148 ClampBoundingBox(gesture
.details
.bounding_box_f(),
149 config_
.min_gesture_bounds_length
,
150 config_
.max_gesture_bounds_length
));
153 switch (gesture
.type()) {
154 case ET_GESTURE_LONG_PRESS
:
155 DCHECK(!IsScaleGestureDetectionInProgress());
156 current_longpress_time_
= gesture
.time
;
158 case ET_GESTURE_LONG_TAP
:
159 current_longpress_time_
= base::TimeTicks();
161 case ET_GESTURE_SCROLL_BEGIN
:
162 DCHECK(!scroll_event_sent_
);
163 scroll_event_sent_
= true;
165 case ET_GESTURE_SCROLL_END
:
166 DCHECK(scroll_event_sent_
);
167 if (pinch_event_sent_
)
168 Send(GestureEventData(ET_GESTURE_PINCH_END
, gesture
));
169 scroll_event_sent_
= false;
171 case ET_SCROLL_FLING_START
:
172 DCHECK(scroll_event_sent_
);
173 scroll_event_sent_
= false;
175 case ET_GESTURE_PINCH_BEGIN
:
176 DCHECK(!pinch_event_sent_
);
177 if (!scroll_event_sent_
)
178 Send(GestureEventData(ET_GESTURE_SCROLL_BEGIN
, gesture
));
179 pinch_event_sent_
= true;
181 case ET_GESTURE_PINCH_END
:
182 DCHECK(pinch_event_sent_
);
183 pinch_event_sent_
= false;
185 case ET_GESTURE_SHOW_PRESS
:
186 // It's possible that a double-tap drag zoom (from ScaleGestureDetector)
187 // will start before the press gesture fires (from GestureDetector), in
188 // which case the press should simply be dropped.
189 if (pinch_event_sent_
|| scroll_event_sent_
)
195 client_
->OnGestureEvent(gesture
);
196 GestureTouchUMAHistogram::RecordGestureEvent(gesture
);
199 // ScaleGestureListener implementation.
200 bool OnScaleBegin(const ScaleGestureDetector
& detector
,
201 const MotionEvent
& e
) override
{
202 if (ignore_multitouch_zoom_events_
&& !detector
.InDoubleTapMode())
207 void OnScaleEnd(const ScaleGestureDetector
& detector
,
208 const MotionEvent
& e
) override
{
209 if (!pinch_event_sent_
)
211 Send(CreateGesture(ET_GESTURE_PINCH_END
, e
));
214 bool OnScale(const ScaleGestureDetector
& detector
,
215 const MotionEvent
& e
) override
{
216 if (ignore_multitouch_zoom_events_
&& !detector
.InDoubleTapMode())
218 if (!pinch_event_sent_
) {
219 Send(CreateGesture(ET_GESTURE_PINCH_BEGIN
,
222 detector
.GetEventTime(),
223 detector
.GetFocusX(),
224 detector
.GetFocusY(),
225 detector
.GetFocusX() + e
.GetRawOffsetX(),
226 detector
.GetFocusY() + e
.GetRawOffsetY(),
228 GetBoundingBox(e
, ET_GESTURE_PINCH_BEGIN
),
232 if (std::abs(detector
.GetCurrentSpan() - detector
.GetPreviousSpan()) <
233 config_
.scale_gesture_detector_config
.min_pinch_update_span_delta
) {
237 float scale
= detector
.GetScaleFactor();
241 if (detector
.InDoubleTapMode()) {
242 // Relative changes in the double-tap scale factor computed by |detector|
243 // diminish as the touch moves away from the original double-tap focus.
244 // For historical reasons, Chrome has instead adopted a scale factor
245 // computation that is invariant to the focal distance, where
246 // the scale delta remains constant if the touch velocity is constant.
248 (detector
.GetCurrentSpanY() - detector
.GetPreviousSpanY()) * 0.5f
;
249 scale
= std::pow(scale
> 1 ? 1.0f
+ kDoubleTapDragZoomSpeed
250 : 1.0f
- kDoubleTapDragZoomSpeed
,
253 GestureEventDetails
pinch_details(ET_GESTURE_PINCH_UPDATE
);
254 pinch_details
.set_scale(scale
);
255 Send(CreateGesture(pinch_details
,
258 detector
.GetEventTime(),
259 detector
.GetFocusX(),
260 detector
.GetFocusY(),
261 detector
.GetFocusX() + e
.GetRawOffsetX(),
262 detector
.GetFocusY() + e
.GetRawOffsetY(),
264 GetBoundingBox(e
, pinch_details
.type()),
269 // GestureListener implementation.
270 bool OnDown(const MotionEvent
& e
) override
{
271 GestureEventDetails
tap_details(ET_GESTURE_TAP_DOWN
);
272 Send(CreateGesture(tap_details
, e
));
274 // Return true to indicate that we want to handle touch.
278 bool OnScroll(const MotionEvent
& e1
,
279 const MotionEvent
& e2
,
280 float raw_distance_x
,
281 float raw_distance_y
) override
{
282 float distance_x
= raw_distance_x
;
283 float distance_y
= raw_distance_y
;
284 if (!scroll_event_sent_
) {
285 // Remove the touch slop region from the first scroll event to avoid a
288 std::sqrt(distance_x
* distance_x
+ distance_y
* distance_y
);
289 float epsilon
= 1e-3f
;
290 if (distance
> epsilon
) {
293 distance
- config_
.gesture_detector_config
.touch_slop
) /
299 // Note that scroll start hints are in distance traveled, where
300 // scroll deltas are in the opposite direction.
301 GestureEventDetails
scroll_details(
302 ET_GESTURE_SCROLL_BEGIN
, -raw_distance_x
, -raw_distance_y
);
304 // Use the co-ordinates from the touch down, as these co-ordinates are
305 // used to determine which layer the scroll should affect.
306 Send(CreateGesture(scroll_details
,
314 e2
.GetPointerCount(),
315 GetBoundingBox(e2
, scroll_details
.type()),
317 DCHECK(scroll_event_sent_
);
320 snap_scroll_controller_
.UpdateSnapScrollMode(distance_x
, distance_y
);
321 if (snap_scroll_controller_
.IsSnappingScrolls()) {
322 if (snap_scroll_controller_
.IsSnapHorizontal())
328 if (distance_x
|| distance_y
) {
329 GestureEventDetails
scroll_details(
330 ET_GESTURE_SCROLL_UPDATE
, -distance_x
, -distance_y
);
331 const gfx::RectF bounding_box
= GetBoundingBox(e2
, scroll_details
.type());
332 const gfx::PointF center
= bounding_box
.CenterPoint();
333 const gfx::PointF raw_center
=
334 center
+ gfx::Vector2dF(e2
.GetRawOffsetX(), e2
.GetRawOffsetY());
335 Send(CreateGesture(scroll_details
,
343 e2
.GetPointerCount(),
351 bool OnFling(const MotionEvent
& e1
,
352 const MotionEvent
& e2
,
354 float velocity_y
) override
{
355 if (snap_scroll_controller_
.IsSnappingScrolls()) {
356 if (snap_scroll_controller_
.IsSnapHorizontal()) {
363 if (!velocity_x
&& !velocity_y
)
366 if (!scroll_event_sent_
) {
367 // The native side needs a ET_GESTURE_SCROLL_BEGIN before
368 // ET_SCROLL_FLING_START to send the fling to the correct target.
369 // The distance traveled in one second is a reasonable scroll start hint.
370 GestureEventDetails
scroll_details(
371 ET_GESTURE_SCROLL_BEGIN
, velocity_x
, velocity_y
);
372 Send(CreateGesture(scroll_details
, e2
));
375 GestureEventDetails
fling_details(
376 ET_SCROLL_FLING_START
, velocity_x
, velocity_y
);
377 Send(CreateGesture(fling_details
, e2
));
381 bool OnSwipe(const MotionEvent
& e1
,
382 const MotionEvent
& e2
,
384 float velocity_y
) override
{
385 GestureEventDetails
swipe_details(ET_GESTURE_SWIPE
, velocity_x
, velocity_y
);
386 Send(CreateGesture(swipe_details
, e2
));
390 bool OnTwoFingerTap(const MotionEvent
& e1
, const MotionEvent
& e2
) override
{
391 // The location of the two finger tap event should be the location of the
393 GestureEventDetails
two_finger_tap_details(
394 ET_GESTURE_TWO_FINGER_TAP
, e1
.GetTouchMajor(), e1
.GetTouchMajor());
395 Send(CreateGesture(two_finger_tap_details
,
403 e2
.GetPointerCount(),
404 GetBoundingBox(e2
, two_finger_tap_details
.type()),
409 void OnShowPress(const MotionEvent
& e
) override
{
410 GestureEventDetails
show_press_details(ET_GESTURE_SHOW_PRESS
);
411 show_press_event_sent_
= true;
412 Send(CreateGesture(show_press_details
, e
));
415 bool OnSingleTapUp(const MotionEvent
& e
) override
{
416 // This is a hack to address the issue where user hovers
417 // over a link for longer than double_tap_timeout_, then
418 // OnSingleTapConfirmed() is not triggered. But we still
419 // want to trigger the tap event at UP. So we override
420 // OnSingleTapUp() in this case. This assumes singleTapUp
421 // gets always called before singleTapConfirmed.
422 if (!ignore_single_tap_
) {
423 if (e
.GetEventTime() - current_down_time_
>
424 config_
.gesture_detector_config
.double_tap_timeout
) {
425 return OnSingleTapConfirmed(e
);
426 } else if (!IsDoubleTapEnabled() || config_
.disable_click_delay
) {
427 // If double-tap has been disabled, there is no need to wait
428 // for the double-tap timeout.
429 return OnSingleTapConfirmed(e
);
431 // Notify Blink about this tapUp event anyway, when none of the above
432 // conditions applied.
433 Send(CreateTapGesture(ET_GESTURE_TAP_UNCONFIRMED
, e
));
437 if (e
.GetAction() == MotionEvent::ACTION_UP
&&
438 !current_longpress_time_
.is_null() &&
439 !IsScaleGestureDetectionInProgress()) {
440 GestureEventDetails
long_tap_details(ET_GESTURE_LONG_TAP
);
441 Send(CreateGesture(long_tap_details
, e
));
448 // DoubleTapListener implementation.
449 bool OnSingleTapConfirmed(const MotionEvent
& e
) override
{
450 // Long taps in the edges of the screen have their events delayed by
451 // ContentViewHolder for tab swipe operations. As a consequence of the delay
452 // this method might be called after receiving the up event.
453 // These corner cases should be ignored.
454 if (ignore_single_tap_
)
457 ignore_single_tap_
= true;
459 Send(CreateTapGesture(ET_GESTURE_TAP
, e
));
463 bool OnDoubleTap(const MotionEvent
& e
) override
{
464 return scale_gesture_detector_
.OnDoubleTap(e
);
467 bool OnDoubleTapEvent(const MotionEvent
& e
) override
{
468 switch (e
.GetAction()) {
469 case MotionEvent::ACTION_DOWN
:
470 gesture_detector_
.set_longpress_enabled(false);
473 case MotionEvent::ACTION_UP
:
474 if (!IsPinchInProgress() && !IsScrollInProgress()) {
475 Send(CreateTapGesture(ET_GESTURE_DOUBLE_TAP
, e
));
486 void OnLongPress(const MotionEvent
& e
) override
{
487 DCHECK(!IsDoubleTapInProgress());
488 SetIgnoreSingleTap(true);
489 GestureEventDetails
long_press_details(ET_GESTURE_LONG_PRESS
);
490 Send(CreateGesture(long_press_details
, e
));
493 GestureEventData
CreateGesture(const GestureEventDetails
& details
,
495 MotionEvent::ToolType primary_tool_type
,
496 base::TimeTicks time
,
501 size_t touch_point_count
,
502 const gfx::RectF
& bounding_box
,
504 return GestureEventData(details
,
517 GestureEventData
CreateGesture(EventType type
,
519 MotionEvent::ToolType primary_tool_type
,
520 base::TimeTicks time
,
525 size_t touch_point_count
,
526 const gfx::RectF
& bounding_box
,
528 return GestureEventData(GestureEventDetails(type
),
541 GestureEventData
CreateGesture(const GestureEventDetails
& details
,
542 const MotionEvent
& event
) {
543 return GestureEventData(details
,
546 event
.GetEventTime(),
551 event
.GetPointerCount(),
552 GetBoundingBox(event
, details
.type()),
556 GestureEventData
CreateGesture(EventType type
, const MotionEvent
& event
) {
557 return CreateGesture(GestureEventDetails(type
), event
);
560 GestureEventData
CreateTapGesture(EventType type
, const MotionEvent
& event
) {
561 // Set the tap count to 1 even for ET_GESTURE_DOUBLE_TAP, in order to be
562 // consistent with double tap behavior on a mobile viewport. See
563 // crbug.com/234986 for context.
564 GestureEventDetails
details(type
);
565 details
.set_tap_count(1);
566 return CreateGesture(details
, event
);
569 gfx::RectF
GetBoundingBox(const MotionEvent
& event
, EventType type
) {
570 // Can't use gfx::RectF::Union, as it ignores touches with a radius of 0.
571 float left
= std::numeric_limits
<float>::max();
572 float top
= std::numeric_limits
<float>::max();
573 float right
= -std::numeric_limits
<float>::max();
574 float bottom
= -std::numeric_limits
<float>::max();
575 for (size_t i
= 0; i
< event
.GetPointerCount(); ++i
) {
576 float x
, y
, diameter
;
577 // Only for the show press and tap events, the bounding box is calculated
578 // based on the touch start point and the maximum diameter before the
579 // show press event is sent.
580 if (type
== ET_GESTURE_SHOW_PRESS
|| type
== ET_GESTURE_TAP
||
581 type
== ET_GESTURE_TAP_UNCONFIRMED
) {
583 diameter
= max_diameter_before_show_press_
;
584 x
= tap_down_point_
.x();
585 y
= tap_down_point_
.y();
587 diameter
= event
.GetTouchMajor(i
);
591 x
= x
- diameter
/ 2;
592 y
= y
- diameter
/ 2;
593 left
= std::min(left
, x
);
594 right
= std::max(right
, x
+ diameter
);
595 top
= std::min(top
, y
);
596 bottom
= std::max(bottom
, y
+ diameter
);
598 return gfx::RectF(left
, top
, right
- left
, bottom
- top
);
601 void SetDoubleTapEnabled(bool enabled
) {
602 DCHECK(!IsDoubleTapInProgress());
603 gesture_detector_
.SetDoubleTapListener(enabled
? this : NULL
);
606 void SetMultiTouchZoomEnabled(bool enabled
) {
607 // Note that returning false from |OnScaleBegin()| or |OnScale()| prevents
608 // the detector from emitting further scale updates for the current touch
609 // sequence. Thus, if multitouch events are enabled in the middle of a
610 // gesture, it will only take effect with the next gesture.
611 ignore_multitouch_zoom_events_
= !enabled
;
614 bool IsDoubleTapInProgress() const {
615 return gesture_detector_
.is_double_tapping() ||
616 (IsScaleGestureDetectionInProgress() && InDoubleTapMode());
619 bool IsScrollInProgress() const { return scroll_event_sent_
; }
621 bool IsPinchInProgress() const { return pinch_event_sent_
; }
624 bool IsScaleGestureDetectionInProgress() const {
625 return scale_gesture_detector_
.IsInProgress();
628 bool InDoubleTapMode() const {
629 return scale_gesture_detector_
.InDoubleTapMode();
632 bool IsDoubleTapEnabled() const {
633 return gesture_detector_
.has_doubletap_listener();
636 void SetIgnoreSingleTap(bool value
) { ignore_single_tap_
= value
; }
638 const GestureProvider::Config config_
;
639 GestureProviderClient
* const client_
;
641 GestureDetector gesture_detector_
;
642 ScaleGestureDetector scale_gesture_detector_
;
643 SnapScrollController snap_scroll_controller_
;
645 base::TimeTicks current_down_time_
;
647 // Keeps track of the current GESTURE_LONG_PRESS event. If a context menu is
648 // opened after a GESTURE_LONG_PRESS, this is used to insert a
649 // GESTURE_TAP_CANCEL for removing any ::active styling.
650 base::TimeTicks current_longpress_time_
;
652 // Completely silence multi-touch (pinch) scaling events. Used in WebView when
653 // zoom support is turned off.
654 bool ignore_multitouch_zoom_events_
;
656 // TODO(klobag): This is to avoid a bug in GestureDetector. With multi-touch,
657 // always_in_tap_region_ is not reset. So when the last finger is up,
658 // |OnSingleTapUp()| will be mistakenly fired.
659 bool ignore_single_tap_
;
661 // Tracks whether {PINCH|SCROLL}_BEGIN events have been forwarded for the
662 // current touch sequence.
663 bool pinch_event_sent_
;
664 bool scroll_event_sent_
;
666 // Only track the maximum diameter before the show press event has been
667 // sent and a tap must still be possible for this touch sequence.
668 float max_diameter_before_show_press_
;
670 gfx::PointF tap_down_point_
;
672 // Tracks whether an ET_GESTURE_SHOW_PRESS event has been sent for this touch
674 bool show_press_event_sent_
;
676 DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl
);
681 GestureProvider::GestureProvider(const Config
& config
,
682 GestureProviderClient
* client
)
683 : double_tap_support_for_page_(true),
684 double_tap_support_for_platform_(
685 config
.double_tap_support_for_platform_enabled
),
686 gesture_begin_end_types_enabled_(config
.gesture_begin_end_types_enabled
) {
688 DCHECK(!config
.min_gesture_bounds_length
||
689 !config
.max_gesture_bounds_length
||
690 config
.min_gesture_bounds_length
<= config
.max_gesture_bounds_length
);
691 TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors");
692 gesture_listener_
.reset(new GestureListenerImpl(config
, client
));
693 UpdateDoubleTapDetectionSupport();
696 GestureProvider::~GestureProvider() {
699 bool GestureProvider::OnTouchEvent(const MotionEvent
& event
) {
700 TRACE_EVENT1("input",
701 "GestureProvider::OnTouchEvent",
703 GetMotionEventActionName(event
.GetAction()));
705 DCHECK_NE(0u, event
.GetPointerCount());
707 if (!CanHandle(event
))
710 OnTouchEventHandlingBegin(event
);
711 gesture_listener_
->OnTouchEvent(event
);
712 OnTouchEventHandlingEnd(event
);
713 uma_histogram_
.RecordTouchEvent(event
);
717 void GestureProvider::ResetDetection() {
718 MotionEventGeneric
generic_cancel_event(MotionEvent::ACTION_CANCEL
,
719 base::TimeTicks::Now(),
720 PointerProperties());
721 OnTouchEvent(generic_cancel_event
);
724 void GestureProvider::SetMultiTouchZoomSupportEnabled(bool enabled
) {
725 gesture_listener_
->SetMultiTouchZoomEnabled(enabled
);
728 void GestureProvider::SetDoubleTapSupportForPlatformEnabled(bool enabled
) {
729 if (double_tap_support_for_platform_
== enabled
)
731 double_tap_support_for_platform_
= enabled
;
732 UpdateDoubleTapDetectionSupport();
735 void GestureProvider::SetDoubleTapSupportForPageEnabled(bool enabled
) {
736 if (double_tap_support_for_page_
== enabled
)
738 double_tap_support_for_page_
= enabled
;
739 UpdateDoubleTapDetectionSupport();
742 bool GestureProvider::IsScrollInProgress() const {
743 return gesture_listener_
->IsScrollInProgress();
746 bool GestureProvider::IsPinchInProgress() const {
747 return gesture_listener_
->IsPinchInProgress();
750 bool GestureProvider::IsDoubleTapInProgress() const {
751 return gesture_listener_
->IsDoubleTapInProgress();
754 bool GestureProvider::CanHandle(const MotionEvent
& event
) const {
755 // Aura requires one cancel event per touch point, whereas Android requires
756 // one cancel event per touch sequence. Thus we need to allow extra cancel
758 return current_down_event_
|| event
.GetAction() == MotionEvent::ACTION_DOWN
||
759 event
.GetAction() == MotionEvent::ACTION_CANCEL
;
762 void GestureProvider::OnTouchEventHandlingBegin(const MotionEvent
& event
) {
763 switch (event
.GetAction()) {
764 case MotionEvent::ACTION_DOWN
:
765 current_down_event_
= event
.Clone();
766 if (gesture_begin_end_types_enabled_
)
767 gesture_listener_
->Send(
768 gesture_listener_
->CreateGesture(ET_GESTURE_BEGIN
, event
));
770 case MotionEvent::ACTION_POINTER_DOWN
:
771 if (gesture_begin_end_types_enabled_
) {
772 const int action_index
= event
.GetActionIndex();
773 gesture_listener_
->Send(gesture_listener_
->CreateGesture(
777 event
.GetEventTime(),
778 event
.GetX(action_index
),
779 event
.GetY(action_index
),
780 event
.GetRawX(action_index
),
781 event
.GetRawY(action_index
),
782 event
.GetPointerCount(),
783 gesture_listener_
->GetBoundingBox(event
, ET_GESTURE_BEGIN
),
787 case MotionEvent::ACTION_POINTER_UP
:
788 case MotionEvent::ACTION_UP
:
789 case MotionEvent::ACTION_CANCEL
:
790 case MotionEvent::ACTION_MOVE
:
795 void GestureProvider::OnTouchEventHandlingEnd(const MotionEvent
& event
) {
796 switch (event
.GetAction()) {
797 case MotionEvent::ACTION_UP
:
798 case MotionEvent::ACTION_CANCEL
: {
799 if (gesture_begin_end_types_enabled_
)
800 gesture_listener_
->Send(
801 gesture_listener_
->CreateGesture(ET_GESTURE_END
, event
));
803 current_down_event_
.reset();
805 UpdateDoubleTapDetectionSupport();
808 case MotionEvent::ACTION_POINTER_UP
:
809 if (gesture_begin_end_types_enabled_
)
810 gesture_listener_
->Send(
811 gesture_listener_
->CreateGesture(ET_GESTURE_END
, event
));
813 case MotionEvent::ACTION_DOWN
:
814 case MotionEvent::ACTION_POINTER_DOWN
:
815 case MotionEvent::ACTION_MOVE
:
820 void GestureProvider::UpdateDoubleTapDetectionSupport() {
821 // The GestureDetector requires that any provided DoubleTapListener remain
822 // attached to it for the duration of a touch sequence. Defer any potential
823 // null'ing of the listener until the sequence has ended.
824 if (current_down_event_
)
827 const bool double_tap_enabled
=
828 double_tap_support_for_page_
&& double_tap_support_for_platform_
;
829 gesture_listener_
->SetDoubleTapEnabled(double_tap_enabled
);