Commit 57366e25 authored by Ryan Daum's avatar Ryan Daum Committed by Commit Bot

[chromecast] Make triple tap detector detect doubles, too.

A single detector for both multiple and triple tap events on cast, as
part of the effort so that the triple tap for the magnification
gesture and the double tap inside the TouchExplorationController do
not conflict.

Bug: internal b/112634029
Test: manual and unit test

Change-Id: Ifd23bbc40eb9cfd710acce7ca387eaae137c7ba1
Reviewed-on: https://chromium-review.googlesource.com/1178640Reviewed-by: default avatarAlex Sakhartchouk <alexst@chromium.org>
Commit-Queue: Ryan Daum <rdaum@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584080}
parent 5b0fc0fb
......@@ -30,7 +30,8 @@ AccessibilityManager::AccessibilityManager(
root_window, activation_client,
accessibility_focus_ring_controller_.get(), &accessibility_sound_proxy_,
window_manager->GetGestureHandler());
triple_tap_detector_ = std::make_unique<TripleTapDetector>(root_window, this);
magnify_gesture_detector_ =
std::make_unique<MultipleTapDetector>(root_window, this);
magnification_controller_ =
std::make_unique<FullscreenMagnificationController>(
root_window, window_manager->GetGestureHandler());
......@@ -99,7 +100,7 @@ aura::WindowTreeHost* AccessibilityManager::window_tree_host() const {
void AccessibilityManager::SetMagnificationGestureEnabled(
bool gesture_enabled) {
triple_tap_detector_->set_enabled(gesture_enabled);
magnify_gesture_detector_->set_enabled(gesture_enabled);
// If the gesture is not enabled, make sure that magnification is turned off,
// in case we're already in magnification. Otherwise the user will be stuck in
......@@ -110,7 +111,7 @@ void AccessibilityManager::SetMagnificationGestureEnabled(
}
bool AccessibilityManager::IsMagnificationGestureEnabled() const {
return triple_tap_detector_->enabled();
return magnify_gesture_detector_->enabled();
}
void AccessibilityManager::OnTripleTap(const gfx::Point& tap_location) {
......
......@@ -11,7 +11,7 @@
#include "chromecast/browser/accessibility/accessibility_sound_proxy.h"
#include "chromecast/browser/accessibility/touch_exploration_manager.h"
#include "chromecast/graphics/accessibility/accessibility_focus_ring_controller.h"
#include "chromecast/graphics/gestures/triple_tap_detector.h"
#include "chromecast/graphics/gestures/multiple_tap_detector.h"
namespace aura {
class WindowTreeHost;
......@@ -27,7 +27,7 @@ namespace shell {
// Responsible for delegating chromecast browser process accessibility functions
// to the responsible party.
class AccessibilityManager : public TripleTapDetectorDelegate {
class AccessibilityManager : public MultipleTapDetectorDelegate {
public:
explicit AccessibilityManager(CastWindowManagerAura* window_manager);
~AccessibilityManager() override;
......@@ -73,7 +73,7 @@ class AccessibilityManager : public TripleTapDetectorDelegate {
// Returns whether the magnification gesture is currently enabled.
bool IsMagnificationGestureEnabled() const;
// TripleTapDetectorDelegate implementation
// MultipleTapDetectorDelegate implementation
void OnTripleTap(const gfx::Point& tap_location) override;
// Sets the player for earcons.
......@@ -87,7 +87,7 @@ class AccessibilityManager : public TripleTapDetectorDelegate {
std::unique_ptr<AccessibilityFocusRingController>
accessibility_focus_ring_controller_;
std::unique_ptr<TouchExplorationManager> touch_exploration_manager_;
std::unique_ptr<TripleTapDetector> triple_tap_detector_;
std::unique_ptr<MultipleTapDetector> magnify_gesture_detector_;
std::unique_ptr<MagnificationController> magnification_controller_;
AccessibilitySoundProxy accessibility_sound_proxy_;
......
......@@ -50,10 +50,10 @@ cast_source_set("graphics") {
"gestures/cast_system_gesture_dispatcher.h",
"gestures/cast_system_gesture_event_handler.cc",
"gestures/cast_system_gesture_event_handler.h",
"gestures/multiple_tap_detector.cc",
"gestures/multiple_tap_detector.h",
"gestures/side_swipe_detector.cc",
"gestures/side_swipe_detector.h",
"gestures/triple_tap_detector.cc",
"gestures/triple_tap_detector.h",
]
deps += [
......@@ -116,8 +116,8 @@ if (use_aura && !is_cast_audio_only) {
"cast_focus_client_aura_test.cc",
"cast_views_test.cc",
"cast_window_manager_aura_test.cc",
"gestures/multiple_tap_detector_test.cc",
"gestures/side_swipe_detector_test.cc",
"gestures/triple_tap_detector_test.cc",
"run_all_unittests.cc",
]
deps = [
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/graphics/gestures/triple_tap_detector.h"
#include "chromecast/graphics/gestures/multiple_tap_detector.h"
#include "base/auto_reset.h"
#include "base/logging.h"
......@@ -15,21 +15,39 @@
namespace chromecast {
TripleTapDetector::TripleTapDetector(aura::Window* root_window,
TripleTapDetectorDelegate* delegate)
namespace {
std::unique_ptr<ui::TouchEvent> MakeCancelEvent(
const base::TimeTicks& time_stamp,
const ui::TouchEvent& stashed_press) {
std::unique_ptr<ui::TouchEvent> rewritten_touch_event =
std::make_unique<ui::TouchEvent>(ui::ET_TOUCH_CANCELLED, gfx::Point(),
time_stamp,
stashed_press.pointer_details());
rewritten_touch_event->set_location_f(stashed_press.location_f());
rewritten_touch_event->set_root_location_f(stashed_press.root_location_f());
rewritten_touch_event->set_flags(stashed_press.flags());
return rewritten_touch_event;
}
} // namespace
MultipleTapDetector::MultipleTapDetector(aura::Window* root_window,
MultipleTapDetectorDelegate* delegate)
: root_window_(root_window),
delegate_(delegate),
enabled_(false),
triple_tap_state_(TripleTapState::NONE),
tap_state_(MultiTapState::NONE),
tap_count_(0) {
root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
}
TripleTapDetector::~TripleTapDetector() {
MultipleTapDetector::~MultipleTapDetector() {
root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
}
ui::EventRewriteStatus TripleTapDetector::RewriteEvent(
ui::EventRewriteStatus MultipleTapDetector::RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* rewritten_event) {
if (!enabled_ || !delegate_ || !event.IsTouchEvent()) {
......@@ -39,35 +57,35 @@ ui::EventRewriteStatus TripleTapDetector::RewriteEvent(
const ui::TouchEvent& touch_event = static_cast<const ui::TouchEvent&>(event);
if (event.type() == ui::ET_TOUCH_PRESSED) {
// If a press happened again before the minimum inter-tap interval, cancel
// the triple tap.
if (triple_tap_state_ == TripleTapState::INTERVAL_WAIT &&
// the detection.
if (tap_state_ == MultiTapState::INTERVAL_WAIT &&
(event.time_stamp() - stashed_events_.back().time_stamp()) <
gesture_detector_config_.double_tap_min_time) {
stashed_events_.clear();
TripleTapReset();
TapDetectorStateReset();
return ui::EVENT_REWRITE_CONTINUE;
}
// If the user moved too far from the last tap position, it's not a triple
// If the user moved too far from the last tap position, it's not a multi
// tap.
if (tap_count_) {
float distance = (touch_event.location() - last_tap_location_).Length();
if (distance > gesture_detector_config_.double_tap_slop) {
TripleTapReset();
TapDetectorStateReset();
stashed_events_.clear();
return ui::EVENT_REWRITE_CONTINUE;
}
}
// Otherwise transition into a touched state.
triple_tap_state_ = TripleTapState::TOUCH;
tap_state_ = MultiTapState::TOUCH;
last_tap_location_ = touch_event.location();
// If this is pressed too long, it should be treated as a long-press, and
// not part of a triple-tap, so set a timer to detect that.
triple_tap_timer_.Start(FROM_HERE,
gesture_detector_config_.longpress_timeout, this,
&TripleTapDetector::OnLongPressIntervalTimerFired);
triple_tap_timer_.Start(
FROM_HERE, gesture_detector_config_.longpress_timeout, this,
&MultipleTapDetector::OnLongPressIntervalTimerFired);
// If we've already gotten one tap, discard this event, only the original
// tap needs to get through.
......@@ -76,7 +94,7 @@ ui::EventRewriteStatus TripleTapDetector::RewriteEvent(
}
// Copy the event so we can issue a cancel for it later if this turns out to
// be a triple-tap.
// be a multi-tap.
stashed_events_.push_back(touch_event);
return ui::EVENT_REWRITE_CONTINUE;
......@@ -84,29 +102,21 @@ ui::EventRewriteStatus TripleTapDetector::RewriteEvent(
// Finger was released while we were waiting for one, count it as a tap.
if (touch_event.type() == ui::ET_TOUCH_RELEASED &&
triple_tap_state_ == TripleTapState::TOUCH) {
triple_tap_state_ = TripleTapState::INTERVAL_WAIT;
tap_state_ == MultiTapState::TOUCH) {
tap_state_ = MultiTapState::INTERVAL_WAIT;
triple_tap_timer_.Start(FROM_HERE,
gesture_detector_config_.double_tap_timeout, this,
&TripleTapDetector::OnTripleTapIntervalTimerFired);
&MultipleTapDetector::OnTapIntervalTimerFired);
tap_count_++;
if (tap_count_ == 3) {
TripleTapReset();
TapDetectorStateReset();
delegate_->OnTripleTap(touch_event.location());
// Start issuing cancel events for old presses.
DCHECK(!stashed_events_.empty());
const auto& stashed_press = stashed_events_.front();
std::unique_ptr<ui::TouchEvent> rewritten_touch_event =
std::make_unique<ui::TouchEvent>(ui::ET_TOUCH_CANCELLED, gfx::Point(),
touch_event.time_stamp(),
stashed_press.pointer_details());
rewritten_touch_event->set_location_f(stashed_press.location_f());
rewritten_touch_event->set_root_location_f(
stashed_press.root_location_f());
rewritten_touch_event->set_flags(stashed_press.flags());
*rewritten_event = std::move(rewritten_touch_event);
*rewritten_event =
MakeCancelEvent(touch_event.time_stamp(), stashed_events_.front());
stashed_events_.pop_front();
if (!stashed_events_.empty())
......@@ -121,18 +131,11 @@ ui::EventRewriteStatus TripleTapDetector::RewriteEvent(
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus TripleTapDetector::NextDispatchEvent(
ui::EventRewriteStatus MultipleTapDetector::NextDispatchEvent(
const ui::Event& last_event,
std::unique_ptr<ui::Event>* new_event) {
const auto& stashed_press = stashed_events_.front();
std::unique_ptr<ui::TouchEvent> rewritten_touch_event =
std::make_unique<ui::TouchEvent>(ui::ET_TOUCH_CANCELLED, gfx::Point(),
last_event.time_stamp(),
stashed_press.pointer_details());
rewritten_touch_event->set_location_f(stashed_press.location_f());
rewritten_touch_event->set_root_location_f(stashed_press.root_location_f());
rewritten_touch_event->set_flags(stashed_press.flags());
*new_event = std::move(rewritten_touch_event);
*new_event =
MakeCancelEvent(last_event.time_stamp(), stashed_events_.front());
stashed_events_.pop_front();
if (!stashed_events_.empty())
......@@ -141,20 +144,43 @@ ui::EventRewriteStatus TripleTapDetector::NextDispatchEvent(
return ui::EVENT_REWRITE_DISCARD;
}
void TripleTapDetector::OnTripleTapIntervalTimerFired() {
TripleTapReset();
void MultipleTapDetector::OnTapIntervalTimerFired() {
// We didn't quite reach a third tap, but a second was reached.
// So call out the double-tap.
if (tap_count_ == 2) {
delegate_->OnDoubleTap(last_tap_location_);
// Unfortunately we cannot NextDispatchEvent to issue a cancel on the second
// tap, so we have to manually DispatchEvent to the EventSource.
// Subsequent EventRewriters in the chain will not see the event.
if (!stashed_events_.empty()) {
std::unique_ptr<ui::TouchEvent> cancel_event =
MakeCancelEvent(base::TimeTicks::Now(), stashed_events_.front());
DispatchEvent(cancel_event.get());
}
}
TapDetectorStateReset();
stashed_events_.clear();
}
void TripleTapDetector::OnLongPressIntervalTimerFired() {
TripleTapReset();
void MultipleTapDetector::OnLongPressIntervalTimerFired() {
TapDetectorStateReset();
stashed_events_.clear();
}
void TripleTapDetector::TripleTapReset() {
triple_tap_state_ = TripleTapState::NONE;
void MultipleTapDetector::TapDetectorStateReset() {
tap_state_ = MultiTapState::NONE;
tap_count_ = 0;
triple_tap_timer_.Stop();
}
void MultipleTapDetector::DispatchEvent(ui::TouchEvent* event) {
// Turn off triple-tap before re-dispatching to avoid infinite recursion into
// this detector.
base::AutoReset<bool> toggle_enable(&enabled_, false);
DCHECK(!root_window_->GetHost()
->dispatcher()
->OnEventFromSource(event)
.dispatcher_destroyed);
}
} // namespace chromecast
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_GRAPHICS_GESTURES_TRIPLE_TAP_DETECTOR_H_
#define CHROMECAST_GRAPHICS_GESTURES_TRIPLE_TAP_DETECTOR_H_
#ifndef CHROMECAST_GRAPHICS_GESTURES_MULTIPLE_TAP_DETECTOR_H_
#define CHROMECAST_GRAPHICS_GESTURES_MULTIPLE_TAP_DETECTOR_H_
#include <deque>
......@@ -28,25 +28,26 @@ class TouchEvent;
namespace chromecast {
class TripleTapDetectorDelegate {
class MultipleTapDetectorDelegate {
public:
virtual ~TripleTapDetectorDelegate() = default;
virtual ~MultipleTapDetectorDelegate() = default;
virtual void OnTripleTap(const gfx::Point& touch_location) = 0;
virtual void OnDoubleTap(const gfx::Point& touch_location) {}
};
enum class TripleTapState {
enum class MultiTapState {
NONE,
TOUCH,
INTERVAL_WAIT,
};
// An event rewriter responsible for detecting triple-tap events on the root
// window.
class TripleTapDetector : public ui::EventRewriter {
// An event rewriter responsible for detecting triple-tap or double-tap events
// on the root window.
class MultipleTapDetector : public ui::EventRewriter {
public:
TripleTapDetector(aura::Window* root_window,
TripleTapDetectorDelegate* delegate);
~TripleTapDetector() override;
MultipleTapDetector(aura::Window* root_window,
MultipleTapDetectorDelegate* delegate);
~MultipleTapDetector() override;
void set_enabled(bool enabled) { enabled_ = enabled; }
bool enabled() const { return enabled_; }
......@@ -60,33 +61,34 @@ class TripleTapDetector : public ui::EventRewriter {
std::unique_ptr<ui::Event>* new_event) override;
private:
friend class TripleTapDetectorTest;
friend class MultipleTapDetectorTest;
// Expiration event for maximum time between taps in a triple tap.
void OnTripleTapIntervalTimerFired();
// Expiration event for a finger that is pressed too long during a triple tap.
// Expiration event for maximum time between taps in a tap.
void OnTapIntervalTimerFired();
// Expiration event for a finger that is pressed too long during a multi tap.
void OnLongPressIntervalTimerFired();
void TapDetectorStateReset();
void TripleTapReset();
void DispatchEvent(ui::TouchEvent* event);
// A default gesture detector config, so we can share the same
// timeout and pixel slop constants.
ui::GestureDetector::Config gesture_detector_config_;
aura::Window* root_window_;
TripleTapDetectorDelegate* delegate_;
MultipleTapDetectorDelegate* delegate_;
bool enabled_;
TripleTapState triple_tap_state_;
MultiTapState tap_state_;
int tap_count_;
gfx::Point last_tap_location_;
base::OneShotTimer triple_tap_timer_;
std::deque<ui::TouchEvent> stashed_events_;
DISALLOW_COPY_AND_ASSIGN(TripleTapDetector);
DISALLOW_COPY_AND_ASSIGN(MultipleTapDetector);
};
} // namespace chromecast
#endif // CHROMECAST_GRAPHICS_GESTURES_TRIPLE_TAP_DETECTOR_H_
#endif // CHROMECAST_GRAPHICS_GESTURES_MULTIPLE_TAP_DETECTOR_H_
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/graphics/gestures/triple_tap_detector.h"
#include "chromecast/graphics/gestures/multiple_tap_detector.h"
#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
......@@ -26,15 +26,16 @@ constexpr gfx::Point kTestTapLocation(50, 50);
constexpr int kTapLengthMs = 50;
} // namespace
class MockTripleTapDetectorDelegate : public TripleTapDetectorDelegate {
class MockMultipleTapDetectorDelegate : public MultipleTapDetectorDelegate {
public:
~MockTripleTapDetectorDelegate() override = default;
~MockMultipleTapDetectorDelegate() override = default;
MOCK_METHOD1(OnTripleTap, void(const gfx::Point&));
MOCK_METHOD1(OnDoubleTap, void(const gfx::Point&));
};
class TripleTapDetectorTest : public aura::test::AuraTestBase {
class MultipleTapDetectorTest : public aura::test::AuraTestBase {
public:
~TripleTapDetectorTest() override = default;
~MultipleTapDetectorTest() override = default;
void SetUp() override {
aura::test::AuraTestBase::SetUp();
......@@ -43,8 +44,8 @@ class TripleTapDetectorTest : public aura::test::AuraTestBase {
aura::client::SetScreenPositionClient(root_window(),
screen_position_client_.get());
triple_tap_delegate_ = std::make_unique<MockTripleTapDetectorDelegate>();
triple_tap_detector_ = std::make_unique<TripleTapDetector>(
triple_tap_delegate_ = std::make_unique<MockMultipleTapDetectorDelegate>();
triple_tap_detector_ = std::make_unique<MultipleTapDetector>(
root_window(), triple_tap_delegate_.get());
generator_.reset(new ui::test::EventGenerator(root_window()));
......@@ -70,7 +71,7 @@ class TripleTapDetectorTest : public aura::test::AuraTestBase {
simulated_clock_.Advance(gesture_detector_config_.double_tap_timeout);
simulated_clock_.Advance(base::TimeDelta::FromMilliseconds(1));
triple_tap_detector_->triple_tap_timer_.Stop();
triple_tap_detector_->OnTripleTapIntervalTimerFired();
triple_tap_detector_->OnTapIntervalTimerFired();
}
// Simulate a tap event.
......@@ -90,23 +91,25 @@ class TripleTapDetectorTest : public aura::test::AuraTestBase {
void Tap() { Tap(kTestTapLocation); }
TripleTapDetector& detector() { return *triple_tap_detector_; }
MockTripleTapDetectorDelegate& delegate() { return *triple_tap_delegate_; }
MultipleTapDetector& detector() { return *triple_tap_detector_; }
MockMultipleTapDetectorDelegate& delegate() { return *triple_tap_delegate_; }
private:
ui::GestureDetector::Config gesture_detector_config_;
std::unique_ptr<aura::client::ScreenPositionClient> screen_position_client_;
std::unique_ptr<TripleTapDetector> triple_tap_detector_;
std::unique_ptr<MockTripleTapDetectorDelegate> triple_tap_delegate_;
std::unique_ptr<MultipleTapDetector> triple_tap_detector_;
std::unique_ptr<MockMultipleTapDetectorDelegate> triple_tap_delegate_;
base::SimpleTestTickClock simulated_clock_;
std::unique_ptr<ui::test::EventGenerator> generator_;
};
// Verify that a simple correct triple tap triggers the delegate.
TEST_F(TripleTapDetectorTest, TripleTap) {
EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation)));
TEST_F(MultipleTapDetectorTest, TripleTap) {
EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(1);
;
EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(0);
detector().set_enabled(true);
......@@ -120,10 +123,27 @@ TEST_F(TripleTapDetectorTest, TripleTap) {
run_loop.RunUntilIdle();
}
// Verify that no triple taps are detected when the detector is not enabled.
TEST_F(TripleTapDetectorTest, InactiveNoTriple) {
// Verify double tap when there's two taps then a pause past our double tap
// interval.
TEST_F(MultipleTapDetectorTest, DoubleTap) {
EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(1);
detector().set_enabled(true);
Tap();
DoubleTapPause();
Tap();
TooLongPause();
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
// Verify that no multi taps are detected when the detector is not enabled.
TEST_F(MultipleTapDetectorTest, Inactive) {
EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(0);
Tap();
DoubleTapPause();
Tap();
......@@ -134,10 +154,11 @@ TEST_F(TripleTapDetectorTest, InactiveNoTriple) {
run_loop.RunUntilIdle();
}
// Verify it's not a triple tap if the pause from the first tap to the second
// tap is too long.
TEST_F(TripleTapDetectorTest, FirstTapTooLong) {
// Verify it's not a multi tap of any kind if the pause from the first tap to
// the second tap is too long.
TEST_F(MultipleTapDetectorTest, FirstTapTooLong) {
EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(0);
detector().set_enabled(true);
......@@ -151,10 +172,11 @@ TEST_F(TripleTapDetectorTest, FirstTapTooLong) {
run_loop.RunUntilIdle();
}
// Verify it's not a triple tap if the pause from the second tap to the last tap
// is too long.
TEST_F(TripleTapDetectorTest, LastTapTooLong) {
// Verify it's not a triple tap but instead a doubletap if the pause from the
// second tap to the last tap is too long.
TEST_F(MultipleTapDetectorTest, LastTapTooLongIsDoubleTap) {
EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(1);
detector().set_enabled(true);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment