blob: 6f6af6f15ae6f15bc3c62f4691bcdae7245bce80 [file] [log] [blame]
// 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/touch_disposition_gesture_filter.h"
#include <stddef.h>
#include "base/auto_reset.h"
#include "base/logging.h"
#include "ui/events/gesture_event_details.h"
namespace ui {
namespace {
// A BitSet32 is used for tracking dropped gesture types.
static_assert(ET_GESTURE_TYPE_END - ET_GESTURE_TYPE_START < 32,
"gesture type count too large");
GestureEventData CreateGesture(EventType type,
int motion_event_id,
MotionEvent::ToolType primary_tool_type,
const GestureEventDataPacket& packet) {
// As the event is purely synthetic, we needn't be strict with event flags.
int flags = EF_NONE;
GestureEventDetails details(type);
details.set_device_type(GestureDeviceType::DEVICE_TOUCHSCREEN);
return GestureEventData(details,
motion_event_id,
primary_tool_type,
packet.timestamp(),
packet.touch_location().x(),
packet.touch_location().y(),
packet.raw_touch_location().x(),
packet.raw_touch_location().y(),
1,
gfx::RectF(packet.touch_location(), gfx::SizeF()),
flags,
packet.unique_touch_event_id());
}
enum RequiredTouches {
RT_NONE = 0,
RT_START = 1 << 0,
RT_CURRENT = 1 << 1,
};
struct DispositionHandlingInfo {
// A bitwise-OR of |RequiredTouches|.
int required_touches;
EventType antecedent_event_type;
explicit DispositionHandlingInfo(int required_touches)
: required_touches(required_touches), antecedent_event_type(ET_UNKNOWN) {}
DispositionHandlingInfo(int required_touches,
EventType antecedent_event_type)
: required_touches(required_touches),
antecedent_event_type(antecedent_event_type) {}
};
DispositionHandlingInfo Info(int required_touches) {
return DispositionHandlingInfo(required_touches);
}
DispositionHandlingInfo Info(int required_touches,
EventType antecedent_event_type) {
return DispositionHandlingInfo(required_touches, antecedent_event_type);
}
// This approach to disposition handling is described at http://goo.gl/5G8PWJ.
DispositionHandlingInfo GetDispositionHandlingInfo(EventType type) {
switch (type) {
case ET_GESTURE_TAP_DOWN:
return Info(RT_START);
case ET_GESTURE_TAP_CANCEL:
return Info(RT_START);
case ET_GESTURE_SHOW_PRESS:
return Info(RT_START);
case ET_GESTURE_LONG_PRESS:
return Info(RT_START);
case ET_GESTURE_LONG_TAP:
return Info(RT_START | RT_CURRENT);
case ET_GESTURE_TAP:
return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
case ET_GESTURE_TAP_UNCONFIRMED:
return Info(RT_START | RT_CURRENT);
case ET_GESTURE_DOUBLE_TAP:
return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
case ET_GESTURE_SCROLL_BEGIN:
return Info(RT_START);
case ET_GESTURE_SCROLL_UPDATE:
return Info(RT_CURRENT, ET_GESTURE_SCROLL_BEGIN);
case ET_GESTURE_SCROLL_END:
return Info(RT_NONE, ET_GESTURE_SCROLL_BEGIN);
case ET_SCROLL_FLING_START:
// We rely on |EndScrollGestureIfNecessary| to end the scroll if the fling
// start is prevented.
return Info(RT_NONE, ET_GESTURE_SCROLL_UPDATE);
case ET_SCROLL_FLING_CANCEL:
return Info(RT_NONE, ET_SCROLL_FLING_START);
case ET_GESTURE_PINCH_BEGIN:
return Info(RT_START, ET_GESTURE_SCROLL_BEGIN);
case ET_GESTURE_PINCH_UPDATE:
return Info(RT_CURRENT, ET_GESTURE_PINCH_BEGIN);
case ET_GESTURE_PINCH_END:
return Info(RT_NONE, ET_GESTURE_PINCH_BEGIN);
case ET_GESTURE_BEGIN:
return Info(RT_START);
case ET_GESTURE_END:
return Info(RT_NONE, ET_GESTURE_BEGIN);
case ET_GESTURE_SWIPE:
return Info(RT_START, ET_GESTURE_SCROLL_BEGIN);
case ET_GESTURE_TWO_FINGER_TAP:
return Info(RT_START);
default:
break;
}
NOTREACHED();
return Info(RT_NONE);
}
int GetGestureTypeIndex(EventType type) {
DCHECK_GE(type, ET_GESTURE_TYPE_START);
DCHECK_LE(type, ET_GESTURE_TYPE_END);
return type - ET_GESTURE_TYPE_START;
}
bool IsTouchStartEvent(GestureEventDataPacket::GestureSource gesture_source) {
return gesture_source == GestureEventDataPacket::TOUCH_SEQUENCE_START ||
gesture_source == GestureEventDataPacket::TOUCH_START;
}
} // namespace
// TouchDispositionGestureFilter
TouchDispositionGestureFilter::TouchDispositionGestureFilter(
TouchDispositionGestureFilterClient* client)
: client_(client),
ending_event_motion_event_id_(0),
ending_event_primary_tool_type_(MotionEvent::ToolType::UNKNOWN),
needs_tap_ending_event_(false),
needs_show_press_event_(false),
needs_fling_ending_event_(false),
needs_scroll_ending_event_(false) {
DCHECK(client_);
}
TouchDispositionGestureFilter::~TouchDispositionGestureFilter() {
}
TouchDispositionGestureFilter::PacketResult
TouchDispositionGestureFilter::OnGesturePacket(
const GestureEventDataPacket& packet) {
if (packet.gesture_source() == GestureEventDataPacket::UNDEFINED ||
packet.gesture_source() == GestureEventDataPacket::INVALID)
return INVALID_PACKET_TYPE;
if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START)
sequences_.push(GestureSequence());
if (IsEmpty())
return INVALID_PACKET_ORDER;
if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT &&
Tail().empty()) {
// Handle the timeout packet immediately if the packet preceding the timeout
// has already been dispatched.
FilterAndSendPacket(packet);
return SUCCESS;
}
// Check the packet's unique_touch_event_id is valid and unique with the
// exception of TOUCH_TIMEOUT packets which may share an id with other events.
// |TOUCH_TIMEOUT| packets don't wait for an ack, they are dispatched
// as soon as they reach the head of the queue, in |SendAckedEvents|.
if (!Tail().empty()) {
DCHECK((packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT)
|| (packet.unique_touch_event_id() !=
Tail().back().unique_touch_event_id()));
}
if (!Head().empty()) {
DCHECK((packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT) ||
packet.unique_touch_event_id() !=
Head().front().unique_touch_event_id());
}
Tail().push(packet);
return SUCCESS;
}
void TouchDispositionGestureFilter::OnTouchEventAck(
uint32_t unique_touch_event_id,
bool event_consumed,
bool is_source_touch_event_set_non_blocking) {
// Spurious asynchronous acks should not trigger a crash.
if (IsEmpty() || (Head().empty() && sequences_.size() == 1))
return;
if (Head().empty())
PopGestureSequence();
// If the tail's event_source is TOUCH_TIMEOUT, then it may share a
// unique_touch_event_id() with another event; in this case don't ack it here.
if (!Tail().empty() &&
Tail().back().unique_touch_event_id() == unique_touch_event_id &&
Tail().back().gesture_source() != GestureEventDataPacket::TOUCH_TIMEOUT) {
Tail().back().Ack(event_consumed, is_source_touch_event_set_non_blocking);
if (sequences_.size() == 1 && Tail().size() == 1)
SendAckedEvents();
} else {
DCHECK(!Head().empty());
DCHECK_EQ(Head().front().unique_touch_event_id(), unique_touch_event_id);
Head().front().Ack(event_consumed, is_source_touch_event_set_non_blocking);
SendAckedEvents();
}
}
void TouchDispositionGestureFilter::SendAckedEvents() {
// Dispatch all packets corresponding to ack'ed touches, as well as
// any pending timeout-based packets.
bool touch_packet_for_current_ack_handled = false;
while (!IsEmpty() && (!Head().empty() || sequences_.size() != 1)) {
if (Head().empty())
PopGestureSequence();
GestureSequence& sequence = Head();
DCHECK_NE(sequence.front().gesture_source(),
GestureEventDataPacket::UNDEFINED);
DCHECK_NE(sequence.front().gesture_source(),
GestureEventDataPacket::INVALID);
GestureEventDataPacket::GestureSource source =
sequence.front().gesture_source();
GestureEventDataPacket::AckState ack_state = sequence.front().ack_state();
if (source != GestureEventDataPacket::TOUCH_TIMEOUT) {
// We've sent all packets which aren't pending their ack.
if (ack_state == GestureEventDataPacket::AckState::PENDING)
break;
state_.OnTouchEventAck(
ack_state == GestureEventDataPacket::AckState::CONSUMED,
IsTouchStartEvent(source));
}
// We need to pop the current sequence before sending the packet, because
// sending the packet could result in this method being re-entered (e.g. on
// Aura, we could trigger a touch-cancel). As popping the sequence destroys
// the packet, we copy the packet before popping it.
touch_packet_for_current_ack_handled = true;
const GestureEventDataPacket packet = sequence.front();
sequence.pop();
FilterAndSendPacket(packet);
}
DCHECK(touch_packet_for_current_ack_handled);
}
bool TouchDispositionGestureFilter::IsEmpty() const {
return sequences_.empty();
}
void TouchDispositionGestureFilter::ResetGestureHandlingState() {
state_ = GestureHandlingState();
}
void TouchDispositionGestureFilter::FilterAndSendPacket(
const GestureEventDataPacket& packet) {
if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START) {
CancelTapIfNecessary(packet);
EndScrollIfNecessary(packet);
CancelFlingIfNecessary(packet);
} else if (packet.gesture_source() == GestureEventDataPacket::TOUCH_START) {
CancelTapIfNecessary(packet);
}
int gesture_end_index = -1;
for (size_t i = 0; i < packet.gesture_count(); ++i) {
const GestureEventData& gesture = packet.gesture(i);
DCHECK_GE(gesture.details.type(), ET_GESTURE_TYPE_START);
DCHECK_LE(gesture.details.type(), ET_GESTURE_TYPE_END);
if (state_.Filter(gesture.details.type())) {
CancelTapIfNecessary(packet);
continue;
}
if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT) {
// Sending a timed gesture could delete |this|, so we need to return
// directly after the |SendGesture| call.
SendGesture(gesture, packet);
// We should not have a timeout gesture and other gestures in the same
// packet.
DCHECK_EQ(1U, packet.gesture_count());
return;
}
// Occasionally scroll or tap cancel events are synthesized when a touch
// sequence has been canceled or terminated, we want to make sure that
// ET_GESTURE_END always happens after them.
if (gesture.type() == ET_GESTURE_END) {
// Make sure there is at most one ET_GESTURE_END event in each packet.
DCHECK_EQ(-1, gesture_end_index);
gesture_end_index = static_cast<int>(i);
continue;
}
SendGesture(gesture, packet);
}
if (packet.gesture_source() ==
GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL) {
EndScrollIfNecessary(packet);
CancelTapIfNecessary(packet);
} else if (packet.gesture_source() ==
GestureEventDataPacket::TOUCH_SEQUENCE_END) {
EndScrollIfNecessary(packet);
}
// Always send the ET_GESTURE_END event as the last one for every touch event.
if (gesture_end_index >= 0)
SendGesture(packet.gesture(gesture_end_index), packet);
}
void TouchDispositionGestureFilter::SendGesture(
const GestureEventData& event,
const GestureEventDataPacket& packet_being_sent) {
DCHECK(event.unique_touch_event_id ==
packet_being_sent.unique_touch_event_id());
// TODO(jdduke): Factor out gesture stream reparation code into a standalone
// utility class.
switch (event.type()) {
case ET_GESTURE_LONG_TAP:
if (!needs_tap_ending_event_)
return;
CancelTapIfNecessary(packet_being_sent);
CancelFlingIfNecessary(packet_being_sent);
break;
case ET_GESTURE_TAP_DOWN:
DCHECK(!needs_tap_ending_event_);
ending_event_motion_event_id_ = event.motion_event_id;
ending_event_primary_tool_type_ = event.primary_tool_type;
needs_show_press_event_ = true;
needs_tap_ending_event_ = true;
break;
case ET_GESTURE_SHOW_PRESS:
if (!needs_show_press_event_)
return;
needs_show_press_event_ = false;
break;
case ET_GESTURE_DOUBLE_TAP:
CancelTapIfNecessary(packet_being_sent);
needs_show_press_event_ = false;
break;
case ET_GESTURE_TAP:
DCHECK(needs_tap_ending_event_);
if (needs_show_press_event_) {
SendGesture(GestureEventData(ET_GESTURE_SHOW_PRESS, event),
packet_being_sent);
DCHECK(!needs_show_press_event_);
}
needs_tap_ending_event_ = false;
break;
case ET_GESTURE_TAP_CANCEL:
needs_show_press_event_ = false;
needs_tap_ending_event_ = false;
break;
case ET_GESTURE_SCROLL_BEGIN:
CancelTapIfNecessary(packet_being_sent);
CancelFlingIfNecessary(packet_being_sent);
EndScrollIfNecessary(packet_being_sent);
ending_event_motion_event_id_ = event.motion_event_id;
ending_event_primary_tool_type_ = event.primary_tool_type;
needs_scroll_ending_event_ = true;
break;
case ET_GESTURE_SCROLL_UPDATE:
if (state_.HasFilteredGestureType(ET_GESTURE_SCROLL_UPDATE)) {
GestureEventData modified_event(ET_GESTURE_SCROLL_UPDATE, event);
modified_event.details
.mark_previous_scroll_update_in_sequence_prevented();
client_->ForwardGestureEvent(modified_event);
return;
}
break;
case ET_GESTURE_SCROLL_END:
needs_scroll_ending_event_ = false;
break;
case ET_SCROLL_FLING_START:
CancelFlingIfNecessary(packet_being_sent);
ending_event_motion_event_id_ = event.motion_event_id;
ending_event_primary_tool_type_ = event.primary_tool_type;
needs_fling_ending_event_ = true;
needs_scroll_ending_event_ = false;
break;
case ET_SCROLL_FLING_CANCEL:
needs_fling_ending_event_ = false;
break;
default:
break;
}
client_->ForwardGestureEvent(event);
}
void TouchDispositionGestureFilter::CancelTapIfNecessary(
const GestureEventDataPacket& packet_being_sent) {
if (!needs_tap_ending_event_)
return;
SendGesture(CreateGesture(ET_GESTURE_TAP_CANCEL,
ending_event_motion_event_id_,
ending_event_primary_tool_type_,
packet_being_sent),
packet_being_sent);
DCHECK(!needs_tap_ending_event_);
}
void TouchDispositionGestureFilter::CancelFlingIfNecessary(
const GestureEventDataPacket& packet_being_sent) {
if (!needs_fling_ending_event_)
return;
SendGesture(CreateGesture(ET_SCROLL_FLING_CANCEL,
ending_event_motion_event_id_,
ending_event_primary_tool_type_,
packet_being_sent),
packet_being_sent);
DCHECK(!needs_fling_ending_event_);
}
void TouchDispositionGestureFilter::EndScrollIfNecessary(
const GestureEventDataPacket& packet_being_sent) {
if (!needs_scroll_ending_event_)
return;
SendGesture(CreateGesture(ET_GESTURE_SCROLL_END,
ending_event_motion_event_id_,
ending_event_primary_tool_type_,
packet_being_sent),
packet_being_sent);
DCHECK(!needs_scroll_ending_event_);
}
void TouchDispositionGestureFilter::PopGestureSequence() {
DCHECK(Head().empty());
state_ = GestureHandlingState();
sequences_.pop();
}
TouchDispositionGestureFilter::GestureSequence&
TouchDispositionGestureFilter::Head() {
DCHECK(!sequences_.empty());
return sequences_.front();
}
TouchDispositionGestureFilter::GestureSequence&
TouchDispositionGestureFilter::Tail() {
DCHECK(!sequences_.empty());
return sequences_.back();
}
// TouchDispositionGestureFilter::GestureHandlingState
TouchDispositionGestureFilter::GestureHandlingState::GestureHandlingState()
: start_touch_consumed_(false),
current_touch_consumed_(false) {}
void TouchDispositionGestureFilter::GestureHandlingState::OnTouchEventAck(
bool event_consumed,
bool is_touch_start_event) {
current_touch_consumed_ = event_consumed;
if (event_consumed && is_touch_start_event)
start_touch_consumed_ = true;
}
bool TouchDispositionGestureFilter::GestureHandlingState::Filter(
EventType gesture_type) {
DispositionHandlingInfo disposition_handling_info =
GetDispositionHandlingInfo(gesture_type);
int required_touches = disposition_handling_info.required_touches;
EventType antecedent_event_type =
disposition_handling_info.antecedent_event_type;
if ((required_touches & RT_START && start_touch_consumed_) ||
(required_touches & RT_CURRENT && current_touch_consumed_) ||
(antecedent_event_type != ET_UNKNOWN &&
last_gesture_of_type_dropped_.has_bit(
GetGestureTypeIndex(antecedent_event_type)))) {
last_gesture_of_type_dropped_.mark_bit(GetGestureTypeIndex(gesture_type));
any_gesture_of_type_dropped_.mark_bit(GetGestureTypeIndex(gesture_type));
return true;
}
last_gesture_of_type_dropped_.clear_bit(GetGestureTypeIndex(gesture_type));
return false;
}
bool TouchDispositionGestureFilter::GestureHandlingState::
HasFilteredGestureType(EventType gesture_type) const {
return any_gesture_of_type_dropped_.has_bit(
GetGestureTypeIndex(gesture_type));
}
} // namespace content