Commit 65983c94 authored by Sean Topping's avatar Sean Topping Committed by Commit Bot

[Chromecast] Send gestures to Root UI after several attempts

In some cases, the main gesture handler might permanently have priority,
and lower priority handlers (such as the root UI) will never receive a
gesture until that handler is removed. This change ensures that if a
user attempts multiple gestures in quick succession, that the root UI
will receive the gesture instead. This mitigates buggy gesture handling
logic and prevents the UI from being permanently stuck.

      then try to swipe several times in succession. Eventually the root
      UI should handle the swipe.

Bug: internal b/112338841
Test: Run an app which is permanently able to handle gesture events,
Change-Id: I741e041feba56b5e87ad8cb3ca5e1f564f48518b
Reviewed-on: https://chromium-review.googlesource.com/1199967
Commit-Queue: Sean Topping <seantopping@chromium.org>
Reviewed-by: default avatarLuke Halliwell <halliwell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589002}
parent 091547fa
......@@ -5,10 +5,21 @@
#include "chromecast/graphics/gestures/cast_system_gesture_dispatcher.h"
#include "base/logging.h"
#include "base/time/default_tick_clock.h"
namespace chromecast {
CastSystemGestureDispatcher::CastSystemGestureDispatcher() {}
namespace {
const base::TimeDelta kExpirationTime = base::TimeDelta::FromSeconds(3);
const size_t kMaxSwipes = 3;
} // namespace
CastSystemGestureDispatcher::CastSystemGestureDispatcher(
const base::TickClock* tick_clock)
: send_gestures_to_root_(false), tick_clock_(tick_clock) {}
CastSystemGestureDispatcher::CastSystemGestureDispatcher()
: CastSystemGestureDispatcher(base::DefaultTickClock::GetInstance()) {}
CastSystemGestureDispatcher::~CastSystemGestureDispatcher() {
DCHECK(gesture_handlers_.empty());
......@@ -42,11 +53,34 @@ void CastSystemGestureDispatcher::HandleSideSwipe(
CastSideSwipeEvent event,
CastSideSwipeOrigin swipe_origin,
const gfx::Point& touch_location) {
// Process previous events and check to see if the user attempted a swipe
// multiple times. This probably indicates that the swipe is not having the
// intended effect in the UI, most likely the highest priority handler is
// consuming the gesture but not taking any action. To prevent the system
// from getting stuck, route new gesture events to the main UI when this
// happens.
base::TimeTicks now = tick_clock_->NowTicks();
if (event == CastSideSwipeEvent::BEGIN) {
recent_events_.push({now, swipe_origin});
// Flush events which are older than the prescribed time.
while (!recent_events_.empty() &&
recent_events_.front().event_time < now - kExpirationTime) {
recent_events_.pop();
}
// If there are too many recent swipes, then this gesture should go to the
// root UI.
send_gestures_to_root_ = recent_events_.size() >= kMaxSwipes;
}
CastGestureHandler* best_handler = nullptr;
Priority highest_priority = Priority::NONE;
// Iterate through all handlers. Pick the handler with the highest priority
// that is capable of handling the swipe event and is not Priority::NONE.
for (auto* gesture_handler : gesture_handlers_) {
if (send_gestures_to_root_ &&
gesture_handler->GetPriority() == Priority::ROOT_UI) {
best_handler = gesture_handler;
break;
}
if (gesture_handler->CanHandleSwipe(swipe_origin) &&
gesture_handler->GetPriority() > highest_priority) {
best_handler = gesture_handler;
......
......@@ -5,7 +5,11 @@
#ifndef CHROMECAST_GRAPHICS_GESTURES_CAST_SYSTEM_GESTURE_DISPATCHER_H_
#define CHROMECAST_GRAPHICS_GESTURES_CAST_SYSTEM_GESTURE_DISPATCHER_H_
#include <queue>
#include "base/containers/flat_set.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "chromecast/graphics/gestures/cast_gesture_handler.h"
namespace chromecast {
......@@ -15,6 +19,7 @@ namespace chromecast {
class CastSystemGestureDispatcher : public CastGestureHandler {
public:
CastSystemGestureDispatcher();
explicit CastSystemGestureDispatcher(const base::TickClock* tick_clock);
~CastSystemGestureDispatcher() override;
......@@ -35,6 +40,15 @@ class CastSystemGestureDispatcher : public CastGestureHandler {
void HandleTapGesture(const gfx::Point& touch_location) override;
private:
// Logs a completed gesture event.
struct GestureEvent {
base::TimeTicks event_time;
CastSideSwipeOrigin origin;
};
std::queue<GestureEvent> recent_events_;
bool send_gestures_to_root_;
const base::TickClock* const tick_clock_;
base::flat_set<CastGestureHandler*> gesture_handlers_;
};
} // namespace chromecast
......
......@@ -6,6 +6,7 @@
#include <memory>
#include "base/test/simple_test_tick_clock.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/point.h"
......@@ -15,6 +16,13 @@ using testing::Return;
namespace chromecast {
namespace {
constexpr base::TimeDelta kTimeoutWindow = base::TimeDelta::FromSeconds(3);
const size_t kMaxSwipesWithinTimeout = 3;
} // namespace
class MockCastGestureHandler : public CastGestureHandler {
public:
~MockCastGestureHandler() override = default;
......@@ -34,13 +42,16 @@ class CastSystemGestureDispatcherTest : public testing::Test {
~CastSystemGestureDispatcherTest() override = default;
void SetUp() override {
gesture_dispatcher_ = std::make_unique<CastSystemGestureDispatcher>();
test_clock_ = std::make_unique<base::SimpleTestTickClock>();
gesture_dispatcher_ =
std::make_unique<CastSystemGestureDispatcher>(test_clock_.get());
}
void TearDown() override { gesture_dispatcher_.reset(); }
protected:
std::unique_ptr<CastSystemGestureDispatcher> gesture_dispatcher_;
std::unique_ptr<base::SimpleTestTickClock> test_clock_;
};
TEST_F(CastSystemGestureDispatcherTest, SingleHandler) {
......@@ -106,6 +117,7 @@ TEST_F(CastSystemGestureDispatcherTest, MultipleHandlersByPriority) {
EXPECT_CALL(handler_2, HandleSideSwipe(event, origin, point));
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
test_clock_->Advance(kTimeoutWindow);
// Test when the higher priority handler can't handle the event. Lower
// priority handler should get it instead.
origin = CastSideSwipeOrigin::BOTTOM;
......@@ -113,12 +125,83 @@ TEST_F(CastSystemGestureDispatcherTest, MultipleHandlersByPriority) {
EXPECT_CALL(handler_1, HandleSideSwipe(event, origin, point));
EXPECT_CALL(handler_2, HandleSideSwipe(_, _, _)).Times(0);
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
test_clock_->Advance(kTimeoutWindow);
// Test when no handlers can handle the event.
ON_CALL(handler_1, CanHandleSwipe(origin)).WillByDefault(Return(false));
EXPECT_CALL(handler_1, HandleSideSwipe(_, _, _)).Times(0);
EXPECT_CALL(handler_2, HandleSideSwipe(_, _, _)).Times(0);
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
test_clock_->Advance(kTimeoutWindow);
gesture_dispatcher_->RemoveGestureHandler(&handler_2);
gesture_dispatcher_->RemoveGestureHandler(&handler_1);
}
TEST_F(CastSystemGestureDispatcherTest, MultipleSwipesToRootUi) {
MockCastGestureHandler handler_1;
MockCastGestureHandler handler_2;
ON_CALL(handler_1, GetPriority())
.WillByDefault(Return(CastGestureHandler::Priority::ROOT_UI));
ON_CALL(handler_2, GetPriority())
.WillByDefault(Return(CastGestureHandler::Priority::MAIN_ACTIVITY));
gesture_dispatcher_->AddGestureHandler(&handler_1);
gesture_dispatcher_->AddGestureHandler(&handler_2);
// Higher priority handler should get the event.
ON_CALL(handler_1, CanHandleSwipe(_)).WillByDefault(Return(true));
ON_CALL(handler_2, CanHandleSwipe(_)).WillByDefault(Return(true));
CastSideSwipeEvent event = CastSideSwipeEvent::BEGIN;
CastSideSwipeOrigin origin = CastSideSwipeOrigin::TOP;
gfx::Point point(0, 0);
// Trigger N - 1 events within the recent events timeout window.
for (size_t i = 0; i < kMaxSwipesWithinTimeout - 1; ++i) {
EXPECT_CALL(handler_1, HandleSideSwipe(_, _, _)).Times(0);
EXPECT_CALL(handler_2, HandleSideSwipe(event, origin, point));
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
base::TimeDelta time_between_events =
(kTimeoutWindow - base::TimeDelta::FromSeconds(1)) /
(kMaxSwipesWithinTimeout - 1);
test_clock_->Advance(time_between_events);
}
// The Nth event will go to the root UI, since there were N BEGIN events that
// happened in rapid succession. This means the main activity is probably not
// handling the events properly, and we should defer to the root UI which is
// assumed to behave properly.
event = CastSideSwipeEvent::BEGIN;
EXPECT_CALL(handler_1, HandleSideSwipe(event, origin, point));
EXPECT_CALL(handler_2, HandleSideSwipe(_, _, _)).Times(0);
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
// CONTINUE events still go to the root UI.
event = CastSideSwipeEvent::CONTINUE;
EXPECT_CALL(handler_1, HandleSideSwipe(event, origin, point));
EXPECT_CALL(handler_2, HandleSideSwipe(_, _, _)).Times(0);
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
// We are now outside the timeout window.
test_clock_->Advance(kTimeoutWindow);
// All events will go to the root UI until the next BEGIN event after the
// 3-event timeout.
event = CastSideSwipeEvent::CONTINUE;
EXPECT_CALL(handler_1, HandleSideSwipe(event, origin, point));
EXPECT_CALL(handler_2, HandleSideSwipe(_, _, _)).Times(0);
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
event = CastSideSwipeEvent::END;
EXPECT_CALL(handler_1, HandleSideSwipe(event, origin, point));
EXPECT_CALL(handler_2, HandleSideSwipe(_, _, _)).Times(0);
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
// Timeout has elapsed, next BEGIN event will go to the main handler.
event = CastSideSwipeEvent::BEGIN;
EXPECT_CALL(handler_1, HandleSideSwipe(_, _, _)).Times(0);
EXPECT_CALL(handler_2, HandleSideSwipe(event, origin, point));
gesture_dispatcher_->HandleSideSwipe(event, origin, point);
gesture_dispatcher_->RemoveGestureHandler(&handler_2);
gesture_dispatcher_->RemoveGestureHandler(&handler_1);
......
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