1// Copyright (c) 2012 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/base/gestures/gesture_sequence.h"
6
7#include <cmath>
8#include <stdlib.h>
9
10#include "base/command_line.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/time/time.h"
15#include "ui/base/events/event.h"
16#include "ui/base/events/event_constants.h"
17#include "ui/base/gestures/gesture_configuration.h"
18#include "ui/base/gestures/gesture_util.h"
19#include "ui/base/ui_base_switches.h"
20#include "ui/gfx/rect.h"
21
22namespace ui {
23
24namespace {
25
26// ui::EventType is mapped to TouchState so it can fit into 3 bits of
27// Signature.
28enum TouchState {
29  TS_RELEASED,
30  TS_PRESSED,
31  TS_MOVED,
32  TS_STATIONARY,
33  TS_CANCELLED,
34  TS_UNKNOWN,
35};
36
37// ui::EventResult is mapped to TouchStatusInternal to simply indicate whether a
38// processed touch-event should affect gesture-recognition or not.
39enum TouchStatusInternal {
40  TSI_NOT_PROCESSED,  // The touch-event should take-part into
41                      // gesture-recognition only if the touch-event has not
42                      // been processed.
43
44  TSI_PROCESSED,      // The touch-event should affect gesture-recognition only
45                      // if the touch-event has been processed. For example,,
46                      // this means that a JavaScript touch handler called
47                      // |preventDefault| on the associated touch event
48                      // or was processed by an aura-window or views-view.
49
50  TSI_ALWAYS          // The touch-event should always affect gesture
51                      // recognition.
52};
53
54// Get equivalent TouchState from EventType |type|.
55TouchState TouchEventTypeToTouchState(ui::EventType type) {
56  switch (type) {
57    case ui::ET_TOUCH_RELEASED:
58      return TS_RELEASED;
59    case ui::ET_TOUCH_PRESSED:
60      return TS_PRESSED;
61    case ui::ET_TOUCH_MOVED:
62      return TS_MOVED;
63    case ui::ET_TOUCH_STATIONARY:
64      return TS_STATIONARY;
65    case ui::ET_TOUCH_CANCELLED:
66      return TS_CANCELLED;
67    default:
68      DVLOG(1) << "Unknown Touch Event type";
69  }
70  return TS_UNKNOWN;
71}
72
73// Gesture signature types for different values of combination (GestureState,
74// touch_id, ui::EventType, touch_handled), see Signature for more info.
75//
76// Note: New addition of types should be placed as per their Signature value.
77#define G(gesture_state, id, touch_state, handled) 1 + ( \
78  (((touch_state) & 0x7) << 1) |                         \
79  ((handled & 0x3) << 4) |                               \
80  (((id) & 0xfff) << 6) |                                \
81  ((gesture_state) << 18))
82
83enum EdgeStateSignatureType {
84  GST_INVALID = -1,
85
86  GST_NO_GESTURE_FIRST_PRESSED =
87      G(GS_NO_GESTURE, 0, TS_PRESSED, TSI_NOT_PROCESSED),
88
89  GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED =
90      G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_RELEASED, TSI_NOT_PROCESSED),
91
92  GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED =
93      G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_RELEASED, TSI_PROCESSED),
94
95  // Ignore processed touch-move events until gesture-scroll starts.
96  GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED =
97      G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_MOVED, TSI_NOT_PROCESSED),
98
99  GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED =
100      G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_MOVED, TSI_PROCESSED),
101
102  GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY =
103      G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_STATIONARY, TSI_NOT_PROCESSED),
104
105  GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED =
106      G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_CANCELLED, TSI_ALWAYS),
107
108  GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED =
109      G(GS_PENDING_SYNTHETIC_CLICK, 1, TS_PRESSED, TSI_NOT_PROCESSED),
110
111  GST_SCROLL_FIRST_RELEASED =
112      G(GS_SCROLL, 0, TS_RELEASED, TSI_ALWAYS),
113
114  // Once scroll has started, process all touch-move events.
115  GST_SCROLL_FIRST_MOVED =
116      G(GS_SCROLL, 0, TS_MOVED, TSI_ALWAYS),
117
118  GST_SCROLL_FIRST_CANCELLED =
119      G(GS_SCROLL, 0, TS_CANCELLED, TSI_ALWAYS),
120
121  GST_SCROLL_SECOND_PRESSED =
122      G(GS_SCROLL, 1, TS_PRESSED, TSI_NOT_PROCESSED),
123
124  GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED =
125      G(GS_PENDING_TWO_FINGER_TAP, 0, TS_RELEASED, TSI_NOT_PROCESSED),
126
127  GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED_HANDLED =
128      G(GS_PENDING_TWO_FINGER_TAP, 0, TS_RELEASED, TSI_PROCESSED),
129
130  GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED =
131      G(GS_PENDING_TWO_FINGER_TAP, 1, TS_RELEASED, TSI_NOT_PROCESSED),
132
133  GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED =
134      G(GS_PENDING_TWO_FINGER_TAP, 1, TS_RELEASED, TSI_PROCESSED),
135
136  GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED =
137      G(GS_PENDING_TWO_FINGER_TAP, 0, TS_MOVED, TSI_ALWAYS),
138
139  GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED =
140      G(GS_PENDING_TWO_FINGER_TAP, 1, TS_MOVED, TSI_ALWAYS),
141
142  GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED =
143      G(GS_PENDING_TWO_FINGER_TAP, 0, TS_CANCELLED, TSI_ALWAYS),
144
145  GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED =
146      G(GS_PENDING_TWO_FINGER_TAP, 1, TS_CANCELLED, TSI_ALWAYS),
147
148  GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED =
149      G(GS_PENDING_TWO_FINGER_TAP, 2, TS_PRESSED, TSI_NOT_PROCESSED),
150
151  GST_PINCH_FIRST_MOVED =
152      G(GS_PINCH, 0, TS_MOVED, TSI_NOT_PROCESSED),
153
154  GST_PINCH_FIRST_MOVED_HANDLED =
155      G(GS_PINCH, 0, TS_MOVED, TSI_PROCESSED),
156
157  GST_PINCH_SECOND_MOVED =
158      G(GS_PINCH, 1, TS_MOVED, TSI_NOT_PROCESSED),
159
160  GST_PINCH_SECOND_MOVED_HANDLED =
161      G(GS_PINCH, 1, TS_MOVED, TSI_PROCESSED),
162
163  GST_PINCH_FIRST_RELEASED =
164      G(GS_PINCH, 0, TS_RELEASED, TSI_ALWAYS),
165
166  GST_PINCH_SECOND_RELEASED =
167      G(GS_PINCH, 1, TS_RELEASED, TSI_ALWAYS),
168
169  GST_PINCH_FIRST_CANCELLED =
170      G(GS_PINCH, 0, TS_CANCELLED, TSI_ALWAYS),
171
172  GST_PINCH_SECOND_CANCELLED =
173      G(GS_PINCH, 1, TS_CANCELLED, TSI_ALWAYS),
174
175  GST_PINCH_THIRD_PRESSED =
176      G(GS_PINCH, 2, TS_PRESSED, TSI_NOT_PROCESSED),
177
178  GST_PINCH_THIRD_MOVED =
179      G(GS_PINCH, 2, TS_MOVED, TSI_NOT_PROCESSED),
180
181  GST_PINCH_THIRD_MOVED_HANDLED =
182      G(GS_PINCH, 2, TS_MOVED, TSI_PROCESSED),
183
184  GST_PINCH_THIRD_RELEASED =
185      G(GS_PINCH, 2, TS_RELEASED, TSI_ALWAYS),
186
187  GST_PINCH_THIRD_CANCELLED =
188      G(GS_PINCH, 2, TS_CANCELLED, TSI_ALWAYS),
189
190  GST_PINCH_FOURTH_PRESSED =
191      G(GS_PINCH, 3, TS_PRESSED, TSI_NOT_PROCESSED),
192
193  GST_PINCH_FOURTH_MOVED =
194      G(GS_PINCH, 3, TS_MOVED, TSI_NOT_PROCESSED),
195
196  GST_PINCH_FOURTH_MOVED_HANDLED =
197      G(GS_PINCH, 3, TS_MOVED, TSI_PROCESSED),
198
199  GST_PINCH_FOURTH_RELEASED =
200      G(GS_PINCH, 3, TS_RELEASED, TSI_ALWAYS),
201
202  GST_PINCH_FOURTH_CANCELLED =
203      G(GS_PINCH, 3, TS_CANCELLED, TSI_ALWAYS),
204
205  GST_PINCH_FIFTH_PRESSED =
206      G(GS_PINCH, 4, TS_PRESSED, TSI_NOT_PROCESSED),
207
208  GST_PINCH_FIFTH_MOVED =
209      G(GS_PINCH, 4, TS_MOVED, TSI_NOT_PROCESSED),
210
211  GST_PINCH_FIFTH_MOVED_HANDLED =
212      G(GS_PINCH, 4, TS_MOVED, TSI_PROCESSED),
213
214  GST_PINCH_FIFTH_RELEASED =
215      G(GS_PINCH, 4, TS_RELEASED, TSI_ALWAYS),
216
217  GST_PINCH_FIFTH_CANCELLED =
218      G(GS_PINCH, 4, TS_CANCELLED, TSI_ALWAYS),
219};
220
221// Builds a signature. Signatures are assembled by joining together
222// multiple bits.
223// 1 LSB bit so that the computed signature is always greater than 0
224// 3 bits for the |type|.
225// 2 bit for |touch_status|
226// 12 bits for |touch_id|
227// 14 bits for the |gesture_state|.
228EdgeStateSignatureType Signature(GestureState gesture_state,
229                                 unsigned int touch_id,
230                                 ui::EventType type,
231                                 TouchStatusInternal touch_status) {
232  CHECK((touch_id & 0xfff) == touch_id);
233  TouchState touch_state = TouchEventTypeToTouchState(type);
234  EdgeStateSignatureType signature = static_cast<EdgeStateSignatureType>
235      (G(gesture_state, touch_id, touch_state, touch_status));
236
237  switch (signature) {
238    case GST_NO_GESTURE_FIRST_PRESSED:
239    case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED:
240    case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED:
241    case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED:
242    case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED:
243    case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY:
244    case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED:
245    case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED:
246    case GST_SCROLL_FIRST_RELEASED:
247    case GST_SCROLL_FIRST_MOVED:
248    case GST_SCROLL_FIRST_CANCELLED:
249    case GST_SCROLL_SECOND_PRESSED:
250    case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED:
251    case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED_HANDLED:
252    case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED:
253    case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED:
254    case GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED:
255    case GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED:
256    case GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED:
257    case GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED:
258    case GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED:
259    case GST_PINCH_FIRST_MOVED:
260    case GST_PINCH_FIRST_MOVED_HANDLED:
261    case GST_PINCH_SECOND_MOVED:
262    case GST_PINCH_SECOND_MOVED_HANDLED:
263    case GST_PINCH_FIRST_RELEASED:
264    case GST_PINCH_SECOND_RELEASED:
265    case GST_PINCH_FIRST_CANCELLED:
266    case GST_PINCH_SECOND_CANCELLED:
267    case GST_PINCH_THIRD_PRESSED:
268    case GST_PINCH_THIRD_MOVED:
269    case GST_PINCH_THIRD_MOVED_HANDLED:
270    case GST_PINCH_THIRD_RELEASED:
271    case GST_PINCH_THIRD_CANCELLED:
272    case GST_PINCH_FOURTH_PRESSED:
273    case GST_PINCH_FOURTH_MOVED:
274    case GST_PINCH_FOURTH_MOVED_HANDLED:
275    case GST_PINCH_FOURTH_RELEASED:
276    case GST_PINCH_FOURTH_CANCELLED:
277    case GST_PINCH_FIFTH_PRESSED:
278    case GST_PINCH_FIFTH_MOVED:
279    case GST_PINCH_FIFTH_MOVED_HANDLED:
280    case GST_PINCH_FIFTH_RELEASED:
281    case GST_PINCH_FIFTH_CANCELLED:
282      break;
283    default:
284      signature = GST_INVALID;
285      break;
286  }
287
288  return signature;
289}
290#undef G
291
292float BoundingBoxDiagonal(const gfx::Rect& rect) {
293  float width = rect.width() * rect.width();
294  float height = rect.height() * rect.height();
295  return sqrt(width + height);
296}
297
298unsigned int ComputeTouchBitmask(const GesturePoint* points) {
299  unsigned int touch_bitmask = 0;
300  for (int i = 0; i < GestureSequence::kMaxGesturePoints; ++i) {
301    if (points[i].in_use())
302      touch_bitmask |= 1 << points[i].touch_id();
303  }
304  return touch_bitmask;
305}
306
307const float kFlingCurveNormalization = 1.0f / 1875.f;
308
309float CalibrateFlingVelocity(float velocity) {
310  const unsigned last_coefficient =
311      GestureConfiguration::NumAccelParams - 1;
312  float normalized_velocity = fabs(velocity * kFlingCurveNormalization);
313  float nu = 0.0f, x = 1.f;
314
315  for (int i = last_coefficient ; i >= 0; i--) {
316    float a = GestureConfiguration::fling_acceleration_curve_coefficients(i);
317    nu += x * a;
318    x *= normalized_velocity;
319  }
320  if (velocity < 0.f)
321    return std::max(nu * velocity, -GestureConfiguration::fling_velocity_cap());
322  else
323    return std::min(nu * velocity, GestureConfiguration::fling_velocity_cap());
324}
325
326}  // namespace
327
328////////////////////////////////////////////////////////////////////////////////
329// GestureSequence Public:
330
331GestureSequence::GestureSequence(GestureEventHelper* helper)
332    : state_(GS_NO_GESTURE),
333      flags_(0),
334      pinch_distance_start_(0.f),
335      pinch_distance_current_(0.f),
336      scroll_type_(ST_FREE),
337      point_count_(0),
338      helper_(helper) {
339}
340
341GestureSequence::~GestureSequence() {
342}
343
344GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
345    const TouchEvent& event,
346    EventResult result) {
347  StopLongPressTimerIfRequired(event);
348  last_touch_location_ = event.location();
349  if (result & ER_CONSUMED)
350    return NULL;
351
352  // Set a limit on the number of simultaneous touches in a gesture.
353  if (event.touch_id() >= kMaxGesturePoints)
354    return NULL;
355
356  if (event.type() == ui::ET_TOUCH_PRESSED) {
357    if (point_count_ == kMaxGesturePoints)
358      return NULL;
359    GesturePoint* new_point = &points_[event.touch_id()];
360    // We shouldn't be able to get two PRESSED events from the same
361    // finger without either a RELEASE or CANCEL in between. But let's not crash
362    // in a release build.
363    if (new_point->in_use()) {
364      LOG(ERROR) << "Received a second press for a point: " << event.touch_id();
365      new_point->ResetVelocity();
366      new_point->UpdateValues(event);
367      return NULL;
368    }
369    new_point->set_point_id(point_count_++);
370    new_point->set_touch_id(event.touch_id());
371  }
372
373  GestureState last_state = state_;
374
375  // NOTE: when modifying these state transitions, also update gestures.dot
376  scoped_ptr<Gestures> gestures(new Gestures());
377  GesturePoint& point = GesturePointForEvent(event);
378  point.UpdateValues(event);
379  RecreateBoundingBox();
380  flags_ = event.flags();
381  const int point_id = point.point_id();
382  if (point_id < 0)
383    return NULL;
384
385  // Send GESTURE_BEGIN for any touch pressed.
386  if (event.type() == ui::ET_TOUCH_PRESSED)
387    AppendBeginGestureEvent(point, gestures.get());
388
389  TouchStatusInternal status_internal = (result == ER_UNHANDLED) ?
390      TSI_NOT_PROCESSED : TSI_PROCESSED;
391
392  EdgeStateSignatureType signature = Signature(state_, point_id,
393      event.type(), status_internal);
394
395  if (signature == GST_INVALID)
396    signature = Signature(state_, point_id, event.type(), TSI_ALWAYS);
397
398  switch (signature) {
399    case GST_INVALID:
400      break;
401
402    case GST_NO_GESTURE_FIRST_PRESSED:
403      TouchDown(event, point, gestures.get());
404      set_state(GS_PENDING_SYNTHETIC_CLICK);
405      break;
406    case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED:
407      if (Click(event, point, gestures.get()))
408        point.UpdateForTap();
409      else
410        PrependTapCancelGestureEvent(point, gestures.get());
411      set_state(GS_NO_GESTURE);
412      break;
413    case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED:
414    case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY:
415      if (ScrollStart(event, point, gestures.get())) {
416        PrependTapCancelGestureEvent(point, gestures.get());
417        set_state(GS_SCROLL);
418        if (ScrollUpdate(event, point, gestures.get()))
419          point.UpdateForScroll();
420      }
421      break;
422    case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED:
423      // TODO(rbyers): This should be able to trigger a TapCancel
424      // if we moved far enough. crbug.com/146397
425      break;
426    case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED:
427    case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED:
428      PrependTapCancelGestureEvent(point, gestures.get());
429      set_state(GS_NO_GESTURE);
430      break;
431    case GST_SCROLL_FIRST_MOVED:
432      if (scroll_type_ == ST_VERTICAL ||
433          scroll_type_ == ST_HORIZONTAL)
434        BreakRailScroll(event, point, gestures.get());
435      if (ScrollUpdate(event, point, gestures.get()))
436        point.UpdateForScroll();
437      break;
438    case GST_SCROLL_FIRST_RELEASED:
439    case GST_SCROLL_FIRST_CANCELLED:
440      ScrollEnd(event, point, gestures.get());
441      set_state(GS_NO_GESTURE);
442      break;
443    case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED:
444      PrependTapCancelGestureEvent(point, gestures.get());
445      if (IsSecondTouchDownCloseEnoughForTwoFingerTap()) {
446        TwoFingerTouchDown(event, point, gestures.get());
447        set_state(GS_PENDING_TWO_FINGER_TAP);
448        break;
449      }
450      // fall through
451    case GST_SCROLL_SECOND_PRESSED:
452      PinchStart(event, point, gestures.get());
453      set_state(GS_PINCH);
454      break;
455    case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED:
456    case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED:
457      TwoFingerTouchReleased(event, point, gestures.get());
458      set_state(GS_SCROLL);
459      break;
460    case GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED:
461    case GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED:
462      if (TwoFingerTouchMove(event, point, gestures.get()))
463        set_state(GS_PINCH);
464      break;
465    case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED_HANDLED:
466    case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED:
467    case GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED:
468    case GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED:
469      scroll_type_ = ST_FREE;
470      set_state(GS_SCROLL);
471      break;
472    case GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED:
473      PinchStart(event, point, gestures.get());
474      set_state(GS_PINCH);
475      break;
476    case GST_PINCH_FIRST_MOVED_HANDLED:
477    case GST_PINCH_SECOND_MOVED_HANDLED:
478    case GST_PINCH_THIRD_MOVED_HANDLED:
479    case GST_PINCH_FOURTH_MOVED_HANDLED:
480    case GST_PINCH_FIFTH_MOVED_HANDLED:
481      break;
482    case GST_PINCH_FIRST_MOVED:
483    case GST_PINCH_SECOND_MOVED:
484    case GST_PINCH_THIRD_MOVED:
485    case GST_PINCH_FOURTH_MOVED:
486    case GST_PINCH_FIFTH_MOVED:
487      if (PinchUpdate(event, point, gestures.get())) {
488        for (int i = 0; i < point_count_; ++i)
489          GetPointByPointId(i)->UpdateForScroll();
490      }
491      break;
492    case GST_PINCH_FIRST_RELEASED:
493    case GST_PINCH_SECOND_RELEASED:
494    case GST_PINCH_THIRD_RELEASED:
495    case GST_PINCH_FOURTH_RELEASED:
496    case GST_PINCH_FIFTH_RELEASED:
497    case GST_PINCH_FIRST_CANCELLED:
498    case GST_PINCH_SECOND_CANCELLED:
499    case GST_PINCH_THIRD_CANCELLED:
500    case GST_PINCH_FOURTH_CANCELLED:
501    case GST_PINCH_FIFTH_CANCELLED:
502      // Was it a swipe? i.e. were all the fingers moving in the same
503      // direction?
504      MaybeSwipe(event, point, gestures.get());
505
506      if (point_count_ == 2) {
507        PinchEnd(event, point, gestures.get());
508
509        // Once pinch ends, it should still be possible to scroll with the
510        // remaining finger on the screen.
511        set_state(GS_SCROLL);
512      } else {
513        // Nothing else to do if we have more than 2 fingers active, since after
514        // the release/cancel, there are still enough fingers to do pinch.
515        // pinch_distance_current_ and pinch_distance_start_ will be updated
516        // when the bounding-box is updated.
517      }
518      ResetVelocities();
519      break;
520    case GST_PINCH_THIRD_PRESSED:
521    case GST_PINCH_FOURTH_PRESSED:
522    case GST_PINCH_FIFTH_PRESSED:
523      pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_);
524      pinch_distance_start_ = pinch_distance_current_;
525      break;
526  }
527
528  if (event.type() == ui::ET_TOUCH_RELEASED ||
529      event.type() == ui::ET_TOUCH_CANCELLED)
530    AppendEndGestureEvent(point, gestures.get());
531
532  if (state_ != last_state)
533    DVLOG(4) << "Gesture Sequence"
534             << " State: " << state_
535             << " touch id: " << event.touch_id();
536
537  if (last_state == GS_PENDING_SYNTHETIC_CLICK && state_ != last_state)
538    GetLongPressTimer()->Stop();
539
540  // The set of point_ids must be contiguous and include 0.
541  // When a touch point is released, all points with ids greater than the
542  // released point must have their ids decremented, or the set of point_ids
543  // could end up with gaps.
544  if (event.type() == ui::ET_TOUCH_RELEASED ||
545      event.type() == ui::ET_TOUCH_CANCELLED) {
546    for (int i = 0; i < kMaxGesturePoints; ++i) {
547      GesturePoint& iter_point = points_[i];
548      if (iter_point.point_id() > point.point_id())
549        iter_point.set_point_id(iter_point.point_id() - 1);
550    }
551
552    point.Reset();
553    --point_count_;
554    CHECK_GE(point_count_, 0);
555    RecreateBoundingBox();
556    if (state_ == GS_PINCH) {
557      pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_);
558      pinch_distance_start_ = pinch_distance_current_;
559    }
560  }
561
562  const ui::LatencyInfo* touch_latency = event.latency();
563  Gestures::iterator it = gestures->begin();
564  for (; it != gestures->end(); it++) {
565    (*it)->latency()->MergeWith(*touch_latency);
566  }
567
568  return gestures.release();
569}
570
571void GestureSequence::RecreateBoundingBox() {
572  // TODO(sad): Recreating the bounding box at every touch-event is not very
573  // efficient. This should be made better.
574  if (point_count_ == 0) {
575    bounding_box_.SetRect(0, 0, 0, 0);
576  } else if (point_count_ == 1) {
577    bounding_box_ = GetPointByPointId(0)->enclosing_rectangle();
578  } else {
579    int left = INT_MAX / 20, top = INT_MAX / 20;
580    int right = INT_MIN / 20, bottom = INT_MIN / 20;
581    for (int i = 0; i < kMaxGesturePoints; ++i) {
582      if (!points_[i].in_use())
583        continue;
584      // Using the |enclosing_rectangle()| for the touch-points would be ideal.
585      // However, this becomes brittle especially when a finger is in motion
586      // because the change in radius can overshadow the actual change in
587      // position. So the actual position of the point is used instead.
588      const gfx::Point& point = points_[i].last_touch_position();
589      left = std::min(left, point.x());
590      right = std::max(right, point.x());
591      top = std::min(top, point.y());
592      bottom = std::max(bottom, point.y());
593    }
594    bounding_box_.SetRect(left, top, right - left, bottom - top);
595  }
596}
597
598void GestureSequence::ResetVelocities() {
599  for (int i = 0; i < kMaxGesturePoints; ++i) {
600    if (points_[i].in_use())
601      points_[i].ResetVelocity();
602  }
603}
604
605////////////////////////////////////////////////////////////////////////////////
606// GestureSequence Protected:
607
608base::OneShotTimer<GestureSequence>* GestureSequence::CreateTimer() {
609  return new base::OneShotTimer<GestureSequence>();
610}
611
612base::OneShotTimer<GestureSequence>* GestureSequence::GetLongPressTimer() {
613  if (!long_press_timer_.get())
614    long_press_timer_.reset(CreateTimer());
615  return long_press_timer_.get();
616}
617
618////////////////////////////////////////////////////////////////////////////////
619// GestureSequence Private:
620
621GesturePoint& GestureSequence::GesturePointForEvent(
622    const TouchEvent& event) {
623  return points_[event.touch_id()];
624}
625
626GesturePoint* GestureSequence::GetPointByPointId(int point_id) {
627  DCHECK(0 <= point_id && point_id < kMaxGesturePoints);
628  for (int i = 0; i < kMaxGesturePoints; ++i) {
629    GesturePoint& point = points_[i];
630    if (point.in_use() && point.point_id() == point_id)
631      return &point;
632  }
633  NOTREACHED();
634  return NULL;
635}
636
637bool GestureSequence::IsSecondTouchDownCloseEnoughForTwoFingerTap() {
638  gfx::Point p1 = GetPointByPointId(0)->last_touch_position();
639  gfx::Point p2 = GetPointByPointId(1)->last_touch_position();
640  double max_distance =
641      GestureConfiguration::max_distance_for_two_finger_tap_in_pixels();
642  double distance = (p1.x() - p2.x()) * (p1.x() - p2.x()) +
643      (p1.y() - p2.y()) * (p1.y() - p2.y());
644  if (distance < max_distance * max_distance)
645    return true;
646  return false;
647}
648
649GestureEvent* GestureSequence::CreateGestureEvent(
650    const GestureEventDetails& details,
651    const gfx::Point& location,
652    int flags,
653    base::Time timestamp,
654    unsigned int touch_id_bitmask) {
655  GestureEventDetails gesture_details(details);
656  gesture_details.set_touch_points(point_count_);
657  gesture_details.set_bounding_box(bounding_box_);
658  base::TimeDelta time_stamp =
659      base::TimeDelta::FromMicroseconds(timestamp.ToDoubleT() * 1000000);
660  return new GestureEvent(gesture_details.type(), location.x(), location.y(),
661                          flags, time_stamp, gesture_details,
662                          touch_id_bitmask);
663}
664
665void GestureSequence::AppendTapDownGestureEvent(const GesturePoint& point,
666                                                Gestures* gestures) {
667  gestures->push_back(CreateGestureEvent(
668      GestureEventDetails(ui::ET_GESTURE_TAP_DOWN, 0, 0),
669      point.first_touch_position(),
670      flags_,
671      base::Time::FromDoubleT(point.last_touch_time()),
672      1 << point.touch_id()));
673}
674
675void GestureSequence::PrependTapCancelGestureEvent(const GesturePoint& point,
676                                            Gestures* gestures) {
677  gestures->insert(gestures->begin(), CreateGestureEvent(
678    GestureEventDetails(ui::ET_GESTURE_TAP_CANCEL, 0, 0),
679    point.first_touch_position(),
680    flags_,
681    base::Time::FromDoubleT(point.last_touch_time()),
682    1 << point.touch_id()));
683}
684
685void GestureSequence::AppendBeginGestureEvent(const GesturePoint& point,
686                                              Gestures* gestures) {
687  gestures->push_back(CreateGestureEvent(
688      GestureEventDetails(ui::ET_GESTURE_BEGIN, 0, 0),
689      point.first_touch_position(),
690      flags_,
691      base::Time::FromDoubleT(point.last_touch_time()),
692      1 << point.touch_id()));
693}
694
695void GestureSequence::AppendEndGestureEvent(const GesturePoint& point,
696                                              Gestures* gestures) {
697  gestures->push_back(CreateGestureEvent(
698      GestureEventDetails(ui::ET_GESTURE_END, 0, 0),
699      point.first_touch_position(),
700      flags_,
701      base::Time::FromDoubleT(point.last_touch_time()),
702      1 << point.touch_id()));
703}
704
705void GestureSequence::AppendClickGestureEvent(const GesturePoint& point,
706                                              int tap_count,
707                                              Gestures* gestures) {
708  gfx::Rect er = point.enclosing_rectangle();
709  gfx::Point center = er.CenterPoint();
710  gestures->push_back(CreateGestureEvent(
711      GestureEventDetails(ui::ET_GESTURE_TAP, tap_count, 0),
712      center,
713      flags_,
714      base::Time::FromDoubleT(point.last_touch_time()),
715      1 << point.touch_id()));
716}
717
718void GestureSequence::AppendScrollGestureBegin(const GesturePoint& point,
719                                               const gfx::Point& location,
720                                               Gestures* gestures) {
721  gestures->push_back(CreateGestureEvent(
722      GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 0),
723      location,
724      flags_,
725      base::Time::FromDoubleT(point.last_touch_time()),
726      1 << point.touch_id()));
727}
728
729void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point,
730                                             const gfx::Point& location,
731                                             Gestures* gestures,
732                                             float x_velocity,
733                                             float y_velocity) {
734  float railed_x_velocity = x_velocity;
735  float railed_y_velocity = y_velocity;
736  last_scroll_prediction_offset_.set_x(0);
737  last_scroll_prediction_offset_.set_y(0);
738
739  if (scroll_type_ == ST_HORIZONTAL)
740    railed_y_velocity = 0;
741  else if (scroll_type_ == ST_VERTICAL)
742    railed_x_velocity = 0;
743
744  if (railed_x_velocity != 0 || railed_y_velocity != 0) {
745
746    gestures->push_back(CreateGestureEvent(
747        GestureEventDetails(ui::ET_SCROLL_FLING_START,
748            CalibrateFlingVelocity(railed_x_velocity),
749            CalibrateFlingVelocity(railed_y_velocity),
750            CalibrateFlingVelocity(x_velocity),
751            CalibrateFlingVelocity(y_velocity)),
752        location,
753        flags_,
754        base::Time::FromDoubleT(point.last_touch_time()),
755        1 << point.touch_id()));
756  } else {
757    gestures->push_back(CreateGestureEvent(
758        GestureEventDetails(ui::ET_GESTURE_SCROLL_END, 0, 0),
759        location,
760        flags_,
761        base::Time::FromDoubleT(point.last_touch_time()),
762        1 << point.touch_id()));
763  }
764}
765
766void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point,
767                                                Gestures* gestures) {
768  static bool use_scroll_prediction = CommandLine::ForCurrentProcess()->
769      HasSwitch(switches::kEnableScrollPrediction);
770  gfx::Vector2dF d;
771  gfx::Point location;
772  if (point_count_ == 1) {
773    d = point.ScrollDelta();
774    location = point.last_touch_position();
775  } else {
776    location = bounding_box_.CenterPoint();
777    d = location - latest_multi_scroll_update_location_;
778    latest_multi_scroll_update_location_ = location;
779  }
780
781  if (use_scroll_prediction) {
782    // Remove the extra distance added by the last scroll prediction and add
783    // the new prediction offset.
784    d -= last_scroll_prediction_offset_;
785    last_scroll_prediction_offset_.set_x(
786        GestureConfiguration::scroll_prediction_seconds() * point.XVelocity());
787    last_scroll_prediction_offset_.set_y(
788        GestureConfiguration::scroll_prediction_seconds() * point.YVelocity());
789    d += last_scroll_prediction_offset_;
790    location += gfx::Vector2d(last_scroll_prediction_offset_.x(),
791                              last_scroll_prediction_offset_.y());
792  }
793
794  gfx::Vector2dF o = d;
795
796  if (scroll_type_ == ST_HORIZONTAL)
797    d.set_y(0);
798  else if (scroll_type_ == ST_VERTICAL)
799    d.set_x(0);
800  if (d.IsZero())
801    return;
802
803  GestureEventDetails details(ui::ET_GESTURE_SCROLL_UPDATE,
804                              d.x(), d.y(), o.x(), o.y());
805  details.SetScrollVelocity(
806      scroll_type_ == ST_VERTICAL ? 0 : point.XVelocity(),
807      scroll_type_ == ST_HORIZONTAL ? 0 : point.YVelocity(),
808      point.XVelocity(),
809      point.YVelocity());
810  gestures->push_back(CreateGestureEvent(
811      details,
812      location,
813      flags_,
814      base::Time::FromDoubleT(point.last_touch_time()),
815      ComputeTouchBitmask(points_)));
816}
817
818void GestureSequence::AppendPinchGestureBegin(const GesturePoint& p1,
819                                              const GesturePoint& p2,
820                                              Gestures* gestures) {
821  gfx::Point center = bounding_box_.CenterPoint();
822  gestures->push_back(CreateGestureEvent(
823      GestureEventDetails(ui::ET_GESTURE_PINCH_BEGIN, 0, 0),
824      center,
825      flags_,
826      base::Time::FromDoubleT(p1.last_touch_time()),
827      1 << p1.touch_id() | 1 << p2.touch_id()));
828}
829
830void GestureSequence::AppendPinchGestureEnd(const GesturePoint& p1,
831                                            const GesturePoint& p2,
832                                            float scale,
833                                            Gestures* gestures) {
834  gfx::Point center = bounding_box_.CenterPoint();
835  gestures->push_back(CreateGestureEvent(
836      GestureEventDetails(ui::ET_GESTURE_PINCH_END, 0, 0),
837      center,
838      flags_,
839      base::Time::FromDoubleT(p1.last_touch_time()),
840      1 << p1.touch_id() | 1 << p2.touch_id()));
841}
842
843void GestureSequence::AppendPinchGestureUpdate(const GesturePoint& point,
844                                               float scale,
845                                               Gestures* gestures) {
846  // TODO(sad): Compute rotation and include it in delta_y.
847  // http://crbug.com/113145
848  gestures->push_back(CreateGestureEvent(
849      GestureEventDetails(ui::ET_GESTURE_PINCH_UPDATE, scale, 0),
850      bounding_box_.CenterPoint(),
851      flags_,
852      base::Time::FromDoubleT(point.last_touch_time()),
853      ComputeTouchBitmask(points_)));
854}
855
856void GestureSequence::AppendSwipeGesture(const GesturePoint& point,
857                                         int swipe_x,
858                                         int swipe_y,
859                                         Gestures* gestures) {
860  gestures->push_back(CreateGestureEvent(
861      GestureEventDetails(ui::ET_GESTURE_MULTIFINGER_SWIPE, swipe_x, swipe_y),
862      bounding_box_.CenterPoint(),
863      flags_,
864      base::Time::FromDoubleT(point.last_touch_time()),
865      ComputeTouchBitmask(points_)));
866}
867
868void GestureSequence::AppendTwoFingerTapGestureEvent(Gestures* gestures) {
869  const GesturePoint* point = GetPointByPointId(0);
870  const gfx::Rect rect = point->enclosing_rectangle();
871  gestures->push_back(CreateGestureEvent(
872      GestureEventDetails(ui::ET_GESTURE_TWO_FINGER_TAP,
873                          rect.width(),
874                          rect.height()),
875      point->enclosing_rectangle().CenterPoint(),
876      flags_,
877      base::Time::FromDoubleT(point->last_touch_time()),
878      1 << point->touch_id()));
879}
880
881bool GestureSequence::Click(const TouchEvent& event,
882                            const GesturePoint& point,
883                            Gestures* gestures) {
884  DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK);
885  if (point.IsInClickWindow(event)) {
886    int tap_count = 1;
887    if (point.IsInTripleClickWindow(event))
888      tap_count = 3;
889    else if (point.IsInDoubleClickWindow(event))
890      tap_count = 2;
891    AppendClickGestureEvent(point, tap_count, gestures);
892    return true;
893  } else if (point.IsInsideManhattanSquare(event) &&
894      !GetLongPressTimer()->IsRunning()) {
895    AppendLongTapGestureEvent(point, gestures);
896  }
897  return false;
898}
899
900bool GestureSequence::ScrollStart(const TouchEvent& event,
901                                  GesturePoint& point,
902                                  Gestures* gestures) {
903  DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK);
904  if (!point.IsConsistentScrollingActionUnderway() &&
905      !point.IsInScrollWindow(event))
906    return false;
907  AppendScrollGestureBegin(point, point.first_touch_position(), gestures);
908  if (point.IsInHorizontalRailWindow())
909    scroll_type_ = ST_HORIZONTAL;
910  else if (point.IsInVerticalRailWindow())
911    scroll_type_ = ST_VERTICAL;
912  else
913    scroll_type_ = ST_FREE;
914  return true;
915}
916
917void GestureSequence::BreakRailScroll(const TouchEvent& event,
918                                      GesturePoint& point,
919                                      Gestures* gestures) {
920  DCHECK(state_ == GS_SCROLL);
921  if (scroll_type_ == ST_HORIZONTAL &&
922      point.BreaksHorizontalRail())
923    scroll_type_ = ST_FREE;
924  else if (scroll_type_ == ST_VERTICAL &&
925           point.BreaksVerticalRail())
926    scroll_type_ = ST_FREE;
927}
928
929bool GestureSequence::ScrollUpdate(const TouchEvent& event,
930                                   GesturePoint& point,
931                                   Gestures* gestures) {
932  DCHECK(state_ == GS_SCROLL);
933  if (!point.DidScroll(event, 0))
934    return false;
935  AppendScrollGestureUpdate(point, gestures);
936  return true;
937}
938
939bool GestureSequence::TouchDown(const TouchEvent& event,
940                                const GesturePoint& point,
941                                Gestures* gestures) {
942  DCHECK(state_ == GS_NO_GESTURE);
943  AppendTapDownGestureEvent(point, gestures);
944  GetLongPressTimer()->Start(
945      FROM_HERE,
946      base::TimeDelta::FromMilliseconds(
947          GestureConfiguration::long_press_time_in_seconds() * 1000),
948      this,
949      &GestureSequence::AppendLongPressGestureEvent);
950  return true;
951}
952
953bool GestureSequence::TwoFingerTouchDown(const TouchEvent& event,
954                                         const GesturePoint& point,
955                                         Gestures* gestures) {
956  DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK || state_ == GS_SCROLL);
957  if (state_ == GS_SCROLL) {
958    AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
959        0.f, 0.f);
960  }
961  second_touch_time_ = event.time_stamp();
962  return true;
963}
964
965bool GestureSequence::TwoFingerTouchMove(const TouchEvent& event,
966                                         const GesturePoint& point,
967                                         Gestures* gestures) {
968  DCHECK(state_ == GS_PENDING_TWO_FINGER_TAP);
969
970  base::TimeDelta time_delta = event.time_stamp() - second_touch_time_;
971  base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 *
972      ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click());
973  if (time_delta > max_delta || !point.IsInsideManhattanSquare(event)) {
974    PinchStart(event, point, gestures);
975    return true;
976  }
977  return false;
978}
979
980bool GestureSequence::TwoFingerTouchReleased(const TouchEvent& event,
981                                             const GesturePoint& point,
982                                             Gestures* gestures) {
983  DCHECK(state_ == GS_PENDING_TWO_FINGER_TAP);
984  base::TimeDelta time_delta = event.time_stamp() - second_touch_time_;
985  base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 *
986      ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click());
987  if (time_delta < max_delta && point.IsInsideManhattanSquare(event))
988    AppendTwoFingerTapGestureEvent(gestures);
989  return true;
990}
991
992void GestureSequence::AppendLongPressGestureEvent() {
993  const GesturePoint* point = GetPointByPointId(0);
994  scoped_ptr<GestureEvent> gesture(CreateGestureEvent(
995      GestureEventDetails(ui::ET_GESTURE_LONG_PRESS, 0, 0),
996      point->first_touch_position(),
997      flags_,
998      base::Time::FromDoubleT(point->last_touch_time()),
999      1 << point->touch_id()));
1000  helper_->DispatchLongPressGestureEvent(gesture.get());
1001}
1002
1003void GestureSequence::AppendLongTapGestureEvent(const GesturePoint& point,
1004                                                Gestures* gestures) {
1005  gfx::Rect er = point.enclosing_rectangle();
1006  gfx::Point center = er.CenterPoint();
1007  gestures->push_back(CreateGestureEvent(
1008      GestureEventDetails(ui::ET_GESTURE_LONG_TAP, 0, 0),
1009      center,
1010      flags_,
1011      base::Time::FromDoubleT(point.last_touch_time()),
1012      1 << point.touch_id()));
1013}
1014
1015bool GestureSequence::ScrollEnd(const TouchEvent& event,
1016                                GesturePoint& point,
1017                                Gestures* gestures) {
1018  DCHECK(state_ == GS_SCROLL);
1019  if (point.IsInFlickWindow(event)) {
1020    AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
1021        point.XVelocity(), point.YVelocity());
1022  } else {
1023    AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
1024        0.f, 0.f);
1025  }
1026  return true;
1027}
1028
1029bool GestureSequence::PinchStart(const TouchEvent& event,
1030                                 const GesturePoint& point,
1031                                 Gestures* gestures) {
1032  DCHECK(state_ == GS_SCROLL ||
1033         state_ == GS_PENDING_SYNTHETIC_CLICK ||
1034         state_ == GS_PENDING_TWO_FINGER_TAP);
1035
1036  // Once pinch starts, we immediately break rail scroll.
1037  scroll_type_ = ST_FREE;
1038
1039  const GesturePoint* point1 = GetPointByPointId(0);
1040  const GesturePoint* point2 = GetPointByPointId(1);
1041
1042  pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_);
1043  pinch_distance_start_ = pinch_distance_current_;
1044  latest_multi_scroll_update_location_ = bounding_box_.CenterPoint();
1045  AppendPinchGestureBegin(*point1, *point2, gestures);
1046
1047  if (state_ == GS_PENDING_SYNTHETIC_CLICK ||
1048      state_ == GS_PENDING_TWO_FINGER_TAP) {
1049    gfx::Point center = bounding_box_.CenterPoint();
1050    AppendScrollGestureBegin(point, center, gestures);
1051  }
1052
1053  return true;
1054}
1055
1056bool GestureSequence::PinchUpdate(const TouchEvent& event,
1057                                  GesturePoint& point,
1058                                  Gestures* gestures) {
1059  DCHECK(state_ == GS_PINCH);
1060
1061  // It is possible that the none of the touch-points changed their position,
1062  // but their radii changed, and that caused the bounding box to also change.
1063  // But in such cases, we do not want to either pinch or scroll.
1064  // To avoid small jiggles, it is also necessary to make sure that at least one
1065  // of the fingers moved enough before a pinch or scroll update is created.
1066  bool did_scroll = false;
1067  for (int i = 0; i < kMaxGesturePoints; ++i) {
1068    if (!points_[i].in_use() || !points_[i].DidScroll(event, 0))
1069      continue;
1070    did_scroll = true;
1071    break;
1072  }
1073
1074  if (!did_scroll)
1075    return false;
1076
1077  float distance = BoundingBoxDiagonal(bounding_box_);
1078
1079  if (abs(distance - pinch_distance_current_) >=
1080      GestureConfiguration::min_pinch_update_distance_in_pixels()) {
1081    AppendPinchGestureUpdate(point,
1082        distance / pinch_distance_current_, gestures);
1083    pinch_distance_current_ = distance;
1084  }
1085  AppendScrollGestureUpdate(point, gestures);
1086
1087  return true;
1088}
1089
1090bool GestureSequence::PinchEnd(const TouchEvent& event,
1091                               const GesturePoint& point,
1092                               Gestures* gestures) {
1093  DCHECK(state_ == GS_PINCH);
1094
1095  GesturePoint* point1 = GetPointByPointId(0);
1096  GesturePoint* point2 = GetPointByPointId(1);
1097
1098  float distance = BoundingBoxDiagonal(bounding_box_);
1099  AppendPinchGestureEnd(*point1, *point2,
1100      distance / pinch_distance_start_, gestures);
1101
1102  pinch_distance_start_ = 0;
1103  pinch_distance_current_ = 0;
1104  return true;
1105}
1106
1107bool GestureSequence::MaybeSwipe(const TouchEvent& event,
1108                                 const GesturePoint& point,
1109                                 Gestures* gestures) {
1110  DCHECK(state_ == GS_PINCH);
1111  float velocity_x = 0.f, velocity_y = 0.f;
1112  bool swipe_x = true, swipe_y = true;
1113  int sign_x = 0, sign_y = 0;
1114  int i = 0;
1115
1116  for (i = 0; i < kMaxGesturePoints; ++i) {
1117    if (points_[i].in_use())
1118      break;
1119  }
1120  DCHECK(i < kMaxGesturePoints);
1121
1122  velocity_x = points_[i].XVelocity();
1123  velocity_y = points_[i].YVelocity();
1124  sign_x = velocity_x < 0.f ? -1 : 1;
1125  sign_y = velocity_y < 0.f ? -1 : 1;
1126
1127  for (++i; i < kMaxGesturePoints; ++i) {
1128    if (!points_[i].in_use())
1129      continue;
1130
1131    if (sign_x * points_[i].XVelocity() < 0)
1132      swipe_x = false;
1133
1134    if (sign_y * points_[i].YVelocity() < 0)
1135      swipe_y = false;
1136
1137    velocity_x += points_[i].XVelocity();
1138    velocity_y += points_[i].YVelocity();
1139  }
1140
1141  float min_velocity = GestureConfiguration::min_swipe_speed();
1142  min_velocity *= min_velocity;
1143
1144  velocity_x = fabs(velocity_x / point_count_);
1145  velocity_y = fabs(velocity_y / point_count_);
1146  if (velocity_x < min_velocity)
1147    swipe_x = false;
1148  if (velocity_y < min_velocity)
1149    swipe_y = false;
1150
1151  if (!swipe_x && !swipe_y)
1152    return false;
1153
1154  if (!swipe_x)
1155    velocity_x = 0.001f;
1156  if (!swipe_y)
1157    velocity_y = 0.001f;
1158
1159  float ratio = velocity_x > velocity_y ? velocity_x / velocity_y :
1160                                          velocity_y / velocity_x;
1161  if (ratio < GestureConfiguration::max_swipe_deviation_ratio())
1162    return false;
1163
1164  if (velocity_x > velocity_y)
1165    sign_y = 0;
1166  else
1167    sign_x = 0;
1168
1169  AppendSwipeGesture(point, sign_x, sign_y, gestures);
1170
1171  return true;
1172}
1173
1174void GestureSequence::StopLongPressTimerIfRequired(const TouchEvent& event) {
1175  if (!GetLongPressTimer()->IsRunning() ||
1176      event.type() != ui::ET_TOUCH_MOVED)
1177    return;
1178
1179  // Since long press timer has been started, there should be a non-NULL point.
1180  const GesturePoint* point = GetPointByPointId(0);
1181  if (!ui::gestures::IsInsideManhattanSquare(point->first_touch_position(),
1182      event.location()))
1183    GetLongPressTimer()->Stop();
1184}
1185
1186}  // namespace ui
1187