blob: 93709b47154041b6f8b19e3921a3a6e0988d54c3 [file] [log] [blame]
// Copyright 2018 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 GARNET_BIN_A11Y_TALKBACK_GESTURE_DETECTOR_H_
#define GARNET_BIN_A11Y_TALKBACK_GESTURE_DETECTOR_H_
#include <fuchsia/accessibility/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/viewsv1/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include "garnet/bin/a11y/talkback/gesture_listener.h"
#include "lib/component/cpp/startup_context.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/macros.h"
namespace talkback {
// Max duration between a touch up and a second touch down to register
// a double tap. Time in milliseconds.
constexpr zx::duration kTapDelay = zx::msec(120);
// Duration to wait between the first touch down and first touch up
// to distinguish between a short tap and long press. Time in nanoseconds.
constexpr uint64_t kLongPressDelay = zx::msec(120).to_nsecs();
// Duration to wait after registering a touch move event during a finger move
// or long press before registering another touch move event. This is done
// to limit the rate of move calls made while a finger is held on the screen.
// Time in nanoseconds.
constexpr uint64_t kMoveCallDelay = zx::msec(17).to_nsecs();
// Talkback gesture detector that takes in raw inputs to process.
// The gestures it detects:
// - Single tap - a11y focus set on element touched.
// - One finger slide - a11y focus set to where currently touching.
// - Double tap - Apply a11y tap action to current a11y focused node.
// - Two finger slide - Simulate full touch events for finger #1
// that is on the screen.
class GestureDetector {
public:
explicit GestureDetector(GestureListener* listener);
~GestureDetector() = default;
private:
enum class State {
kIdle = 0, // No fingers on screen
kFirstTouchDown = 1, // Tap started
kFirstTouchUp = 2, // Full tap finished
kSecondTouchDown = 4, // Double tap started
kTwoFingersDown = 5, // Two fingers are on the screen
};
// Fidl event listener functions for fuchsia::accessibility::InputDispatcher.
// Handle the input event depending on the current state of
// the gesture detector. We break up the handler for each state
// into individual functions below.
void OnInputEvent(fuchsia::ui::input::PointerEvent event);
// Resets the state to idle once a new presentation is displayed; input
// from previous presentations should not extend into new ones. Also stores
// the current ViewTreeToken to have a reference of which view tree to
// perform hit tests.
void OnPresentationChangedEvent(fuchsia::ui::viewsv1::ViewTreeToken token);
// Executed |kTapDelay| ms after a touch up. If a new touch down did not
// appear in this time period, we treat this as a single tap. If a new touch
// down does appear, then we treat this event as a double-tap instead of a
// single-tap.
// After a single tap, the item under finger on touch up gains
// accessibility focus and the state is returned to |kIdle|.
void AfterTapDelay();
// Called when state is |kIdle|.
// If a finger touches down, the state moves to |kFirstTouchDown|.
void FromIdle(fuchsia::ui::input::PointerEvent event);
// Called when state is |kFirstTouchDown|.
// While finger #1 is down, we set accessibility focus at the current
// position after |kLongPressDelay| ms after the touch down event. The delay
// is added to keep in line with the delay before checking for a tap. This
// provides functionality to explore UI with a finger moving around the
// screen without lifting up.
//
// If finger #1 is lifted:
// We return to |kIdle| if it happened |kLongPressDelay| ms after touching
// down (We do not treat long presses as taps).
// Otherwise, state is set to |kFirstTouchDown|, to continue checking
// for a double tap. We also launch an AfterTapDelay call We use delayed
// async call to AfterTapDelay to detect single taps if the delay is too long.
// This is necessary because taps can lead to a second touch down quickly
// afterwards if the user wishes to double tap.
//
// If finger #2 touches the screen, we move to |kTwoFingersDown| and
// simulate a touch down event. The position of finger #1 on the
// screen is always used for simulation.
void FromFirstTouchDown(fuchsia::ui::input::PointerEvent event);
// Called when state is |kFirstTouchUp|.
// If a finger touches down:
// If the delay after the first tap was too long, return to |kIdle|.
// Otherwise, move to |kSecondTouchDown|, to signal the start of a double tap.
void FromFirstTouchUp(fuchsia::ui::input::PointerEvent event);
// Called when state is |kSecondTouchDown|.
// On the second tap up, perform an a11y tap on the currently a11y focused
// node. Return to |kIdle| afterwards.
void FromSecondTouchDown(fuchsia::ui::input::PointerEvent event);
// Called when state is |kTwoFingersDown|.
// If finger #1 on the screen moves, we return the move event to
// the regular input pipeline to simulate it.
// When finger #1 on the screen is lifted first, we simulate a
// touch up event, set finger #2 on the screen to be the first
// one, and go back to |kFirstTouchDown|.
//
// If finger #2 on the screen is lifted first, we simulate a touch
// up event, and only return to |kFirstTouchDown|.
void FromTwoFingersDown(fuchsia::ui::input::PointerEvent event);
// Sets the current state to |kIdle|. If the current state was
// |kTwoFingersDown|, we send a simulated cancel since a simulated
// touch down has not finished in this phase.
void CancelAndIdle();
// Simulates a touch down event when two fingers are on the screen by
// sending the location of finger #1 that was registered. We
// return two clones of the currently stored |finger1_pointer_event|
// with PointerEventPhase ADD and DOWN in that order.
void SimulateTouchDown();
// Simulates a touch up event when two fingers are on the screen by
// sending the location of finger #1 that was registered. We
// return two clones of the currently stored |finger1_pointer_event|
// with PointerEventPhase UP and REMOVE in that order.
void SimulateTouchUp();
// Simulates a touch cancel event when two fingers are on the screen by
// sending the location of finger #1 that was registered. We
// return a clone of the currently stored |finger1_pointer_event|
// with PointerEventPhase CANCEL.
void SimulateCancel();
GestureListener* listener_;
// The ViewTreeToken for the currently displayed presentation is needed
// to perform hit-tests on views in the current view tree.
fuchsia::ui::viewsv1::ViewTreeToken token_;
// The current state of the gesture detector state machine. Starts in
// the idle position.
State state_ = State::kIdle;
// Pointer id of finger #1 to touch the screen at a time. This is
// necessary to ignore events from other fingers and perform checking when
// finger #2 touches the screen.
uint32_t finger1_pointer_id_;
// Pointer id of finger #2 to touch the screen to detect two finger
// scrolling. This value is only meaningful during the |kTwoFingersDown|
// state, and replaces |finger1_pointer_id_| if finger #1 is lifted first
// during two finger mode.
uint32_t finger2_pointer_id_;
// Last pointer event time for DOWN and UP events used to find time deltas
// to check for tap/double tap/long press delays. Time in nanoseconds.
uint64_t last_pointer_down_or_up_event_;
// Last time for acting upon a MOVE event during a finger drag along the
// screen or a long press.
uint64_t last_move_call_;
// The following two cached pointer events are used to clone simulated
// PointerEvents while two fingers are on the screen.
// The last pointer event received for finger #1 on the screen.
fuchsia::ui::input::PointerEvent finger1_pointer_event_;
// The last pointer event received for finger #2 on the screen.
// Replaces |finger1_pointer_event| if finger #1 is
// lifted first during two finger mode.
fuchsia::ui::input::PointerEvent finger2_pointer_event_;
// Needed to queue up AfterTapDelay.
async_dispatcher_t* const tap_dispatcher_ = nullptr;
};
} // namespace talkback
#endif // GARNET_BIN_A11Y_TALKBACK_GESTURE_DETECTOR_H_