Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1261)

Unified Diff: ui/events/gesture_detection/gesture_provider.cc

Issue 128613003: [Tracking Patch] Unified gesture detection (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Cleanup Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/events/gesture_detection/gesture_provider.cc
diff --git a/ui/events/gesture_detection/gesture_provider.cc b/ui/events/gesture_detection/gesture_provider.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1766d960c8c612dd6ef19f623791722b3c7ffa46
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_provider.cc
@@ -0,0 +1,807 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/events/gesture_detection/gesture_provider.h"
+
+#include "base/auto_reset.h"
+#include "base/debug/trace_event.h"
+#include "ui/events/gesture_detection/gesture_event_params.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+namespace {
+
+// Double tap drag zoom sensitive (speed).
tdresser 2014/02/19 17:44:12 sensitive -> sensitivity
jdduke (slow) 2014/02/19 18:51:58 Done.
+const float kDoubleTapDragZoomSpeed = 0.005f;
+
+GestureEventParams CreateGesture(GestureEventType type,
+ base::TimeTicks time,
+ float x,
+ float y,
+ const GestureEventParams::Data& extra_data) {
+ return GestureEventParams(type, time, x, y, extra_data);
+}
+
+GestureEventParams CreateGesture(GestureEventType type,
+ base::TimeTicks time,
+ float x,
+ float y) {
+ return CreateGesture(type, time, x, y, GestureEventParams::Data());
+}
+
+GestureEventParams CreateGesture(GestureEventType type,
+ const MotionEvent& event,
+ const GestureEventParams::Data& extra_data) {
+ return CreateGesture(type,
+ event.GetEventTime(),
+ event.GetX(),
+ event.GetY(),
+ extra_data);
+}
+
+
+GestureEventParams CreateGesture(GestureEventType type,
+ const MotionEvent& event) {
+ return CreateGesture(type, event, GestureEventParams::Data());
+}
+
+float Round(float f) {
+ return (f > 0.f) ? std::floor(f + 0.5f) : std::ceil(f - 0.5f);
+}
+
+} // namespace
+
+
+// GestureProvider:::Config
+
+GestureProvider::Config::Config() : disable_click_delay(false) {}
+
+GestureProvider::Config::~Config() {}
+
+
+// GestureProvider::ScaleGestureListener
+
+class GestureProvider::ScaleGestureListener
+ : public ScaleGestureDetector::OnScaleGestureListener {
+ public:
+ ScaleGestureListener(const ScaleGestureDetector::Config& config,
+ GestureProvider* handler)
+ : scale_gesture_detector_(config, this),
+ provider_(handler),
+ ignore_detector_events_(false),
+ pinch_event_sent_(false) {}
+
+ bool OnTouchEvent(const MotionEvent& event) {
+ // TODO: Need to deal with multi-touch transition
+ const bool in_scale_gesture = IsScaleGestureDetectionInProgress();
+ bool handled = scale_gesture_detector_.OnTouchEvent(event);
+ if (!in_scale_gesture &&
+ (event.GetAction() == MotionEvent::ACTION_UP ||
+ event.GetAction() == MotionEvent::ACTION_CANCEL)) {
+ return false;
+ }
+ return handled;
+ }
+
+ // ScaleGestureDetector::OnScaleGestureListener
+ virtual bool OnScaleBegin(const ScaleGestureDetector& detector) OVERRIDE {
+ if (ignore_detector_events_)
+ return false;
+ pinch_event_sent_ = false;
+ return true;
+ }
+
+ virtual void OnScaleEnd(const ScaleGestureDetector& detector) OVERRIDE {
+ if (!pinch_event_sent_)
+ return;
+ provider_->Send(
+ CreateGesture(GESTURE_PINCH_END, detector.GetEventTime(), 0, 0));
+ pinch_event_sent_ = false;
+ }
+
+ virtual bool OnScale(const ScaleGestureDetector& detector) OVERRIDE {
+ if (ignore_detector_events_)
+ return false;
+ if (!pinch_event_sent_) {
+ pinch_event_sent_ = true;
+ provider_->Send(CreateGesture(GESTURE_PINCH_BEGIN,
+ detector.GetEventTime(),
+ detector.GetFocusX(),
+ detector.GetFocusY()));
+ }
+ GestureEventParams::Data pinch_data;
+ pinch_data.pinch_update.scale = detector.GetScaleFactor();
+ provider_->Send(CreateGesture(GESTURE_PINCH_UPDATE,
+ detector.GetEventTime(),
+ detector.GetFocusX(),
+ detector.GetFocusY(),
+ pinch_data));
+ return true;
+ }
+
+ bool IsScaleGestureDetectionInProgress() {
+ return !ignore_detector_events_ && scale_gesture_detector_.IsInProgress();
+ }
+
+ void SetIgnoreDetectorEvents(bool value) {
+ // Note that returning false from OnScaleBegin / OnScale makes the
+ // gesture detector not to emit further scaling notifications
+ // related to this gesture. Thus, if detector events are enabled in
+ // the middle of the gesture, we don't need to do anything.
+ ignore_detector_events_ = value;
+ }
+
+ private:
+ ScaleGestureDetector scale_gesture_detector_;
+
+ GestureProvider* const provider_;
+
+ // Completely silence scaling events. Used in WebView when zoom support
+ // is turned off.
+ bool ignore_detector_events_;
+
+ // Whether any pinch zoom event has been sent to native.
+ bool pinch_event_sent_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScaleGestureListener);
+};
+
+// GestureProvider::GestureListener
+
+class GestureProvider::GestureListener
+ : public GestureDetector::OnGestureListener,
+ public GestureDetector::OnDoubleTapListener {
+ public:
+ GestureListener(
+ const GestureDetector::Config& gesture_detector_config,
+ const SnapScrollController::Config& snap_scroll_controller_config,
+ bool disable_click_delay,
+ GestureProvider* handler)
+ : gesture_detector_(gesture_detector_config, this, this),
+ snap_scroll_controller_(snap_scroll_controller_config),
+ provider_(handler),
+ px_to_dp_(1.0f / snap_scroll_controller_config.device_scale_factor),
+ disable_click_delay_(disable_click_delay),
+ scaled_touch_slop_(gesture_detector_config.scaled_touch_slop),
+ scaled_touch_slop_square_(scaled_touch_slop_ * scaled_touch_slop_),
+ double_tap_timeout_(gesture_detector_config.double_tap_timeout),
+ ignore_single_tap_(false),
+ seen_first_scroll_event_(false),
+ double_tap_mode_(DOUBLE_TAP_MODE_NONE),
+ double_tap_y_(0),
+ support_double_tap_(true),
+ double_tap_drag_zoom_anchor_x_(0),
+ double_tap_drag_zoom_anchor_y_(0),
+ last_raw_x_(0),
+ last_raw_y_(0),
+ accumulated_scroll_error_x_(0),
+ accumulated_scroll_error_y_(0) {
+ UpdateDoubleTapListener();
+ }
+
+ virtual ~GestureListener() {}
+
+ bool OnTouchEvent(const MotionEvent& e,
+ bool is_scale_gesture_detection_in_progress) {
+ snap_scroll_controller_.SetSnapScrollingMode(
+ e, is_scale_gesture_detection_in_progress);
+
+ if (is_scale_gesture_detection_in_progress) {
+ // TODO(jdduke): Determine if GestureDetector properly prevents
+ // taps if a secondary pointer is depressed.
+ SetIgnoreSingleTap(true);
+ }
+
+ if (e.GetAction() == MotionEvent::ACTION_POINTER_DOWN) {
+ EndDoubleTapDragIfNecessary(e);
+ } else if (e.GetAction() == MotionEvent::ACTION_DOWN) {
+ gesture_detector_.set_is_longpress_enabled(true);
+ }
+
+ return gesture_detector_.OnTouchEvent(e);
+ }
+
+ // GestureDetector::OnGestureListener
+ virtual bool OnDown(const MotionEvent& e) OVERRIDE {
+ current_down_time_ = e.GetEventTime();
+ ignore_single_tap_ = false;
+ seen_first_scroll_event_ = false;
+ last_raw_x_ = e.GetRawX();
+ last_raw_y_ = e.GetRawY();
+ accumulated_scroll_error_x_ = 0;
+ accumulated_scroll_error_y_ = 0;
+
+ GestureEventParams::Data tap_down_data;
+ tap_down_data.tap.width = tap_down_data.tap.height = e.GetTouchMajor();
+ provider_->Send(CreateGesture(GESTURE_TAP_DOWN, e, tap_down_data));
+
+ // Return true to indicate that we want to handle touch
+ return true;
+ }
+
+ virtual bool OnScroll(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float raw_distance_x,
+ float raw_distance_y) OVERRIDE {
+ float distance_x = raw_distance_x;
+ float distance_y = raw_distance_y;
+ if (!seen_first_scroll_event_) {
+ // Remove the touch slop region from the first scroll event to avoid a
+ // jump.
+ seen_first_scroll_event_ = true;
+ double distance =
+ std::sqrt(distance_x * distance_x + distance_y * distance_y);
+ double epsilon = 1e-3;
+ if (distance > epsilon) {
+ double ratio = std::max(0., distance - scaled_touch_slop_) / distance;
+ distance_x *= ratio;
+ distance_y *= ratio;
+ }
+ }
+ snap_scroll_controller_.UpdateSnapScrollMode(distance_x, distance_y);
+ if (snap_scroll_controller_.IsSnappingScrolls()) {
+ if (snap_scroll_controller_.IsSnapHorizontal()) {
+ distance_y = 0;
+ } else {
+ distance_x = 0;
+ }
+ }
+
+ last_raw_x_ = e2.GetRawX();
+ last_raw_y_ = e2.GetRawY();
+ if (!provider_->IsScrollInProgress()) {
+ // Note that scroll start hints are in distance traveled, where
+ // scroll deltas are in the opposite direction.
+ GestureEventParams::Data scroll_data;
+ scroll_data.scroll_begin.delta_x_hint = -raw_distance_x;
+ scroll_data.scroll_begin.delta_y_hint = -raw_distance_y;
+ provider_->Send(CreateGesture(GESTURE_SCROLL_BEGIN,
+ e2.GetEventTime(),
+ e1.GetX(),
+ e1.GetY(),
+ scroll_data));
+ }
+
+ // distance_x and distance_y is the scrolling offset since last OnScroll.
+ // Because we are passing integers to webkit, this could introduce
+ // rounding errors. The rounding errors will accumulate overtime.
+ // To solve this, we should be adding back the rounding errors each time
+ // when we calculate the new offset.
+ int dx = (int)(distance_x + accumulated_scroll_error_x_);
+ int dy = (int)(distance_y + accumulated_scroll_error_y_);
+ accumulated_scroll_error_x_ += (distance_x - dx);
+ accumulated_scroll_error_y_ += (distance_y - dy);
+
+ if (dx || dy) {
+ GestureEventParams::Data scroll_data;
+ scroll_data.scroll_update.delta_x = -dx;
+ scroll_data.scroll_update.delta_y = -dy;
+ provider_->Send(CreateGesture(GESTURE_SCROLL_UPDATE, e2, scroll_data));
+ }
+
+ return true;
+ }
+
+ virtual bool OnFling(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float velocity_x,
+ float velocity_y) OVERRIDE {
+ if (snap_scroll_controller_.IsSnappingScrolls()) {
+ if (snap_scroll_controller_.IsSnapHorizontal()) {
+ velocity_y = 0;
+ } else {
+ velocity_x = 0;
+ }
+ }
+
+ provider_->Fling(
+ e2.GetEventTime(), e1.GetX(), e1.GetY(), velocity_x, velocity_y);
+ return true;
+ }
+
+ virtual void OnShowPress(const MotionEvent& e) OVERRIDE {
+ GestureEventParams::Data show_press_data;
+ show_press_data.show_press.width = e.GetTouchMajor();
+ show_press_data.show_press.height = show_press_data.show_press.width;
+ provider_->Send(CreateGesture(GESTURE_SHOW_PRESS, e, show_press_data));
+ }
+
+ virtual bool OnSingleTapUp(const MotionEvent& e) OVERRIDE {
+ if (IsPointOutsideCurrentSlopRegion(e.GetRawX(), e.GetRawY())) {
+ provider_->SendTapCancelIfNecessary(e);
+ ignore_single_tap_ = true;
+ return true;
+ }
+ // This is a hack to address the issue where user hovers
+ // over a link for longer than double_tap_timeout_, then
+ // OnSingleTapConfirmed() is not triggered. But we still
+ // want to trigger the tap event at UP. So we override
+ // OnSingleTapUp() in this case. This assumes singleTapUp
+ // gets always called before singleTapConfirmed.
+ if (!ignore_single_tap_) {
+ if (e.GetEventTime() - current_down_time_ > double_tap_timeout_) {
+ return OnSingleTapConfirmed(e);
+ } else if (IsDoubleTapDisabled() || disable_click_delay_) {
+ // If double tap has been disabled, there is no need to wait
+ // for the double tap timeout.
+ return OnSingleTapConfirmed(e);
+ } else {
+ // Notify Blink about this tapUp event anyway,
+ // when none of the above conditions applied.
+ provider_->Send(CreateGesture(GESTURE_SINGLE_TAP_UNCONFIRMED, e));
+ }
+ }
+
+ return provider_->SendLongTapIfNecessary(e);
+ }
+
+ // GestureDetector::OnDoubleTapListener
+ virtual bool OnSingleTapConfirmed(const MotionEvent& e) OVERRIDE {
+ // Long taps in the edges of the screen have their events delayed by
+ // ContentViewHolder for tab swipe operations. As a consequence of the delay
+ // this method might be called after receiving the up event.
+ // These corner cases should be ignored.
+ if (ignore_single_tap_)
+ return true;
+
+ ignore_single_tap_ = true;
+
+ GestureEventParams::Data tap_data;
+ tap_data.tap.tap_count = 1;
+ tap_data.tap.width = tap_data.tap.height = e.GetTouchMajor();
+ provider_->Send(CreateGesture(GESTURE_SINGLE_TAP_CONFIRMED, e, tap_data));
+ return true;
+ }
+
+ virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE { return false; }
+
+ virtual bool OnDoubleTapEvent(const MotionEvent& e) OVERRIDE {
+ switch (e.GetAction()) {
+ case MotionEvent::ACTION_DOWN:
+ // Note that this will be called before the corresponding |onDown()|
+ // of the same ACTION_DOWN event. Thus, the preceding TAP_DOWN
+ // should be cancelled prior to sending a new one (in |onDown()|).
+ double_tap_drag_zoom_anchor_x_ = e.GetX();
+ double_tap_drag_zoom_anchor_y_ = e.GetY();
+ double_tap_mode_ = DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS;
+ // If a long-press fires during a double-tap, the GestureDetector
+ // will stop feeding MotionEvents to |onDoubleTapEvent()|,
+ // preventing double-tap drag zoom. Long press detection will be
+ // re-enabled on the next ACTION_DOWN.
+ gesture_detector_.set_is_longpress_enabled(false);
+ break;
+ case MotionEvent::ACTION_MOVE:
+ if (double_tap_mode_ == DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS) {
+ float distance_x = double_tap_drag_zoom_anchor_x_ - e.GetX();
+ float distance_y = double_tap_drag_zoom_anchor_y_ - e.GetY();
+
+ // Begin double tap drag zoom mode if the move distance is
+ // further than the threshold.
+ if (IsDistanceGreaterThanTouchSlop(distance_x, distance_y)) {
+ GestureEventParams::Data scroll_data;
+ scroll_data.scroll_begin.delta_x_hint = -distance_x;
+ scroll_data.scroll_begin.delta_y_hint = -distance_y;
+ provider_->Send(
+ CreateGesture(GESTURE_SCROLL_BEGIN, e, scroll_data));
+ provider_->Send(
+ CreateGesture(GESTURE_PINCH_BEGIN,
+ e.GetEventTime(),
+ Round(double_tap_drag_zoom_anchor_x_),
+ Round(double_tap_drag_zoom_anchor_y_)));
+ double_tap_mode_ = DOUBLE_TAP_MODE_DRAG_ZOOM;
+ }
+ } else if (double_tap_mode_ == DOUBLE_TAP_MODE_DRAG_ZOOM) {
+ provider_->Send(CreateGesture(GESTURE_SCROLL_UPDATE, e));
+
+ float dy = double_tap_y_ - e.GetY();
+ GestureEventParams::Data pinch_data;
+ pinch_data.pinch_update.scale =
+ std::pow(dy > 0 ? 1.0f - kDoubleTapDragZoomSpeed
+ : 1.0f + kDoubleTapDragZoomSpeed,
+ std::abs(dy * px_to_dp_));
+ provider_->Send(CreateGesture(GESTURE_PINCH_UPDATE,
+ e.GetEventTime(),
+ Round(double_tap_drag_zoom_anchor_x_),
+ Round(double_tap_drag_zoom_anchor_y_),
+ pinch_data));
+ }
+ break;
+ case MotionEvent::ACTION_UP:
+ if (double_tap_mode_ != DOUBLE_TAP_MODE_DRAG_ZOOM) {
+ // Normal double tap gesture.
+ provider_->Send(CreateGesture(GESTURE_DOUBLE_TAP, e));
+ }
+ EndDoubleTapDragIfNecessary(e);
+ break;
+ case MotionEvent::ACTION_CANCEL:
+ EndDoubleTapDragIfNecessary(e);
+ break;
+ default:
+ break;
+ }
+ double_tap_y_ = e.GetY();
+ return true;
+ }
+
+ virtual bool OnLongPress(const MotionEvent& e) OVERRIDE {
+ DCHECK(!IsDoubleTapInProgress());
+ SetIgnoreSingleTap(true);
+
+ GestureEventParams::Data long_press_data;
+ long_press_data.long_press.width = e.GetTouchMajor();
+ long_press_data.long_press.height = long_press_data.long_press.width;
+ provider_->Send(CreateGesture(GESTURE_LONG_PRESS, e, long_press_data));
+
+ // Returning true puts the GestureDetector in "longpress" mode, disabling
+ // further scrolling. This is undesirable, as it is quite common for a
+ // longpress gesture to fire on content that won't trigger a context menu.
+ return false;
+ }
+
+ void UpdateDoubleTapSupportForPlatform(bool support_double_tap) {
+ DCHECK(!IsDoubleTapInProgress());
+ DoubleTapMode double_tap_mode =
+ support_double_tap ? DOUBLE_TAP_MODE_NONE : DOUBLE_TAP_MODE_DISABLED;
+ if (double_tap_mode_ == double_tap_mode)
+ return;
+ double_tap_mode_ = double_tap_mode;
+ UpdateDoubleTapListener();
+ }
+
+ void UpdateDoubleTapSupportForPage(bool support_double_tap) {
+ if (support_double_tap_ == support_double_tap)
+ return;
+ support_double_tap_ = support_double_tap;
+ UpdateDoubleTapListener();
+ }
+
+ bool IsDoubleTapDisabled() const {
+ return double_tap_mode_ == DOUBLE_TAP_MODE_DISABLED || !support_double_tap_;
+ }
+
+ bool IsClickDelayDisabled() const { return disable_click_delay_; }
+
+ bool IsDoubleTapInProgress() const {
+ return double_tap_mode_ != DOUBLE_TAP_MODE_DISABLED &&
+ double_tap_mode_ != DOUBLE_TAP_MODE_NONE;
+ }
+
+ private:
+ enum DoubleTapMode {
+ DOUBLE_TAP_MODE_NONE,
+ DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS,
+ DOUBLE_TAP_MODE_DRAG_ZOOM,
+ DOUBLE_TAP_MODE_DISABLED
+ };
+
+ bool IsPointOutsideCurrentSlopRegion(float x, float y) const {
+ return IsDistanceGreaterThanTouchSlop(last_raw_x_ - x, last_raw_y_ - y);
+ }
+
+ bool IsDistanceGreaterThanTouchSlop(float distance_x,
+ float distance_y) const {
+ return distance_x * distance_x + distance_y * distance_y >
+ scaled_touch_slop_square_;
+ }
+
+ void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; }
+
+ void EndDoubleTapDragIfNecessary(const MotionEvent& event) {
+ if (!IsDoubleTapInProgress())
+ return;
+ if (double_tap_mode_ == DOUBLE_TAP_MODE_DRAG_ZOOM) {
+ provider_->Send(CreateGesture(GESTURE_PINCH_END, event));
+ provider_->Send(CreateGesture(GESTURE_SCROLL_END, event));
+ }
+ double_tap_mode_ = DOUBLE_TAP_MODE_NONE;
+ UpdateDoubleTapListener();
+ }
+
+ void UpdateDoubleTapListener() {
+ if (IsDoubleTapDisabled()) {
+ // Defer nulling the DoubleTapListener until the double tap gesture is
+ // complete.
+ if (IsDoubleTapInProgress())
+ return;
+ gesture_detector_.set_on_doubletap_listener(NULL);
+ } else {
+ gesture_detector_.set_on_doubletap_listener(this);
+ }
+ }
+
+ GestureDetector gesture_detector_;
+ SnapScrollController snap_scroll_controller_;
+
+ GestureProvider* const provider_;
+
+ const float px_to_dp_;
+
+ // Whether the click delay should always be disabled by sending clicks for
+ // double tap gestures.
+ const bool disable_click_delay_;
+
+ const int scaled_touch_slop_;
+
+ // Cache of square of the scaled touch slop so we don't have to calculate it
+ // on every touch.
+ const int scaled_touch_slop_square_;
+
+ const base::TimeDelta double_tap_timeout_;
+
+ base::TimeTicks current_down_time_;
+
+ // TODO(klobag): this is to avoid a bug in GestureDetector. With multi-touch,
+ // mAlwaysInTapRegion is not reset. So when the last finger is up,
+ // onSingleTapUp() will be mistakenly fired.
+ bool ignore_single_tap_;
+
+ // Used to remove the touch slop from the initial scroll event in a scroll
+ // gesture.
+ bool seen_first_scroll_event_;
+
+ // Indicate current double tap mode state.
+ int double_tap_mode_;
+
+ // On double tap this will store the y coordinates of the touch.
+ float double_tap_y_;
+
+ // The page's viewport and scale sometimes allow us to disable double tap
+ // gesture detection,
+ // according to the logic in ContentViewCore.onRenderCoordinatesUpdated().
+ bool support_double_tap_;
+
+ // x, y coordinates for an Anchor on double tap drag zoom.
+ float double_tap_drag_zoom_anchor_x_;
+ float double_tap_drag_zoom_anchor_y_;
+
+ // Used to track the last rawX/Y coordinates for moves. This gives absolute
+ // scroll distance.
+ // Useful for full screen tracking.
+ float last_raw_x_;
+ float last_raw_y_;
+
+ // Used to track the accumulated scroll error over time. This is used to
+ // remove the
+ // rounding error we introduced by passing integers to webkit.
+ float accumulated_scroll_error_x_;
+ float accumulated_scroll_error_y_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureListener);
+};
+
+// GestureProvider
+
+GestureProvider::GestureProvider(const Config& config,
+ GestureProviderClient* client)
+ : client_(client),
+ needs_show_press_event_(false),
+ needs_tap_ending_event_(false),
+ touch_scroll_in_progress_(false),
+ pinch_in_progress_(false) {
+ DCHECK(client);
+ InitGestureDetectors(config);
+}
+
+GestureProvider::~GestureProvider() {}
+
+bool GestureProvider::OnTouchEvent(const MotionEvent& event) {
+ TRACE_EVENT0("input", "GestureProvider::OnTouchEvent");
+ if (!CanHandle(event))
+ return false;
+
+ const bool was_touch_scrolling_ = touch_scroll_in_progress_;
+ const bool in_scale_gesture =
+ scale_gesture_listener_->IsScaleGestureDetectionInProgress();
+
+ if (event.GetAction() == MotionEvent::ACTION_DOWN) {
+ current_down_event_ = event.Clone();
+ touch_scroll_in_progress_ = false;
+ needs_show_press_event_ = true;
+ current_longpress_time_ = base::TimeTicks();
+ SendTapCancelIfNecessary(event);
+ }
+
+ bool handled = gesture_listener_->OnTouchEvent(event, in_scale_gesture);
+ handled |= scale_gesture_listener_->OnTouchEvent(event);
+
+ if (event.GetAction() == MotionEvent::ACTION_UP ||
+ event.GetAction() == MotionEvent::ACTION_CANCEL) {
+ // "Last finger raised" could be an end to movement, but it should
+ // only terminate scrolling if the event did not cause a fling.
+ if (was_touch_scrolling_ && !handled)
+ EndTouchScrollIfNecessary(event.GetEventTime(), true);
+
+ // We shouldn't necessarily cancel a tap on ACTION_UP, as the double-tap
+ // timeout may yet trigger a SINGLE_TAP_CONFIRMED.
+ if (event.GetAction() == MotionEvent::ACTION_CANCEL)
+ SendTapCancelIfNecessary(event);
+
+ current_down_event_.reset();
+ }
+
+ // TODO(jdduke): Determine if we should always return true or |handled|.
+ return true;
+}
+
+void GestureProvider::ResetGestureDetectors() {
+ if (!current_down_event_)
+ return;
+ scoped_ptr<MotionEvent> cancel_event = current_down_event_->Cancel();
+ // TODO(jdduke): Determine if gestures generated by this reset should be
+ // forwarded to the client.
+ gesture_listener_->OnTouchEvent(*cancel_event, false);
+ scale_gesture_listener_->OnTouchEvent(*cancel_event);
+}
+
+void GestureProvider::CancelActiveTouchSequence() {
+ if (!current_down_event_)
+ return;
+ OnTouchEvent(*current_down_event_->Cancel());
+ current_down_event_.reset();
+}
+
+void GestureProvider::UpdateMultiTouchSupport(bool support_multi_touch_zoom) {
+ scale_gesture_listener_->SetIgnoreDetectorEvents(!support_multi_touch_zoom);
+}
+
+void GestureProvider::UpdateDoubleTapSupportForPlatform(
+ bool support_double_tap) {
+ gesture_listener_->UpdateDoubleTapSupportForPlatform(support_double_tap);
+}
+
+void GestureProvider::UpdateDoubleTapSupportForPage(
+ bool support_double_tap) {
+ gesture_listener_->UpdateDoubleTapSupportForPage(support_double_tap);
+}
+
+bool GestureProvider::IsScrollInProgress() const {
+ // TODO(wangxianzhu): Also return true when fling is active once the UI knows
+ // exactly when the fling ends.
+ return touch_scroll_in_progress_;
+}
+
+bool GestureProvider::IsPinchInProgress() const { return pinch_in_progress_; }
+
+bool GestureProvider::IsDoubleTapInProgress() const {
+ return gesture_listener_->IsDoubleTapInProgress();
+}
+
+void GestureProvider::InitGestureDetectors(const Config& config) {
+ TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors");
+ gesture_listener_.reset(
+ new GestureListener(config.gesture_detector_config,
+ config.snap_scroll_controller_config,
+ config.disable_click_delay,
+ this));
+
+ scale_gesture_listener_.reset(
+ new ScaleGestureListener(config.scale_gesture_detector_config, this));
+}
+
+bool GestureProvider::CanHandle(const MotionEvent& event) const {
+ // TODO(jdduke): Determine if we need this check. Are there cases
+ // where a touch stream can be paused/terminated and resumed
+ // without getting the appropriate stream-ending event?
+ return event.GetAction() == MotionEvent::ACTION_DOWN || current_down_event_;
+}
+
+void GestureProvider::Fling(base::TimeTicks time,
+ float x,
+ float y,
+ float velocity_x,
+ float velocity_y) {
+ if (!velocity_x && !velocity_y) {
+ EndTouchScrollIfNecessary(time, true);
+ return;
+ }
+
+ if (!touch_scroll_in_progress_) {
+ // The native side needs a GESTURE_SCROLL_BEGIN before GESTURE_FLING_START
+ // to send the fling to the correct target. Send if it has not sent.
+ // The distance traveled in one second is a reasonable scroll start hint.
+ GestureEventParams::Data scroll_data;
+ scroll_data.scroll_begin.delta_x_hint = velocity_x;
+ scroll_data.scroll_begin.delta_y_hint = velocity_y;
+ Send(CreateGesture(GESTURE_SCROLL_BEGIN, time, x, y, scroll_data));
+ }
+ EndTouchScrollIfNecessary(time, false);
+
+ GestureEventParams::Data fling_data;
+ fling_data.fling_start.velocity_x = velocity_x;
+ fling_data.fling_start.velocity_y = velocity_y;
+ Send(CreateGesture(GESTURE_FLING_START, time, x, y, fling_data));
+}
+
+void GestureProvider::Send(const GestureEventParams& gesture) {
+ DCHECK(!gesture.time.is_null());
+ // The only valid events that should be sent without an active touch sequence
+ // are SHOW_PRESS and TAP_CONFIRMED, potentially triggered by the double-tap
+ // delay timing out.
+ DCHECK(current_down_event_ ||
+ gesture.type == GESTURE_SINGLE_TAP_CONFIRMED ||
+ gesture.type == GESTURE_SHOW_PRESS);
+
+ switch (gesture.type) {
+ case GESTURE_TAP_DOWN:
+ needs_tap_ending_event_ = true;
+ break;
+ case GESTURE_SINGLE_TAP_UNCONFIRMED:
+ needs_show_press_event_ = false;
+ break;
+ case GESTURE_SINGLE_TAP_CONFIRMED:
+ if (needs_show_press_event_)
+ Send(CreateGesture(
+ GESTURE_SHOW_PRESS, gesture.time, gesture.x, gesture.y));
+ needs_tap_ending_event_ = false;
+ break;
+ case GESTURE_DOUBLE_TAP:
+ needs_tap_ending_event_ = false;
+ break;
+ case GESTURE_TAP_CANCEL:
+ if (!needs_tap_ending_event_)
+ return;
+ needs_tap_ending_event_ = false;
+ break;
+ case GESTURE_SHOW_PRESS:
+ needs_show_press_event_ = false;
+ break;
+ case GESTURE_LONG_TAP:
+ needs_tap_ending_event_ = false;
+ current_longpress_time_ = base::TimeTicks();
+ break;
+ case GESTURE_LONG_PRESS:
+ DCHECK(!scale_gesture_listener_->IsScaleGestureDetectionInProgress());
+ current_longpress_time_ = gesture.time;
+ break;
+ case GESTURE_PINCH_UPDATE:
+ pinch_in_progress_ = true;
+ break;
+ case GESTURE_PINCH_END:
+ pinch_in_progress_ = false;
+ break;
+ case GESTURE_SCROLL_BEGIN:
+ touch_scroll_in_progress_ = true;
+ SendTapCancelIfNecessary(*current_down_event_);
+ break;
+ case GESTURE_SCROLL_END:
+ touch_scroll_in_progress_ = false;
+ break;
+ default:
+ break;
+ };
+
+ client_->OnGestureEvent(gesture);
+}
+
+void GestureProvider::SendTapCancelIfNecessary(const MotionEvent& event) {
+ if (!needs_tap_ending_event_)
+ return;
+ current_longpress_time_ = base::TimeTicks();
+ Send(CreateGesture(GESTURE_TAP_CANCEL, event));
+}
+
+bool GestureProvider::SendLongTapIfNecessary(const MotionEvent& event) {
+ if (event.GetAction() == MotionEvent::ACTION_UP &&
+ !current_longpress_time_.is_null() &&
+ !scale_gesture_listener_->IsScaleGestureDetectionInProgress()) {
+ SendTapCancelIfNecessary(event);
+ GestureEventParams::Data long_tap_data;
+ long_tap_data.long_press.width = event.GetTouchMajor();
+ long_tap_data.long_press.height = long_tap_data.long_press.width;
+ Send(CreateGesture(GESTURE_LONG_TAP, event, long_tap_data));
+ return true;
+ }
+ return false;
+}
+
+void GestureProvider::EndTouchScrollIfNecessary(base::TimeTicks time,
+ bool send_scroll_end_event) {
+ if (!touch_scroll_in_progress_)
+ return;
+ touch_scroll_in_progress_ = false;
+ if (send_scroll_end_event)
+ Send(CreateGesture(GESTURE_SCROLL_END, time, 0, 0));
+}
+
+} // namespace ui

Powered by Google App Engine
This is Rietveld 408576698