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.
4
5 #include "ui/events/gesture_detection/gesture_provider.h"
6
7 #include <cmath>
8
9 #include "base/auto_reset.h"
10 #include "base/debug/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/motion_event.h"
14
15 namespace ui {
16 namespace {
17
18 // Double-tap drag zoom sensitivity (speed).
19 const float kDoubleTapDragZoomSpeed = 0.005f;
20
GetMotionEventActionName(MotionEvent::Action action)21 const char* GetMotionEventActionName(MotionEvent::Action action) {
22 switch(action) {
23 case MotionEvent::ACTION_POINTER_DOWN: return "ACTION_POINTER_DOWN";
24 case MotionEvent::ACTION_POINTER_UP: return "ACTION_POINTER_UP";
25 case MotionEvent::ACTION_DOWN: return "ACTION_DOWN";
26 case MotionEvent::ACTION_UP: return "ACTION_UP";
27 case MotionEvent::ACTION_CANCEL: return "ACTION_CANCEL";
28 case MotionEvent::ACTION_MOVE: return "ACTION_MOVE";
29 }
30 return "";
31 }
32
GetBoundingBox(const MotionEvent & event)33 gfx::RectF GetBoundingBox(const MotionEvent& event) {
34 gfx::RectF bounds;
35 for (size_t i = 0; i < event.GetPointerCount(); ++i) {
36 float diameter = event.GetTouchMajor(i);
37 bounds.Union(gfx::RectF(event.GetX(i) - diameter / 2,
38 event.GetY(i) - diameter / 2,
39 diameter,
40 diameter));
41 }
42 return bounds;
43 }
44
CreateGesture(const GestureEventDetails & details,int motion_event_id,base::TimeTicks time,float x,float y,float raw_x,float raw_y,size_t touch_point_count,const gfx::RectF & bounding_box)45 GestureEventData CreateGesture(const GestureEventDetails& details,
46 int motion_event_id,
47 base::TimeTicks time,
48 float x,
49 float y,
50 float raw_x,
51 float raw_y,
52 size_t touch_point_count,
53 const gfx::RectF& bounding_box) {
54 return GestureEventData(details,
55 motion_event_id,
56 time,
57 x,
58 y,
59 raw_x,
60 raw_y,
61 touch_point_count,
62 bounding_box);
63 }
64
CreateGesture(EventType type,int motion_event_id,base::TimeTicks time,float x,float y,float raw_x,float raw_y,size_t touch_point_count,const gfx::RectF & bounding_box)65 GestureEventData CreateGesture(EventType type,
66 int motion_event_id,
67 base::TimeTicks time,
68 float x,
69 float y,
70 float raw_x,
71 float raw_y,
72 size_t touch_point_count,
73 const gfx::RectF& bounding_box) {
74 return GestureEventData(GestureEventDetails(type, 0, 0),
75 motion_event_id,
76 time,
77 x,
78 y,
79 raw_x,
80 raw_y,
81 touch_point_count,
82 bounding_box);
83 }
84
CreateGesture(const GestureEventDetails & details,const MotionEvent & event)85 GestureEventData CreateGesture(const GestureEventDetails& details,
86 const MotionEvent& event) {
87 return GestureEventData(details,
88 event.GetId(),
89 event.GetEventTime(),
90 event.GetX(),
91 event.GetY(),
92 event.GetRawX(),
93 event.GetRawY(),
94 event.GetPointerCount(),
95 GetBoundingBox(event));
96 }
97
CreateGesture(EventType type,const MotionEvent & event)98 GestureEventData CreateGesture(EventType type, const MotionEvent& event) {
99 return CreateGesture(GestureEventDetails(type, 0, 0), event);
100 }
101
CreateTapGestureDetails(EventType type)102 GestureEventDetails CreateTapGestureDetails(EventType type) {
103 // Set the tap count to 1 even for ET_GESTURE_DOUBLE_TAP, in order to be
104 // consistent with double tap behavior on a mobile viewport. See
105 // crbug.com/234986 for context.
106 GestureEventDetails tap_details(type, 1, 0);
107 return tap_details;
108 }
109
110 } // namespace
111
112 // GestureProvider:::Config
113
Config()114 GestureProvider::Config::Config()
115 : display(gfx::Display::kInvalidDisplayID, gfx::Rect(1, 1)),
116 disable_click_delay(false),
117 gesture_begin_end_types_enabled(false),
118 min_gesture_bounds_length(0) {}
119
~Config()120 GestureProvider::Config::~Config() {}
121
122 // GestureProvider::ScaleGestureListener
123
124 class GestureProvider::ScaleGestureListenerImpl
125 : public ScaleGestureDetector::ScaleGestureListener {
126 public:
ScaleGestureListenerImpl(const ScaleGestureDetector::Config & config,GestureProvider * provider)127 ScaleGestureListenerImpl(const ScaleGestureDetector::Config& config,
128 GestureProvider* provider)
129 : scale_gesture_detector_(config, this),
130 provider_(provider),
131 ignore_multitouch_events_(false),
132 pinch_event_sent_(false),
133 min_pinch_update_span_delta_(config.min_pinch_update_span_delta) {}
134
OnTouchEvent(const MotionEvent & event)135 bool OnTouchEvent(const MotionEvent& event) {
136 // TODO: Need to deal with multi-touch transition.
137 const bool in_scale_gesture = IsScaleGestureDetectionInProgress();
138 bool handled = scale_gesture_detector_.OnTouchEvent(event);
139 if (!in_scale_gesture &&
140 (event.GetAction() == MotionEvent::ACTION_UP ||
141 event.GetAction() == MotionEvent::ACTION_CANCEL)) {
142 return false;
143 }
144 return handled;
145 }
146
147 // ScaleGestureDetector::ScaleGestureListener implementation.
OnScaleBegin(const ScaleGestureDetector & detector,const MotionEvent & e)148 virtual bool OnScaleBegin(const ScaleGestureDetector& detector,
149 const MotionEvent& e) OVERRIDE {
150 if (ignore_multitouch_events_ && !detector.InDoubleTapMode())
151 return false;
152 pinch_event_sent_ = false;
153 return true;
154 }
155
OnScaleEnd(const ScaleGestureDetector & detector,const MotionEvent & e)156 virtual void OnScaleEnd(const ScaleGestureDetector& detector,
157 const MotionEvent& e) OVERRIDE {
158 if (!pinch_event_sent_)
159 return;
160 provider_->Send(CreateGesture(ET_GESTURE_PINCH_END, e));
161 pinch_event_sent_ = false;
162 }
163
OnScale(const ScaleGestureDetector & detector,const MotionEvent & e)164 virtual bool OnScale(const ScaleGestureDetector& detector,
165 const MotionEvent& e) OVERRIDE {
166 if (ignore_multitouch_events_ && !detector.InDoubleTapMode())
167 return false;
168 if (!pinch_event_sent_) {
169 pinch_event_sent_ = true;
170 provider_->Send(CreateGesture(ET_GESTURE_PINCH_BEGIN,
171 e.GetId(),
172 detector.GetEventTime(),
173 detector.GetFocusX(),
174 detector.GetFocusY(),
175 detector.GetFocusX() + e.GetRawOffsetX(),
176 detector.GetFocusY() + e.GetRawOffsetY(),
177 e.GetPointerCount(),
178 GetBoundingBox(e)));
179 }
180
181 if (std::abs(detector.GetCurrentSpan() - detector.GetPreviousSpan()) <
182 min_pinch_update_span_delta_) {
183 return false;
184 }
185
186 float scale = detector.GetScaleFactor();
187 if (scale == 1)
188 return true;
189
190 if (detector.InDoubleTapMode()) {
191 // Relative changes in the double-tap scale factor computed by |detector|
192 // diminish as the touch moves away from the original double-tap focus.
193 // For historical reasons, Chrome has instead adopted a scale factor
194 // computation that is invariant to the focal distance, where
195 // the scale delta remains constant if the touch velocity is constant.
196 float dy =
197 (detector.GetCurrentSpanY() - detector.GetPreviousSpanY()) * 0.5f;
198 scale = std::pow(scale > 1 ? 1.0f + kDoubleTapDragZoomSpeed
199 : 1.0f - kDoubleTapDragZoomSpeed,
200 std::abs(dy));
201 }
202 GestureEventDetails pinch_details(ET_GESTURE_PINCH_UPDATE, scale, 0);
203 provider_->Send(CreateGesture(pinch_details,
204 e.GetId(),
205 detector.GetEventTime(),
206 detector.GetFocusX(),
207 detector.GetFocusY(),
208 detector.GetFocusX() + e.GetRawOffsetX(),
209 detector.GetFocusY() + e.GetRawOffsetY(),
210 e.GetPointerCount(),
211 GetBoundingBox(e)));
212 return true;
213 }
214
SetDoubleTapEnabled(bool enabled)215 void SetDoubleTapEnabled(bool enabled) {
216 DCHECK(!IsDoubleTapInProgress());
217 scale_gesture_detector_.SetQuickScaleEnabled(enabled);
218 }
219
SetMultiTouchEnabled(bool enabled)220 void SetMultiTouchEnabled(bool enabled) {
221 // Note that returning false from OnScaleBegin / OnScale makes the
222 // gesture detector not to emit further scaling notifications
223 // related to this gesture. Thus, if detector events are enabled in
224 // the middle of the gesture, we don't need to do anything.
225 ignore_multitouch_events_ = !enabled;
226 }
227
IsDoubleTapInProgress() const228 bool IsDoubleTapInProgress() const {
229 return IsScaleGestureDetectionInProgress() && InDoubleTapMode();
230 }
231
IsScaleGestureDetectionInProgress() const232 bool IsScaleGestureDetectionInProgress() const {
233 return scale_gesture_detector_.IsInProgress();
234 }
235
236 private:
InDoubleTapMode() const237 bool InDoubleTapMode() const {
238 return scale_gesture_detector_.InDoubleTapMode();
239 }
240
241 ScaleGestureDetector scale_gesture_detector_;
242
243 GestureProvider* const provider_;
244
245 // Completely silence multi-touch (pinch) scaling events. Used in WebView when
246 // zoom support is turned off.
247 bool ignore_multitouch_events_;
248
249 // Whether any pinch zoom event has been sent to native.
250 bool pinch_event_sent_;
251
252 // The minimum change in span required before this is considered a pinch. See
253 // crbug.com/373318.
254 float min_pinch_update_span_delta_;
255
256 DISALLOW_COPY_AND_ASSIGN(ScaleGestureListenerImpl);
257 };
258
259 // GestureProvider::GestureListener
260
261 class GestureProvider::GestureListenerImpl
262 : public GestureDetector::GestureListener,
263 public GestureDetector::DoubleTapListener {
264 public:
GestureListenerImpl(const gfx::Display & display,const GestureDetector::Config & gesture_detector_config,bool disable_click_delay,GestureProvider * provider)265 GestureListenerImpl(
266 const gfx::Display& display,
267 const GestureDetector::Config& gesture_detector_config,
268 bool disable_click_delay,
269 GestureProvider* provider)
270 : gesture_detector_(gesture_detector_config, this, this),
271 snap_scroll_controller_(display),
272 provider_(provider),
273 disable_click_delay_(disable_click_delay),
274 touch_slop_(gesture_detector_config.touch_slop),
275 double_tap_timeout_(gesture_detector_config.double_tap_timeout),
276 ignore_single_tap_(false),
277 seen_first_scroll_event_(false) {}
278
~GestureListenerImpl()279 virtual ~GestureListenerImpl() {}
280
OnTouchEvent(const MotionEvent & e,bool is_scale_gesture_detection_in_progress)281 bool OnTouchEvent(const MotionEvent& e,
282 bool is_scale_gesture_detection_in_progress) {
283 snap_scroll_controller_.SetSnapScrollingMode(
284 e, is_scale_gesture_detection_in_progress);
285
286 if (is_scale_gesture_detection_in_progress)
287 SetIgnoreSingleTap(true);
288
289 if (e.GetAction() == MotionEvent::ACTION_DOWN)
290 gesture_detector_.set_longpress_enabled(true);
291
292 return gesture_detector_.OnTouchEvent(e);
293 }
294
295 // GestureDetector::GestureListener implementation.
OnDown(const MotionEvent & e)296 virtual bool OnDown(const MotionEvent& e) OVERRIDE {
297 current_down_time_ = e.GetEventTime();
298 ignore_single_tap_ = false;
299 seen_first_scroll_event_ = false;
300
301 GestureEventDetails tap_details(ET_GESTURE_TAP_DOWN, 0, 0);
302 provider_->Send(CreateGesture(tap_details, e));
303
304 // Return true to indicate that we want to handle touch.
305 return true;
306 }
307
OnScroll(const MotionEvent & e1,const MotionEvent & e2,float raw_distance_x,float raw_distance_y)308 virtual bool OnScroll(const MotionEvent& e1,
309 const MotionEvent& e2,
310 float raw_distance_x,
311 float raw_distance_y) OVERRIDE {
312 float distance_x = raw_distance_x;
313 float distance_y = raw_distance_y;
314 if (!seen_first_scroll_event_) {
315 // Remove the touch slop region from the first scroll event to avoid a
316 // jump.
317 seen_first_scroll_event_ = true;
318 double distance =
319 std::sqrt(distance_x * distance_x + distance_y * distance_y);
320 double epsilon = 1e-3;
321 if (distance > epsilon) {
322 double ratio = std::max(0., distance - touch_slop_) / distance;
323 distance_x *= ratio;
324 distance_y *= ratio;
325 }
326 }
327 snap_scroll_controller_.UpdateSnapScrollMode(distance_x, distance_y);
328 if (snap_scroll_controller_.IsSnappingScrolls()) {
329 if (snap_scroll_controller_.IsSnapHorizontal()) {
330 distance_y = 0;
331 } else {
332 distance_x = 0;
333 }
334 }
335
336 if (!provider_->IsScrollInProgress()) {
337 // Note that scroll start hints are in distance traveled, where
338 // scroll deltas are in the opposite direction.
339 GestureEventDetails scroll_details(
340 ET_GESTURE_SCROLL_BEGIN, -raw_distance_x, -raw_distance_y);
341
342 // Use the co-ordinates from the touch down, as these co-ordinates are
343 // used to determine which layer the scroll should affect.
344 provider_->Send(CreateGesture(scroll_details,
345 e2.GetId(),
346 e2.GetEventTime(),
347 e1.GetX(),
348 e1.GetY(),
349 e1.GetRawX(),
350 e1.GetRawY(),
351 e2.GetPointerCount(),
352 GetBoundingBox(e2)));
353 }
354
355 if (distance_x || distance_y) {
356 const gfx::RectF bounding_box = GetBoundingBox(e2);
357 const gfx::PointF center = bounding_box.CenterPoint();
358 const gfx::PointF raw_center =
359 center + gfx::Vector2dF(e2.GetRawOffsetX(), e2.GetRawOffsetY());
360 GestureEventDetails scroll_details(
361 ET_GESTURE_SCROLL_UPDATE, -distance_x, -distance_y);
362 provider_->Send(CreateGesture(scroll_details,
363 e2.GetId(),
364 e2.GetEventTime(),
365 center.x(),
366 center.y(),
367 raw_center.x(),
368 raw_center.y(),
369 e2.GetPointerCount(),
370 bounding_box));
371 }
372
373 return true;
374 }
375
OnFling(const MotionEvent & e1,const MotionEvent & e2,float velocity_x,float velocity_y)376 virtual bool OnFling(const MotionEvent& e1,
377 const MotionEvent& e2,
378 float velocity_x,
379 float velocity_y) OVERRIDE {
380 if (snap_scroll_controller_.IsSnappingScrolls()) {
381 if (snap_scroll_controller_.IsSnapHorizontal()) {
382 velocity_y = 0;
383 } else {
384 velocity_x = 0;
385 }
386 }
387
388 provider_->Fling(e2, velocity_x, velocity_y);
389 return true;
390 }
391
OnSwipe(const MotionEvent & e1,const MotionEvent & e2,float velocity_x,float velocity_y)392 virtual bool OnSwipe(const MotionEvent& e1,
393 const MotionEvent& e2,
394 float velocity_x,
395 float velocity_y) OVERRIDE {
396 GestureEventDetails swipe_details(ET_GESTURE_SWIPE, velocity_x, velocity_y);
397 provider_->Send(CreateGesture(swipe_details, e2));
398 return true;
399 }
400
OnTwoFingerTap(const MotionEvent & e1,const MotionEvent & e2)401 virtual bool OnTwoFingerTap(const MotionEvent& e1,
402 const MotionEvent& e2) OVERRIDE {
403 // The location of the two finger tap event should be the location of the
404 // primary pointer.
405 GestureEventDetails two_finger_tap_details(ET_GESTURE_TWO_FINGER_TAP,
406 e1.GetTouchMajor(),
407 e1.GetTouchMajor());
408 provider_->Send(CreateGesture(two_finger_tap_details,
409 e2.GetId(),
410 e2.GetEventTime(),
411 e1.GetX(),
412 e1.GetY(),
413 e1.GetRawX(),
414 e1.GetRawY(),
415 e2.GetPointerCount(),
416 GetBoundingBox(e2)));
417 return true;
418 }
419
OnShowPress(const MotionEvent & e)420 virtual void OnShowPress(const MotionEvent& e) OVERRIDE {
421 GestureEventDetails show_press_details(ET_GESTURE_SHOW_PRESS, 0, 0);
422 provider_->Send(CreateGesture(show_press_details, e));
423 }
424
OnSingleTapUp(const MotionEvent & e)425 virtual bool OnSingleTapUp(const MotionEvent& e) OVERRIDE {
426 // This is a hack to address the issue where user hovers
427 // over a link for longer than double_tap_timeout_, then
428 // OnSingleTapConfirmed() is not triggered. But we still
429 // want to trigger the tap event at UP. So we override
430 // OnSingleTapUp() in this case. This assumes singleTapUp
431 // gets always called before singleTapConfirmed.
432 if (!ignore_single_tap_) {
433 if (e.GetEventTime() - current_down_time_ > double_tap_timeout_) {
434 return OnSingleTapConfirmed(e);
435 } else if (!IsDoubleTapEnabled() || disable_click_delay_) {
436 // If double-tap has been disabled, there is no need to wait
437 // for the double-tap timeout.
438 return OnSingleTapConfirmed(e);
439 } else {
440 // Notify Blink about this tapUp event anyway, when none of the above
441 // conditions applied.
442 provider_->Send(CreateGesture(
443 CreateTapGestureDetails(ET_GESTURE_TAP_UNCONFIRMED), e));
444 }
445 }
446
447 return provider_->SendLongTapIfNecessary(e);
448 }
449
450 // GestureDetector::DoubleTapListener implementation.
OnSingleTapConfirmed(const MotionEvent & e)451 virtual bool OnSingleTapConfirmed(const MotionEvent& e) OVERRIDE {
452 // Long taps in the edges of the screen have their events delayed by
453 // ContentViewHolder for tab swipe operations. As a consequence of the delay
454 // this method might be called after receiving the up event.
455 // These corner cases should be ignored.
456 if (ignore_single_tap_)
457 return true;
458
459 ignore_single_tap_ = true;
460
461 provider_->Send(CreateGesture(CreateTapGestureDetails(ET_GESTURE_TAP), e));
462 return true;
463 }
464
OnDoubleTap(const MotionEvent & e)465 virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE { return false; }
466
OnDoubleTapEvent(const MotionEvent & e)467 virtual bool OnDoubleTapEvent(const MotionEvent& e) OVERRIDE {
468 switch (e.GetAction()) {
469 case MotionEvent::ACTION_DOWN:
470 gesture_detector_.set_longpress_enabled(false);
471 break;
472
473 case MotionEvent::ACTION_UP:
474 if (!provider_->IsPinchInProgress() &&
475 !provider_->IsScrollInProgress()) {
476 provider_->Send(
477 CreateGesture(CreateTapGestureDetails(ET_GESTURE_DOUBLE_TAP), e));
478 return true;
479 }
480 break;
481 default:
482 break;
483 }
484 return false;
485 }
486
OnLongPress(const MotionEvent & e)487 virtual void OnLongPress(const MotionEvent& e) OVERRIDE {
488 DCHECK(!IsDoubleTapInProgress());
489 SetIgnoreSingleTap(true);
490
491 GestureEventDetails long_press_details(ET_GESTURE_LONG_PRESS, 0, 0);
492 provider_->Send(CreateGesture(long_press_details, e));
493 }
494
SetDoubleTapEnabled(bool enabled)495 void SetDoubleTapEnabled(bool enabled) {
496 DCHECK(!IsDoubleTapInProgress());
497 gesture_detector_.SetDoubleTapListener(enabled ? this : NULL);
498 }
499
IsDoubleTapInProgress() const500 bool IsDoubleTapInProgress() const {
501 return gesture_detector_.is_double_tapping();
502 }
503
504 private:
SetIgnoreSingleTap(bool value)505 void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; }
506
IsDoubleTapEnabled() const507 bool IsDoubleTapEnabled() const {
508 return gesture_detector_.has_doubletap_listener();
509 }
510
511 GestureDetector gesture_detector_;
512 SnapScrollController snap_scroll_controller_;
513
514 GestureProvider* const provider_;
515
516 // Whether the click delay should always be disabled by sending clicks for
517 // double-tap gestures.
518 const bool disable_click_delay_;
519
520 const float touch_slop_;
521
522 const base::TimeDelta double_tap_timeout_;
523
524 base::TimeTicks current_down_time_;
525
526 // TODO(klobag): This is to avoid a bug in GestureDetector. With multi-touch,
527 // always_in_tap_region_ is not reset. So when the last finger is up,
528 // OnSingleTapUp() will be mistakenly fired.
529 bool ignore_single_tap_;
530
531 // Used to remove the touch slop from the initial scroll event in a scroll
532 // gesture.
533 bool seen_first_scroll_event_;
534
535 DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl);
536 };
537
538 // GestureProvider
539
GestureProvider(const Config & config,GestureProviderClient * client)540 GestureProvider::GestureProvider(const Config& config,
541 GestureProviderClient* client)
542 : client_(client),
543 touch_scroll_in_progress_(false),
544 pinch_in_progress_(false),
545 double_tap_support_for_page_(true),
546 double_tap_support_for_platform_(true),
547 gesture_begin_end_types_enabled_(config.gesture_begin_end_types_enabled),
548 min_gesture_bounds_length_(config.min_gesture_bounds_length) {
549 DCHECK(client);
550 InitGestureDetectors(config);
551 }
552
~GestureProvider()553 GestureProvider::~GestureProvider() {}
554
OnTouchEvent(const MotionEvent & event)555 bool GestureProvider::OnTouchEvent(const MotionEvent& event) {
556 TRACE_EVENT1("input", "GestureProvider::OnTouchEvent",
557 "action", GetMotionEventActionName(event.GetAction()));
558
559 DCHECK_NE(0u, event.GetPointerCount());
560
561 if (!CanHandle(event))
562 return false;
563
564 const bool in_scale_gesture =
565 scale_gesture_listener_->IsScaleGestureDetectionInProgress();
566
567 OnTouchEventHandlingBegin(event);
568 gesture_listener_->OnTouchEvent(event, in_scale_gesture);
569 scale_gesture_listener_->OnTouchEvent(event);
570 OnTouchEventHandlingEnd(event);
571 return true;
572 }
573
SetMultiTouchZoomSupportEnabled(bool enabled)574 void GestureProvider::SetMultiTouchZoomSupportEnabled(bool enabled) {
575 scale_gesture_listener_->SetMultiTouchEnabled(enabled);
576 }
577
SetDoubleTapSupportForPlatformEnabled(bool enabled)578 void GestureProvider::SetDoubleTapSupportForPlatformEnabled(bool enabled) {
579 if (double_tap_support_for_platform_ == enabled)
580 return;
581 double_tap_support_for_platform_ = enabled;
582 UpdateDoubleTapDetectionSupport();
583 }
584
SetDoubleTapSupportForPageEnabled(bool enabled)585 void GestureProvider::SetDoubleTapSupportForPageEnabled(bool enabled) {
586 if (double_tap_support_for_page_ == enabled)
587 return;
588 double_tap_support_for_page_ = enabled;
589 UpdateDoubleTapDetectionSupport();
590 }
591
IsScrollInProgress() const592 bool GestureProvider::IsScrollInProgress() const {
593 // TODO(wangxianzhu): Also return true when fling is active once the UI knows
594 // exactly when the fling ends.
595 return touch_scroll_in_progress_;
596 }
597
IsPinchInProgress() const598 bool GestureProvider::IsPinchInProgress() const { return pinch_in_progress_; }
599
IsDoubleTapInProgress() const600 bool GestureProvider::IsDoubleTapInProgress() const {
601 return gesture_listener_->IsDoubleTapInProgress() ||
602 scale_gesture_listener_->IsDoubleTapInProgress();
603 }
604
InitGestureDetectors(const Config & config)605 void GestureProvider::InitGestureDetectors(const Config& config) {
606 TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors");
607 gesture_listener_.reset(
608 new GestureListenerImpl(config.display,
609 config.gesture_detector_config,
610 config.disable_click_delay,
611 this));
612
613 scale_gesture_listener_.reset(
614 new ScaleGestureListenerImpl(config.scale_gesture_detector_config, this));
615
616 UpdateDoubleTapDetectionSupport();
617 }
618
CanHandle(const MotionEvent & event) const619 bool GestureProvider::CanHandle(const MotionEvent& event) const {
620 return event.GetAction() == MotionEvent::ACTION_DOWN || current_down_event_;
621 }
622
Fling(const MotionEvent & event,float velocity_x,float velocity_y)623 void GestureProvider::Fling(const MotionEvent& event,
624 float velocity_x,
625 float velocity_y) {
626 if (!velocity_x && !velocity_y) {
627 EndTouchScrollIfNecessary(event, true);
628 return;
629 }
630
631 if (!touch_scroll_in_progress_) {
632 // The native side needs a ET_GESTURE_SCROLL_BEGIN before
633 // ET_SCROLL_FLING_START to send the fling to the correct target. Send if it
634 // has not sent. The distance traveled in one second is a reasonable scroll
635 // start hint.
636 GestureEventDetails scroll_details(
637 ET_GESTURE_SCROLL_BEGIN, velocity_x, velocity_y);
638 Send(CreateGesture(scroll_details, event));
639 }
640 EndTouchScrollIfNecessary(event, false);
641
642 GestureEventDetails fling_details(
643 ET_SCROLL_FLING_START, velocity_x, velocity_y);
644 Send(CreateGesture(fling_details, event));
645 }
646
Send(GestureEventData gesture)647 void GestureProvider::Send(GestureEventData gesture) {
648 DCHECK(!gesture.time.is_null());
649 // The only valid events that should be sent without an active touch sequence
650 // are SHOW_PRESS and TAP, potentially triggered by the double-tap
651 // delay timing out.
652 DCHECK(current_down_event_ || gesture.type() == ET_GESTURE_TAP ||
653 gesture.type() == ET_GESTURE_SHOW_PRESS);
654
655 // TODO(jdduke): Provide a way of skipping this clamping for stylus and/or
656 // mouse-based input, perhaps by exposing the source type on MotionEvent.
657 const gfx::RectF& gesture_bounds = gesture.details.bounding_box_f();
658 gesture.details.set_bounding_box(gfx::RectF(
659 gesture_bounds.x(),
660 gesture_bounds.y(),
661 std::max(min_gesture_bounds_length_, gesture_bounds.width()),
662 std::max(min_gesture_bounds_length_, gesture_bounds.height())));
663
664 switch (gesture.type()) {
665 case ET_GESTURE_LONG_PRESS:
666 DCHECK(!scale_gesture_listener_->IsScaleGestureDetectionInProgress());
667 current_longpress_time_ = gesture.time;
668 break;
669 case ET_GESTURE_LONG_TAP:
670 current_longpress_time_ = base::TimeTicks();
671 break;
672 case ET_GESTURE_SCROLL_BEGIN:
673 DCHECK(!touch_scroll_in_progress_);
674 touch_scroll_in_progress_ = true;
675 break;
676 case ET_GESTURE_SCROLL_END:
677 DCHECK(touch_scroll_in_progress_);
678 if (pinch_in_progress_)
679 Send(GestureEventData(ET_GESTURE_PINCH_END, gesture));
680 touch_scroll_in_progress_ = false;
681 break;
682 case ET_GESTURE_PINCH_BEGIN:
683 DCHECK(!pinch_in_progress_);
684 if (!touch_scroll_in_progress_)
685 Send(GestureEventData(ET_GESTURE_SCROLL_BEGIN, gesture));
686 pinch_in_progress_ = true;
687 break;
688 case ET_GESTURE_PINCH_END:
689 DCHECK(pinch_in_progress_);
690 pinch_in_progress_ = false;
691 break;
692 case ET_GESTURE_SHOW_PRESS:
693 // It's possible that a double-tap drag zoom (from ScaleGestureDetector)
694 // will start before the press gesture fires (from GestureDetector), in
695 // which case the press should simply be dropped.
696 if (pinch_in_progress_ || touch_scroll_in_progress_)
697 return;
698 default:
699 break;
700 };
701
702 client_->OnGestureEvent(gesture);
703 }
704
SendLongTapIfNecessary(const MotionEvent & event)705 bool GestureProvider::SendLongTapIfNecessary(const MotionEvent& event) {
706 if (event.GetAction() == MotionEvent::ACTION_UP &&
707 !current_longpress_time_.is_null() &&
708 !scale_gesture_listener_->IsScaleGestureDetectionInProgress()) {
709 GestureEventDetails long_tap_details(ET_GESTURE_LONG_TAP, 0, 0);
710 Send(CreateGesture(long_tap_details, event));
711 return true;
712 }
713 return false;
714 }
715
EndTouchScrollIfNecessary(const MotionEvent & event,bool send_scroll_end_event)716 void GestureProvider::EndTouchScrollIfNecessary(const MotionEvent& event,
717 bool send_scroll_end_event) {
718 if (!touch_scroll_in_progress_)
719 return;
720 if (send_scroll_end_event)
721 Send(CreateGesture(ET_GESTURE_SCROLL_END, event));
722 touch_scroll_in_progress_ = false;
723 }
724
OnTouchEventHandlingBegin(const MotionEvent & event)725 void GestureProvider::OnTouchEventHandlingBegin(const MotionEvent& event) {
726 switch (event.GetAction()) {
727 case MotionEvent::ACTION_DOWN:
728 current_down_event_ = event.Clone();
729 touch_scroll_in_progress_ = false;
730 pinch_in_progress_ = false;
731 current_longpress_time_ = base::TimeTicks();
732 if (gesture_begin_end_types_enabled_)
733 Send(CreateGesture(ET_GESTURE_BEGIN, event));
734 break;
735 case MotionEvent::ACTION_POINTER_DOWN:
736 if (gesture_begin_end_types_enabled_) {
737 const int action_index = event.GetActionIndex();
738 Send(CreateGesture(ET_GESTURE_BEGIN,
739 event.GetId(),
740 event.GetEventTime(),
741 event.GetX(action_index),
742 event.GetY(action_index),
743 event.GetRawX(action_index),
744 event.GetRawY(action_index),
745 event.GetPointerCount(),
746 GetBoundingBox(event)));
747 }
748 break;
749 case MotionEvent::ACTION_POINTER_UP:
750 case MotionEvent::ACTION_UP:
751 case MotionEvent::ACTION_CANCEL:
752 case MotionEvent::ACTION_MOVE:
753 break;
754 }
755 }
756
OnTouchEventHandlingEnd(const MotionEvent & event)757 void GestureProvider::OnTouchEventHandlingEnd(const MotionEvent& event) {
758 switch (event.GetAction()) {
759 case MotionEvent::ACTION_UP:
760 case MotionEvent::ACTION_CANCEL: {
761 // Note: This call will have no effect if a fling was just generated, as
762 // |Fling()| will have already signalled an end to touch-scrolling.
763 EndTouchScrollIfNecessary(event, true);
764
765 const gfx::RectF bounding_box = GetBoundingBox(event);
766
767 if (gesture_begin_end_types_enabled_) {
768 for (size_t i = 0; i < event.GetPointerCount(); ++i) {
769 Send(CreateGesture(ET_GESTURE_END,
770 event.GetId(),
771 event.GetEventTime(),
772 event.GetX(i),
773 event.GetY(i),
774 event.GetRawX(i),
775 event.GetRawY(i),
776 event.GetPointerCount() - i,
777 bounding_box));
778 }
779 }
780
781 current_down_event_.reset();
782
783 UpdateDoubleTapDetectionSupport();
784 break;
785 }
786 case MotionEvent::ACTION_POINTER_UP:
787 if (gesture_begin_end_types_enabled_)
788 Send(CreateGesture(ET_GESTURE_END, event));
789 break;
790 case MotionEvent::ACTION_DOWN:
791 case MotionEvent::ACTION_POINTER_DOWN:
792 case MotionEvent::ACTION_MOVE:
793 break;
794 }
795 }
796
UpdateDoubleTapDetectionSupport()797 void GestureProvider::UpdateDoubleTapDetectionSupport() {
798 // The GestureDetector requires that any provided DoubleTapListener remain
799 // attached to it for the duration of a touch sequence. Defer any potential
800 // null'ing of the listener until the sequence has ended.
801 if (current_down_event_)
802 return;
803
804 const bool double_tap_enabled = double_tap_support_for_page_ &&
805 double_tap_support_for_platform_;
806 gesture_listener_->SetDoubleTapEnabled(double_tap_enabled);
807 scale_gesture_listener_->SetDoubleTapEnabled(double_tap_enabled);
808 }
809
810 } // namespace ui
811