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