| // 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. |
| |
| #include "src/ui/a11y/lib/gesture_manager/recognizers/swipe_recognizer_base.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <utility> |
| |
| #include "src/ui/a11y/lib/gesture_manager/arena/recognizer.h" |
| #include "src/ui/a11y/lib/gesture_manager/gesture_util/util.h" |
| |
| namespace a11y { |
| |
| struct SwipeRecognizerBase::Contest { |
| explicit Contest(std::unique_ptr<ContestMember> contest_member) |
| : member(std::move(contest_member)), hold_timeout(member.get()) {} |
| |
| std::unique_ptr<ContestMember> member; |
| // Indicates that a down event has been detected. |
| bool in_progress = false; |
| // Async task used to schedule hold timeout. |
| async::TaskClosureMethod<ContestMember, &ContestMember::Reject> hold_timeout; |
| }; |
| |
| SwipeRecognizerBase::SwipeRecognizerBase(SwipeGestureCallback callback, uint32_t number_of_fingers, |
| zx::duration swipe_gesture_timeout, |
| const std::string& debug_name) |
| : swipe_gesture_callback_(std::move(callback)), |
| swipe_gesture_timeout_(swipe_gesture_timeout), |
| number_of_fingers_(number_of_fingers), |
| debug_name_(debug_name) {} |
| |
| SwipeRecognizerBase::~SwipeRecognizerBase() = default; |
| |
| void SwipeRecognizerBase::HandleEvent( |
| const fuchsia::ui::input::accessibility::PointerEvent& pointer_event) { |
| FX_DCHECK(contest_); |
| |
| if (!pointer_event.has_phase()) { |
| FX_LOGS(INFO) << DebugName() << ": Pointer event is missing phase information."; |
| return; |
| } |
| |
| GestureInfo gesture_info; |
| const auto& pointer_id = pointer_event.pointer_id(); |
| switch (pointer_event.phase()) { |
| case fuchsia::ui::input::PointerEventPhase::DOWN: |
| // Check that Up event is not detected before any Down event. |
| if (number_of_up_event_detected_) { |
| FX_LOGS(INFO) << DebugName() |
| << ": Down Event detected after 'Up' event. Dropping current event."; |
| contest_->member->Reject(); |
| break; |
| } |
| |
| if (!InitGestureInfo(pointer_event, &gesture_info, &gesture_context_)) { |
| FX_LOGS(INFO) << DebugName() |
| << ": Pointer Event is missing required fields. Dropping current event."; |
| contest_->member->Reject(); |
| break; |
| } |
| |
| // For the first down event, make sure the contest is not in progress. For subsequent down |
| // events, contest should be in progress. |
| if (gesture_info_map_.empty() == contest_->in_progress) { |
| FX_LOGS(INFO) << DebugName() << ": Failed because contest was already in progress."; |
| contest_->member->Reject(); |
| break; |
| } |
| |
| if (!ValidatePointerEvent(gesture_info, pointer_event)) { |
| FX_LOGS(INFO) << DebugName() << ": Failed because of pointer event validation."; |
| contest_->member->Reject(); |
| break; |
| } |
| |
| gesture_info_map_[pointer_id] = std::move(gesture_info); |
| |
| if (gesture_info_map_.size() > number_of_fingers_) { |
| FX_LOGS(INFO) << DebugName() |
| << ": More fingers detected than expected. Dropping current event."; |
| contest_->member->Reject(); |
| } else if (gesture_info_map_.size() == 1) { |
| // Schedule a task to declare defeat with a timeout equal to swipe_gesture_timeout_. |
| contest_->hold_timeout.PostDelayed(async_get_default_dispatcher(), swipe_gesture_timeout_); |
| contest_->in_progress = true; |
| } |
| UpdateLastPointerPosition(pointer_id, pointer_event); |
| |
| break; |
| case fuchsia::ui::input::PointerEventPhase::MOVE: { |
| FX_DCHECK(contest_->in_progress) |
| << "Pointer MOVE event received without preceding DOWN event."; |
| |
| // Check that gesture info for the pointer_id exists and the pointer event is valid. |
| auto it = gesture_info_map_.find(pointer_id); |
| if ((it == gesture_info_map_.end()) || (!ValidatePointerEvent(it->second, pointer_event))) { |
| contest_->member->Reject(); |
| break; |
| } |
| |
| // Check that fingers are moving in the direction of swipe recognizer only when all the |
| // fingers are detected, there is no up event seen so far and length of swipe so far is longer |
| // than kMinSwipeDistance. |
| if ((gesture_info_map_.size() == number_of_fingers_) && !number_of_up_event_detected_) { |
| if (MinSwipeLengthAchieved(pointer_id, pointer_event) && |
| !ValidateSwipePath(pointer_id, pointer_event)) { |
| contest_->member->Reject(); |
| break; |
| } |
| } |
| |
| UpdateLastPointerPosition(pointer_id, pointer_event); |
| break; |
| } |
| case fuchsia::ui::input::PointerEventPhase::UP: { |
| FX_DCHECK(contest_->in_progress) << "Pointer UP event received without preceding DOWN event."; |
| number_of_up_event_detected_++; |
| |
| // Check if the all the Down events are detected. |
| if (gesture_info_map_.size() != number_of_fingers_) { |
| contest_->member->Reject(); |
| break; |
| } |
| |
| // Validate pointer events. |
| auto it = gesture_info_map_.find(pointer_id); |
| if (!(ValidatePointerEvent(it->second, pointer_event) && |
| ValidateSwipePath(pointer_id, pointer_event) && |
| ValidateSwipeDistance(pointer_id, pointer_event))) { |
| contest_->member->Reject(); |
| break; |
| } |
| |
| // If all the Up events are detected then call Accept. |
| if (number_of_up_event_detected_ == number_of_fingers_) { |
| contest_->member->Accept(); |
| contest_.reset(); |
| } |
| |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void SwipeRecognizerBase::OnWin() { swipe_gesture_callback_(gesture_context_); } |
| |
| void SwipeRecognizerBase::OnDefeat() { contest_.reset(); } |
| |
| bool SwipeRecognizerBase::ValidateSwipePath( |
| uint32_t pointer_id, |
| const fuchsia::ui::input::accessibility::PointerEvent& pointer_event) const { |
| // Verify that slope of line containing gesture start point and current pointer event location |
| // falls within a pre-specified range. |
| auto it = gesture_info_map_.find(pointer_id); |
| if (it == gesture_info_map_.end()) { |
| return false; |
| } |
| auto dx = pointer_event.ndc_point().x - it->second.starting_ndc_position.x; |
| auto dy = pointer_event.ndc_point().y - it->second.starting_ndc_position.y; |
| |
| return SwipeHasValidSlopeAndDirection(dx, dy); |
| } |
| |
| bool SwipeRecognizerBase::ValidateSwipeDistance( |
| uint32_t pointer_id, |
| const fuchsia::ui::input::accessibility::PointerEvent& pointer_event) const { |
| // Check if the distance between the pointer event and the start point is within the allowable |
| // range. |
| auto it = gesture_info_map_.find(pointer_id); |
| if (it == gesture_info_map_.end()) { |
| return false; |
| } |
| float dx = pointer_event.ndc_point().x - it->second.starting_ndc_position.x; |
| float dy = pointer_event.ndc_point().y - it->second.starting_ndc_position.y; |
| |
| float d2 = dx * dx + dy * dy; |
| |
| return d2 >= kMinSwipeDistance * kMinSwipeDistance && d2 <= kMaxSwipeDistance * kMaxSwipeDistance; |
| } |
| |
| bool SwipeRecognizerBase::MinSwipeLengthAchieved( |
| uint32_t pointer_id, |
| const fuchsia::ui::input::accessibility::PointerEvent& pointer_event) const { |
| auto it = gesture_info_map_.find(pointer_id); |
| if (it == gesture_info_map_.end()) { |
| return false; |
| } |
| float dx = pointer_event.ndc_point().x - it->second.starting_ndc_position.x; |
| float dy = pointer_event.ndc_point().y - it->second.starting_ndc_position.y; |
| |
| float d2 = dx * dx + dy * dy; |
| return d2 >= kMinSwipeDistance * kMinSwipeDistance; |
| } |
| |
| void SwipeRecognizerBase::OnContestStarted(std::unique_ptr<ContestMember> contest_member) { |
| ResetGestureContext(&gesture_context_); |
| contest_ = std::make_unique<Contest>(std::move(contest_member)); |
| number_of_up_event_detected_ = 0; |
| gesture_info_map_.clear(); |
| stopping_position_.clear(); |
| } |
| |
| void SwipeRecognizerBase::UpdateLastPointerPosition( |
| uint32_t pointer_id, const fuchsia::ui::input::accessibility::PointerEvent& pointer_event) { |
| GestureInfo gesture_info; |
| GestureContext dummy_context; |
| if (!InitGestureInfo(pointer_event, &gesture_info, &dummy_context)) { |
| FX_LOGS(INFO) << DebugName() |
| << ": Pointer Event is missing required fields. Dropping current event."; |
| contest_->member->Reject(); |
| return; |
| } |
| stopping_position_[pointer_id] = std::move(gesture_info); |
| } |
| |
| } // namespace a11y |