|  | // Copyright 2020 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/screen_reader/linear_navigation_action.h" | 
|  |  | 
|  | #include <lib/fit/bridge.h> | 
|  | #include <lib/fit/scope.h> | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  |  | 
|  | #include <set> | 
|  |  | 
|  | #include "fuchsia/accessibility/semantics/cpp/fidl.h" | 
|  | #include "src/ui/a11y/lib/screen_reader/screen_reader_context.h" | 
|  | #include "src/ui/a11y/lib/screen_reader/util/util.h" | 
|  |  | 
|  | namespace a11y { | 
|  |  | 
|  | LinearNavigationAction::LinearNavigationAction(ActionContext* action_context, | 
|  | ScreenReaderContext* screen_reader_context, | 
|  | LinearNavigationDirection action_type) | 
|  | : ScreenReaderAction(action_context, screen_reader_context), direction_(action_type) {} | 
|  |  | 
|  | LinearNavigationAction::~LinearNavigationAction() = default; | 
|  |  | 
|  | void LinearNavigationAction::Run(ActionData process_data) { | 
|  | auto a11y_focus = screen_reader_context_->GetA11yFocusManager()->GetA11yFocus(); | 
|  | if (!a11y_focus || a11y_focus->view_ref_koid == ZX_KOID_INVALID) { | 
|  | auto* speaker = screen_reader_context_->speaker(); | 
|  | auto promise = speaker->SpeakMessageByIdPromise(fuchsia::intl::l10n::MessageIds::NO_FOCUS_ALERT, | 
|  | {.interrupt = true, .save_utterance = false}); | 
|  | screen_reader_context_->executor()->schedule_task(std::move(promise)); | 
|  | FX_LOGS(INFO) << "Linear Navigation Action: No view is in focus."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | FX_DCHECK(action_context_->semantics_source); | 
|  |  | 
|  | // ************************ Workaround for fxb/64295  ************************ | 
|  | // TODO(fxb/66128): Remove workaround once flutter semantics are fixed. | 
|  | auto nodes_to_exclude = GetNodesToExclude(a11y_focus->view_ref_koid, a11y_focus->node_id, | 
|  | action_context_->semantics_source); | 
|  |  | 
|  | // Get the new node base on ActionType. | 
|  | const fuchsia::accessibility::semantics::Node* new_node; | 
|  | switch (direction_) { | 
|  | case kNextAction: | 
|  | new_node = action_context_->semantics_source->GetNextNode( | 
|  | a11y_focus->view_ref_koid, a11y_focus->node_id, | 
|  | [nodes_to_exclude](const fuchsia::accessibility::semantics::Node* node) { | 
|  | return (nodes_to_exclude.find(node->node_id()) == nodes_to_exclude.end()) && | 
|  | NodeIsDescribable(node); | 
|  | }); | 
|  | break; | 
|  | case kPreviousAction: | 
|  | new_node = action_context_->semantics_source->GetPreviousNode( | 
|  | a11y_focus->view_ref_koid, a11y_focus->node_id, | 
|  | [nodes_to_exclude](const fuchsia::accessibility::semantics::Node* node) { | 
|  | return (nodes_to_exclude.find(node->node_id()) == nodes_to_exclude.end()) && | 
|  | NodeIsDescribable(node); | 
|  | }); | 
|  | break; | 
|  | default: | 
|  | new_node = nullptr; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!new_node || !new_node->has_node_id()) { | 
|  | // This is the last / first node on the tree, inform the user that the linear navigation can't | 
|  | // continue further in this direction. | 
|  | auto* speaker = screen_reader_context_->speaker(); | 
|  | fit::promise<> promise; | 
|  | switch (direction_) { | 
|  | case kNextAction: | 
|  | promise = speaker->SpeakMessageByIdPromise(fuchsia::intl::l10n::MessageIds::LAST_ELEMENT, | 
|  | {.interrupt = true, .save_utterance = false}); | 
|  | break; | 
|  | case kPreviousAction: | 
|  | promise = speaker->SpeakMessageByIdPromise(fuchsia::intl::l10n::MessageIds::FIRST_ELEMENT, | 
|  | {.interrupt = true, .save_utterance = false}); | 
|  | break; | 
|  | } | 
|  | screen_reader_context_->executor()->schedule_task(std::move(promise)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint32_t new_node_id = new_node->node_id(); | 
|  | auto promise = | 
|  | ExecuteAccessibilityActionPromise(a11y_focus->view_ref_koid, new_node_id, | 
|  | fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN) | 
|  | .and_then([this, new_node_id, a11y_focus]() mutable { | 
|  | return SetA11yFocusPromise(new_node_id, a11y_focus->view_ref_koid); | 
|  | }) | 
|  | .and_then([this, a11y_focus, new_node_id]() mutable { | 
|  | return BuildSpeechTaskFromNodePromise(a11y_focus->view_ref_koid, new_node_id); | 
|  | }) | 
|  | // Cancel any promises if this class goes out of scope. | 
|  | .wrap_with(scope_); | 
|  | auto* executor = screen_reader_context_->executor(); | 
|  | executor->schedule_task(std::move(promise)); | 
|  | } | 
|  |  | 
|  | }  // namespace a11y |