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