blob: 657b6cb6d6f09bb65b1df0c1f1afd7517badedea [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_UI_A11Y_LIB_GESTURE_MANAGER_ARENA_GESTURE_ARENA_H_
#define SRC_UI_A11Y_LIB_GESTURE_MANAGER_ARENA_GESTURE_ARENA_H_
#include <fuchsia/ui/input/accessibility/cpp/fidl.h>
#include <list>
#include <map>
#include <set>
#include "src/lib/fxl/memory/weak_ptr.h"
#include "src/ui/a11y/lib/gesture_manager/arena/contest_member.h"
namespace a11y {
// The PointerStreamTracker tracks the life cycle of accessibility pointer streams arriving from the
// OS input system. It can consume or reject tracked pointer streams. Please see
// fuchsia.ui.input.accessibility.EventHandling| for more info on consuming / rejecting pointer
// events.
class PointerStreamTracker {
public:
// Callback signature used to indicate when and how an pointer event sent to the arena was
// processed.
using OnStreamHandledCallback =
fit::function<void(uint32_t device_id, uint32_t pointer_id,
fuchsia::ui::input::accessibility::EventHandling handled)>;
explicit PointerStreamTracker(OnStreamHandledCallback on_stream_handled_callback);
~PointerStreamTracker() = default;
// Resets the handled status for subsequent pointer event streams.
void Reset();
// Rejects all pointer event streams received by the tracker until reset.
void RejectPointerEvents();
// Consumes all pointer event streams received by the tracker until reset.
void ConsumePointerEvents();
// Adds or removes a pointer stream for the given event. For ADD events, also caches the callback
// from the input system to notify it later whether the stream was consumed or rejected.
void OnEvent(const fuchsia::ui::input::accessibility::PointerEvent& pointer_event);
// Returns true if it has any ongoing pointer event streams which are not
// finished yet. A stream is considered finished when it sees and event with
// phase == REMOVE.
bool is_active() const { return !active_streams_.empty(); }
private:
// Used to uniquely identify a pointer event stream. A pair is used rather
// than a structure for a comparable key for std::set.
using StreamID = std::pair</*device_id=*/uint32_t, /*pointer_id=*/uint32_t>;
void InvokePointerEventCallbacks(fuchsia::ui::input::accessibility::EventHandling handled);
// Callback used to notify how each stream was handled.
OnStreamHandledCallback on_stream_handled_callback_;
// Holds how many times the |on_stream_handled_callback_| should be invoked per pointer event
// streams in order to notify the input system whether they were consumed / rejected. A pointer
// event stream is a sequence of pointer events that must start with an ADD phase event and end
// with an REMOVE phase event. Since only one callback call is needed to notify the input system
// per stream, on an ADD event the count is increased.
//
// Note: this is a map holding just a few keys and follows the map type selection guidance
// described at:
// https://chromium.googlesource.com/chromium/src/+/HEAD/base/containers/README.md#map-and-set-selection
std::map<StreamID, uint32_t> pointer_event_callbacks_;
// Holds the streams in progress tracked by the tracker. A stream of pointer events is considered
// to be active when an event with phase ADD was seen, but not an event with phase REMOVE yet.
std::set<StreamID> active_streams_;
std::optional<fuchsia::ui::input::accessibility::EventHandling> handled_;
};
class GestureRecognizer;
// The Gesture Arena for accessibility services.
//
// The Gesture Arena manages several recognizers which are trying to interpret a gesture that is
// being performed. It respects the following rules:
// * Contests begin when a touch pointer is added and continue until every member has either
// claimed a win or declared defeat.
// * Of the members that claim a win, the win is awarded to the highest priority member.
//
// All members must eventually claim a win or declare defeat. Once a member has claimed a win or
// declared defeat, it may not change its declaration.
//
// Recognizers continue to receive incoming pointer events until they release their |ContestMember|
// or are defeated. After the winning recognizer releases its |ContestMember|, the next interaction
// will begin a new contest.
//
// The order in which recognizers are added to the arena determines event dispatch order and win
// priority. When routing pointer events to recognizers, they see the event in order they were
// added. Then they have the chance to claim a win before the next recognizer in the list has the
// chance to act. Then, during resolution, if multiple recognizers claim a win, the one that was
// added first is awarded the win.
//
// In this model, it is important to notice that there are two layers of abstraction:
// 1. Raw pointer events, which come from the input system, arrive at the arena and are dispatched
// to recognizers via a PointerStreamTracker.
// 2. Gestures, which are sequences of pointer events with a semantic meaning, are identified by
// recognizers.
//
// With that in mind, each recognizer defines the semantic meaning for the sequence of pointer
// events that it is receiving. In another words, it is expected that a recognizer could identify a
// single tap, another a double tap, and so on.
//
// Claiming a win indicates that a recognizer identified a gesture. However, the win will not
// necessarily be awarded to that recognizer. Recognizers are free to handle their events
// optimistically, but if they do then they must undo/reset any changes they effect if they are
// eventually defeated.
//
// Recognizers should not destroy the arena.
//
// If any member claims a win, the input system is immediately notified that the pointer event
// streams were consumed (as would be any new pointer event streams until the end of the gesture).
// If no member claims a win, the input system is notified that the pointer event streams were
// rejected.
//
// Implementation notes: this arena is heavily influenced by Fluttter's gesture arena:
// https://flutter.dev/docs/development/ui/advanced/gestures For those familiar how Flutter version
// works, here are the important main differences:
// - The arena here is not per finger (a.k.a. per pointer ID), which means that
// recognizers receive the whole interaction with the screen.
// - There are not default wins or mutiple levels of acceptance. Recognizers must be certain when
// they claim a win.
class GestureArena {
public:
enum class State {
kIdle,
kInProgress,
kWinnerAssigned,
kAllDefeated,
kContestEndedWinnerAssigned,
kContestEndedAllDefeated,
};
// This arena takes |on_stream_handled_callback|, which is called whenever a
// stream of pointer events is handled (e.g., is consumed or rejected).
explicit GestureArena(PointerStreamTracker::OnStreamHandledCallback on_stream_handled_callback =
[](auto...) {});
virtual ~GestureArena() = default;
// Adds a new recognizer to the arena. The new recognizer starts participating in the next
// contest.
void Add(GestureRecognizer* recognizer);
// Dispatches a new pointer event to this arena. This event gets sent to all arena members which
// are active at the moment.
//
// Virtual for testing; overridden by a mock.
virtual void OnEvent(const fuchsia::ui::input::accessibility::PointerEvent& pointer_event);
// Tries to resolve the arena if it is not resolved already.
// It follows two rules:
// * Contests continue until every member has either claimed a win or declared defeat.
// * Of the members that claim a win, the win is awarded to the highest priority member.
// A resolved arena will continue to be so until the winner releases its |ContestMember|, which
// resets the arena for a new contest.
void TryToResolve();
// Get the state of the gesture arena.
//
// Virtual for testing; overridden by a mock.
virtual State GetState();
private:
class ArenaContestMember;
// Tracks the state of a recognizer in an arena and backs the state of a |ContestMember| during a
// contest.
struct ArenaMember {
GestureRecognizer* recognizer;
ContestMember::Status status;
fxl::WeakPtr<ArenaContestMember> contest_member;
};
// Dispatches the pointer event to active arena members and purges inactive contest members.
void DispatchEvent(const fuchsia::ui::input::accessibility::PointerEvent& pointer_event);
// Returns whether there are any contest members wanting events.
bool IsHeld() const;
// Returns true if the arena is not held and the interaction is finished.
bool IsIdle() const;
// Resets the arena and notify members that a new contest has started.
void StartNewContest();
// Informs Scenic of whether input event streams involved in the current contest should be
// consumed or rejected.
void HandleEvents(bool consumed_by_member);
PointerStreamTracker streams_;
std::list<ArenaMember> arena_members_;
size_t undecided_members_ = 0;
fxl::WeakPtrFactory<GestureArena> weak_ptr_factory_;
};
} // namespace a11y
#endif // SRC_UI_A11Y_LIB_GESTURE_MANAGER_ARENA_GESTURE_ARENA_H_