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