touch_disposition_gesture_filter.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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/touch_disposition_gesture_filter.h"
6
7#include "base/auto_reset.h"
8#include "base/logging.h"
9
10namespace ui {
11namespace {
12
13// A BitSet32 is used for tracking dropped gesture types.
14COMPILE_ASSERT(ET_GESTURE_TYPE_END - ET_GESTURE_TYPE_START < 32,
15               gesture_type_count_too_large);
16
17GestureEventData CreateGesture(EventType type) {
18  return GestureEventData(
19      type, base::TimeTicks(), 0, 0, GestureEventData::Details());
20}
21
22enum RequiredTouches {
23  RT_NONE = 0,
24  RT_START = 1 << 0,
25  RT_CURRENT = 1 << 1,
26};
27
28struct DispositionHandlingInfo {
29  // A bitwise-OR of |RequiredTouches|.
30  int required_touches;
31  EventType antecedent_event_type;
32
33  DispositionHandlingInfo(int required_touches)
34      : required_touches(required_touches) {}
35
36  DispositionHandlingInfo(int required_touches,
37                          EventType antecedent_event_type)
38      : required_touches(required_touches),
39        antecedent_event_type(antecedent_event_type) {}
40};
41
42DispositionHandlingInfo Info(int required_touches) {
43  return DispositionHandlingInfo(required_touches);
44}
45
46DispositionHandlingInfo Info(int required_touches,
47                             EventType antecedent_event_type) {
48  return DispositionHandlingInfo(required_touches, antecedent_event_type);
49}
50
51// This approach to disposition handling is described at http://goo.gl/5G8PWJ.
52DispositionHandlingInfo GetDispositionHandlingInfo(EventType type) {
53  switch (type) {
54    case ET_GESTURE_TAP_DOWN:
55      return Info(RT_START);
56    case ET_GESTURE_TAP_CANCEL:
57      return Info(RT_START);
58    case ET_GESTURE_SHOW_PRESS:
59      return Info(RT_START);
60    case ET_GESTURE_LONG_PRESS:
61      return Info(RT_START);
62    case ET_GESTURE_LONG_TAP:
63      return Info(RT_START | RT_CURRENT);
64    case ET_GESTURE_TAP:
65      return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
66    case ET_GESTURE_TAP_UNCONFIRMED:
67      return Info(RT_START | RT_CURRENT);
68    case ET_GESTURE_DOUBLE_TAP:
69      return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
70    case ET_GESTURE_SCROLL_BEGIN:
71      return Info(RT_START | RT_CURRENT);
72    case ET_GESTURE_SCROLL_UPDATE:
73      return Info(RT_CURRENT, ET_GESTURE_SCROLL_BEGIN);
74    case ET_GESTURE_SCROLL_END:
75      return Info(RT_NONE, ET_GESTURE_SCROLL_BEGIN);
76    case ET_SCROLL_FLING_START:
77      return Info(RT_NONE, ET_GESTURE_SCROLL_BEGIN);
78    case ET_SCROLL_FLING_CANCEL:
79      return Info(RT_NONE, ET_SCROLL_FLING_START);
80    case ET_GESTURE_PINCH_BEGIN:
81      return Info(RT_START, ET_GESTURE_SCROLL_BEGIN);
82    case ET_GESTURE_PINCH_UPDATE:
83      return Info(RT_CURRENT, ET_GESTURE_PINCH_BEGIN);
84    case ET_GESTURE_PINCH_END:
85      return Info(RT_NONE, ET_GESTURE_PINCH_BEGIN);
86    default:
87      break;
88  }
89  NOTREACHED();
90  return Info(RT_NONE);
91}
92
93int GetGestureTypeIndex(EventType type) {
94  return type - ET_GESTURE_TYPE_START;
95}
96
97bool IsTouchStartEvent(GestureEventDataPacket::GestureSource gesture_source) {
98  return gesture_source == GestureEventDataPacket::TOUCH_SEQUENCE_START ||
99         gesture_source == GestureEventDataPacket::TOUCH_START;
100}
101
102}  // namespace
103
104// TouchDispositionGestureFilter
105
106TouchDispositionGestureFilter::TouchDispositionGestureFilter(
107    TouchDispositionGestureFilterClient* client)
108    : client_(client),
109      needs_tap_ending_event_(false),
110      needs_fling_ending_event_(false),
111      needs_scroll_ending_event_(false) {
112  DCHECK(client_);
113}
114
115TouchDispositionGestureFilter::~TouchDispositionGestureFilter() {}
116
117TouchDispositionGestureFilter::PacketResult
118TouchDispositionGestureFilter::OnGesturePacket(
119    const GestureEventDataPacket& packet) {
120  if (packet.gesture_source() == GestureEventDataPacket::UNDEFINED ||
121      packet.gesture_source() == GestureEventDataPacket::INVALID)
122    return INVALID_PACKET_TYPE;
123
124  if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START)
125    sequences_.push(GestureSequence());
126
127  if (IsEmpty())
128    return INVALID_PACKET_ORDER;
129
130  if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT &&
131      Tail().empty()) {
132    // Handle the timeout packet immediately if the packet preceding the timeout
133    // has already been dispatched.
134    FilterAndSendPacket(packet);
135    return SUCCESS;
136  }
137
138  Tail().push(packet);
139  return SUCCESS;
140}
141
142void TouchDispositionGestureFilter::OnTouchEventAck(bool event_consumed) {
143  // Spurious touch acks from the renderer should not trigger a crash.
144  if (IsEmpty() || (Head().empty() && sequences_.size() == 1))
145    return;
146
147  if (Head().empty()) {
148    CancelTapIfNecessary();
149    CancelFlingIfNecessary();
150    EndScrollIfNecessary();
151    state_ = GestureHandlingState();
152    sequences_.pop();
153  }
154
155  GestureSequence& sequence = Head();
156
157  // Dispatch the packet corresponding to the ack'ed touch, as well as any
158  // additional timeout-based packets queued before the ack was received.
159  bool touch_packet_for_current_ack_handled = false;
160  while (!sequence.empty()) {
161    const GestureEventDataPacket& packet = sequence.front();
162    DCHECK_NE(packet.gesture_source(), GestureEventDataPacket::UNDEFINED);
163    DCHECK_NE(packet.gesture_source(), GestureEventDataPacket::INVALID);
164
165    if (packet.gesture_source() != GestureEventDataPacket::TOUCH_TIMEOUT) {
166      // We should handle at most one non-timeout based packet.
167      if (touch_packet_for_current_ack_handled)
168        break;
169      state_.OnTouchEventAck(event_consumed,
170                             IsTouchStartEvent(packet.gesture_source()));
171      touch_packet_for_current_ack_handled = true;
172    }
173    FilterAndSendPacket(packet);
174    sequence.pop();
175  }
176  DCHECK(touch_packet_for_current_ack_handled);
177}
178
179bool TouchDispositionGestureFilter::IsEmpty() const {
180  return sequences_.empty();
181}
182
183void TouchDispositionGestureFilter::FilterAndSendPacket(
184    const GestureEventDataPacket& packet) {
185  for (size_t i = 0; i < packet.gesture_count(); ++i) {
186    const GestureEventData& gesture = packet.gesture(i);
187    DCHECK(ET_GESTURE_TYPE_START <= gesture.type &&
188           gesture.type <= ET_GESTURE_TYPE_END);
189    if (state_.Filter(gesture.type)) {
190      CancelTapIfNecessary();
191      continue;
192    }
193    SendGesture(gesture);
194  }
195}
196
197void TouchDispositionGestureFilter::SendGesture(const GestureEventData& event) {
198  // TODO(jdduke): Factor out gesture stream reparation code into a standalone
199  // utility class.
200  switch (event.type) {
201    case ET_GESTURE_LONG_TAP:
202      CancelTapIfNecessary();
203      CancelFlingIfNecessary();
204      break;
205    case ET_GESTURE_TAP_DOWN:
206      DCHECK(!needs_tap_ending_event_);
207      needs_tap_ending_event_ = true;
208      break;
209    case ET_GESTURE_TAP:
210    case ET_GESTURE_TAP_CANCEL:
211    case ET_GESTURE_TAP_UNCONFIRMED:
212    case ET_GESTURE_DOUBLE_TAP:
213      needs_tap_ending_event_ = false;
214      break;
215    case ET_GESTURE_SCROLL_BEGIN:
216      CancelTapIfNecessary();
217      CancelFlingIfNecessary();
218      EndScrollIfNecessary();
219      needs_scroll_ending_event_ = true;
220      break;
221    case ET_GESTURE_SCROLL_END:
222      needs_scroll_ending_event_ = false;
223      break;
224    case ET_SCROLL_FLING_START:
225      CancelFlingIfNecessary();
226      needs_fling_ending_event_ = true;
227      needs_scroll_ending_event_ = false;
228      break;
229    case ET_SCROLL_FLING_CANCEL:
230      needs_fling_ending_event_ = false;
231      break;
232    default:
233      break;
234  }
235  client_->ForwardGestureEvent(event);
236}
237
238void TouchDispositionGestureFilter::CancelTapIfNecessary() {
239  if (!needs_tap_ending_event_)
240    return;
241
242  SendGesture(CreateGesture(ET_GESTURE_TAP_CANCEL));
243  DCHECK(!needs_tap_ending_event_);
244}
245
246void TouchDispositionGestureFilter::CancelFlingIfNecessary() {
247  if (!needs_fling_ending_event_)
248    return;
249
250  SendGesture(CreateGesture(ET_SCROLL_FLING_CANCEL));
251  DCHECK(!needs_fling_ending_event_);
252}
253
254void TouchDispositionGestureFilter::EndScrollIfNecessary() {
255  if (!needs_scroll_ending_event_)
256    return;
257
258  SendGesture(CreateGesture(ET_GESTURE_SCROLL_END));
259  DCHECK(!needs_scroll_ending_event_);
260}
261
262TouchDispositionGestureFilter::GestureSequence&
263TouchDispositionGestureFilter::Head() {
264  DCHECK(!sequences_.empty());
265  return sequences_.front();
266}
267
268TouchDispositionGestureFilter::GestureSequence&
269TouchDispositionGestureFilter::Tail() {
270  DCHECK(!sequences_.empty());
271  return sequences_.back();
272}
273
274// TouchDispositionGestureFilter::GestureHandlingState
275
276TouchDispositionGestureFilter::GestureHandlingState::GestureHandlingState()
277    : start_touch_consumed_(false),
278      current_touch_consumed_(false) {}
279
280void TouchDispositionGestureFilter::GestureHandlingState::OnTouchEventAck(
281    bool event_consumed,
282    bool is_touch_start_event) {
283  current_touch_consumed_ = event_consumed;
284  if (event_consumed && is_touch_start_event)
285    start_touch_consumed_ = true;
286}
287
288bool TouchDispositionGestureFilter::GestureHandlingState::Filter(
289    EventType gesture_type) {
290  DispositionHandlingInfo disposition_handling_info =
291      GetDispositionHandlingInfo(gesture_type);
292
293  int required_touches = disposition_handling_info.required_touches;
294  if ((required_touches & RT_START && start_touch_consumed_) ||
295      (required_touches & RT_CURRENT && current_touch_consumed_) ||
296      (last_gesture_of_type_dropped_.has_bit(GetGestureTypeIndex(
297          disposition_handling_info.antecedent_event_type)))) {
298    last_gesture_of_type_dropped_.mark_bit(GetGestureTypeIndex(gesture_type));
299    return true;
300  }
301  last_gesture_of_type_dropped_.clear_bit(GetGestureTypeIndex(gesture_type));
302  return false;
303}
304
305}  // namespace content
306