blob: 008feb237193c41e4fd8d36902d520605ad93b31 [file] [log] [blame]
// Copyright 2013 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 "content/browser/renderer_host/input/tap_suppression_controller.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/tap_suppression_controller_client.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
namespace content {
// The tapDownTimer is used to avoid waiting for an arbitrarily late fling
// cancel ack. While the timer is running, if a fling cancel ack with
// |Processed = false| arrives, all stashed gesture events get forwarded. If
// the timer expires, the controller forwards stashed GestureTapDown only, and
// drops the rest of the stashed events. The timer delay should be large enough
// for a GestureLongPress to get stashed and forwarded if needed. It's still
// possible for a GestureLongPress to arrive after the timer expiration. In
// this case, it will be suppressed if the controller is in SUPPRESSING_TAPS
// state.
TapSuppressionController::Config::Config()
: enabled(false),
max_cancel_to_down_time(base::TimeDelta::FromMilliseconds(180)) {
ui::GestureConfiguration* gesture_config =
ui::GestureConfiguration::GetInstance();
max_tap_gap_time = base::TimeDelta::FromMilliseconds(
gesture_config->long_press_time_in_ms() + 50);
}
TapSuppressionController::TapSuppressionController(
TapSuppressionControllerClient* client,
const Config& config)
: client_(client),
state_(config.enabled ? NOTHING : DISABLED),
max_cancel_to_down_time_(config.max_cancel_to_down_time),
max_tap_gap_time_(config.max_tap_gap_time) {
}
TapSuppressionController::~TapSuppressionController() {}
void TapSuppressionController::GestureFlingCancel() {
switch (state_) {
case DISABLED:
break;
case NOTHING:
case GFC_IN_PROGRESS:
case LAST_CANCEL_STOPPED_FLING:
case SUPPRESSING_TAPS:
state_ = GFC_IN_PROGRESS;
break;
case TAP_DOWN_STASHED:
break;
}
}
void TapSuppressionController::GestureFlingCancelAck(bool processed) {
base::TimeTicks event_time = Now();
switch (state_) {
case DISABLED:
case NOTHING:
case SUPPRESSING_TAPS:
break;
case GFC_IN_PROGRESS:
if (processed)
fling_cancel_time_ = event_time;
state_ = LAST_CANCEL_STOPPED_FLING;
break;
case TAP_DOWN_STASHED:
if (!processed) {
TRACE_EVENT0("browser",
"TapSuppressionController::GestureFlingCancelAck");
StopTapDownTimer();
// If the fling cancel is not processed, forward all stashed
// gesture events.
client_->ForwardStashedGestureEvents();
state_ = NOTHING;
} // Else waiting for the timer to release the stashed tap down.
break;
case LAST_CANCEL_STOPPED_FLING:
break;
}
}
bool TapSuppressionController::ShouldDeferTapDown() {
base::TimeTicks event_time = Now();
switch (state_) {
case DISABLED:
case NOTHING:
return false;
case GFC_IN_PROGRESS:
state_ = TAP_DOWN_STASHED;
StartTapDownTimer(max_tap_gap_time_);
return true;
case TAP_DOWN_STASHED:
NOTREACHED() << "TapDown on TAP_DOWN_STASHED state";
state_ = NOTHING;
return false;
case LAST_CANCEL_STOPPED_FLING:
if ((event_time - fling_cancel_time_) < max_cancel_to_down_time_) {
state_ = TAP_DOWN_STASHED;
StartTapDownTimer(max_tap_gap_time_);
return true;
} else {
state_ = NOTHING;
return false;
}
// Stop suppressing tap end events.
case SUPPRESSING_TAPS:
state_ = NOTHING;
return false;
}
NOTREACHED() << "Invalid state";
return false;
}
bool TapSuppressionController::ShouldSuppressTapEnd() {
switch (state_) {
case DISABLED:
case NOTHING:
case GFC_IN_PROGRESS:
return false;
case TAP_DOWN_STASHED:
// A tap cancel happens before long tap and two finger tap events. To
// drop the latter events as well as the tap cancel, change the state
// to "SUPPRESSING_TAPS" when the stashed tap down is dropped.
state_ = SUPPRESSING_TAPS;
StopTapDownTimer();
client_->DropStashedTapDown();
return true;
case LAST_CANCEL_STOPPED_FLING:
NOTREACHED() << "Invalid tap end on LAST_CANCEL_STOPPED_FLING state";
case SUPPRESSING_TAPS:
return true;
}
return false;
}
base::TimeTicks TapSuppressionController::Now() {
return base::TimeTicks::Now();
}
void TapSuppressionController::StartTapDownTimer(const base::TimeDelta& delay) {
tap_down_timer_.Start(FROM_HERE, delay, this,
&TapSuppressionController::TapDownTimerExpired);
}
void TapSuppressionController::StopTapDownTimer() {
tap_down_timer_.Stop();
}
void TapSuppressionController::TapDownTimerExpired() {
switch (state_) {
case DISABLED:
case NOTHING:
case SUPPRESSING_TAPS:
NOTREACHED() << "Timer fired on invalid state.";
break;
case GFC_IN_PROGRESS:
case LAST_CANCEL_STOPPED_FLING:
NOTREACHED() << "Timer fired on invalid state.";
state_ = NOTHING;
break;
case TAP_DOWN_STASHED:
TRACE_EVENT0("browser",
"TapSuppressionController::TapDownTimerExpired");
// When the timer expires, only forward the stashed tap down event, and
// drop other stashed gesture events (show press or long press).
client_->ForwardStashedTapDown();
state_ = SUPPRESSING_TAPS;
break;
}
}
} // namespace content