blob: 7d71a3d560fbcd14881ab99a3e9ddf45a436e677 [file] [log] [blame]
// 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