blob: e87dd2d7761ee2ea58c5b4bf464ca2df101b34cd [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.
#include "src/ui/a11y/lib/screen_reader/explore_action.h"
#include <lib/async/cpp/time.h>
#include <lib/async/default.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/syslog/cpp/macros.h>
#include <cstdint>
#include "src/ui/a11y/lib/screen_reader/screen_reader_action.h"
#include "src/ui/a11y/lib/screen_reader/util/util.h"
namespace a11y {
namespace {
using fuchsia::accessibility::semantics::Hit;
} // namespace
ExploreAction::ExploreAction(ActionContext* context, ScreenReaderContext* screen_reader_context)
: ScreenReaderAction(context, screen_reader_context) {}
ExploreAction::~ExploreAction() = default;
fpromise::promise<Hit> ExploreAction::ExecuteHitTestingPromise(
const a11y::gesture_util_v2::GestureContext& gesture_context) {
fpromise::bridge<Hit> bridge;
ExecuteHitTesting(action_context_, gesture_context,
[completer = std::move(bridge.completer)](Hit hit) mutable {
if (!hit.has_node_id()) {
return completer.complete_error();
}
completer.complete_ok(std::move(hit));
});
return bridge.consumer.promise_or(fpromise::error());
}
fpromise::result<uint32_t> ExploreAction::SelectDescribableNodePromise(zx_koid_t view_koid,
Hit& hit) {
if (!hit.has_node_id()) {
return fpromise::error();
}
auto hit_test_result_node =
action_context_->semantics_source->GetSemanticNode(view_koid, hit.node_id());
if (!hit_test_result_node || !hit_test_result_node->has_node_id()) {
FX_LOGS(WARNING) << "Invalid hit test result.";
return fpromise::error();
}
auto node_to_return = hit_test_result_node;
while (node_to_return) {
FX_DCHECK(node_to_return->has_node_id());
auto node_parent =
action_context_->semantics_source->GetParentNode(view_koid, node_to_return->node_id());
if (NodeIsDescribable(node_to_return) &&
!SameInformationAsParent(node_to_return, node_parent)) {
return fpromise::ok(node_to_return->node_id());
}
node_to_return = node_parent;
}
FX_LOGS(WARNING) << "No describable ancestor found for node " << hit_test_result_node->node_id();
return fpromise::error();
}
fpromise::promise<> ExploreAction::SetA11yFocusOrStopPromise(
ScreenReaderContext::ScreenReaderMode mode, zx_koid_t view_koid, uint32_t node_id) {
return fpromise::make_promise([this, mode, view_koid, node_id]() mutable -> fpromise::promise<> {
if (mode == ScreenReaderContext::ScreenReaderMode::kContinuousExploration) {
// If the new a11y focus to be set is the same as the existing one during a
// continuous exploration, this means that the same node would be spoken
// multiple times. Check if the focus is new before continuing.
auto* a11y_focus_manager = screen_reader_context_->GetA11yFocusManager();
auto focus = a11y_focus_manager->GetA11yFocus();
if (!focus) {
return fpromise::make_error_promise();
}
if (focus->view_ref_koid == view_koid && focus->node_id == node_id) {
return fpromise::make_error_promise();
}
}
return SetA11yFocusPromise(view_koid, node_id);
});
}
void ExploreAction::Run(a11y::gesture_util_v2::GestureContext gesture_context) {
// TODO(https://fxbug.dev/42177676): Use activity service to detect when user is using a fuchsia device.
screen_reader_context_->set_last_interaction(async::Now(async_get_default_dispatcher()));
auto promise =
ExecuteHitTestingPromise(gesture_context)
.and_then([this, view_koid = gesture_context.view_ref_koid](
Hit& hit) mutable -> fpromise::result<uint32_t> {
return SelectDescribableNodePromise(view_koid, hit);
})
.and_then([this, view_koid = gesture_context.view_ref_koid,
mode = screen_reader_context_->mode()](
uint32_t& node_id) mutable -> fpromise::promise<> {
return SetA11yFocusOrStopPromise(mode, view_koid, node_id);
})
.and_then([this]() mutable -> fpromise::result<A11yFocusManager::A11yFocusInfo> {
auto* a11y_focus_manager = screen_reader_context_->GetA11yFocusManager();
auto focus = a11y_focus_manager->GetA11yFocus();
if (!focus) {
return fpromise::error();
}
return fpromise::ok(*focus);
})
.and_then([this](const A11yFocusManager::A11yFocusInfo& focus) mutable {
return BuildSpeechTaskFromNodePromise(focus.view_ref_koid, focus.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