blob: 6e7d1b4d717c53afb2469f113ded91f2a96cbad7 [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/semantics/semantic_tree_service.h"
#include <lib/async/default.h>
#include <zircon/types.h>
#include <algorithm>
#include "fuchsia/accessibility/semantics/cpp/fidl.h"
#include "src/lib/fsl/handles/object_info.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/concatenate.h"
#include "src/lib/syslog/cpp/logger.h"
#include "zircon/third_party/uapp/dash/src/output.h"
namespace a11y {
namespace {
using fuchsia::accessibility::semantics::Node;
// Max file size of semantic tree log file is 1MB.
constexpr size_t kMaxDebugFileSize = 1024 * 1024;
const std::string kNewLine = "\n";
constexpr std::string::size_type kIndentSize = 4;
} // namespace
SemanticTreeService::SemanticTreeService(
std::unique_ptr<::a11y::SemanticTree> tree, zx_koid_t(koid),
fuchsia::accessibility::semantics::SemanticListenerPtr semantic_listener,
vfs::PseudoDir* debug_dir, CloseChannelCallback error_callback)
: tree_(std::move(tree)),
close_channel_callback_(std::move(error_callback)),
koid_(koid),
semantic_listener_(std::move(semantic_listener)),
debug_dir_(debug_dir),
semantic_tree_factory_(
std::make_unique<fxl::WeakPtrFactory<::a11y::SemanticTree>>(tree_.get())) {
tree_->set_action_handler([this](uint32_t node_id,
fuchsia::accessibility::semantics::Action action,
fuchsia::accessibility::semantics::SemanticListener::
OnAccessibilityActionRequestedCallback callback) {
this->PerformAccessibilityAction(node_id, action, std::move(callback));
});
tree_->set_hit_testing_handler(
[this](fuchsia::math::PointF local_point,
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) {
this->PerformHitTesting(local_point, std::move(callback));
});
debug_file_name_ = std::to_string(koid_);
InitializeDebugEntry();
}
SemanticTreeService::~SemanticTreeService() {
RemoveDebugEntry();
semantic_tree_factory_->InvalidateWeakPtrs();
}
void SemanticTreeService::PerformAccessibilityAction(
uint32_t node_id, fuchsia::accessibility::semantics::Action action,
fuchsia::accessibility::semantics::SemanticListener::OnAccessibilityActionRequestedCallback
callback) {
semantic_listener_->OnAccessibilityActionRequested(node_id, action, std::move(callback));
}
zx_koid_t SemanticTreeService::view_ref_koid() const { return koid_; }
void SemanticTreeService::CommitUpdates(CommitUpdatesCallback callback) {
if (tree_->Update(std::move(updates_))) {
callback();
updates_.clear();
} else {
FX_LOGS(ERROR) << "Closing Semantic Tree Channel for View(KOID):" << koid_
<< " because client sent an invalid tree update";
callback();
close_channel_callback_(koid_);
}
}
void SemanticTreeService::UpdateSemanticNodes(std::vector<Node> nodes) {
for (auto& node : nodes) {
updates_.emplace_back(std::move(node));
}
}
void SemanticTreeService::DeleteSemanticNodes(std::vector<uint32_t> node_ids) {
for (const auto& node_id : node_ids) {
updates_.emplace_back(node_id);
}
}
void SemanticTreeService::LogSemanticTreeHelper(const Node* node, int current_level,
std::string* tree_log) {
if (!node) {
return;
}
// Add constant spaces proportional to the current tree level, so that
// child nodes are indented under parent node.
tree_log->append(kIndentSize * current_level, ' ');
// Add logs for the current node.
*tree_log = fxl::Concatenate({*tree_log, "Node_id: ", std::to_string(node->node_id()), ", Label:",
node->has_attributes() && node->attributes().has_label()
? node->attributes().label()
: "_empty",
kNewLine});
// Iterate through all the children of the current node.
if (!node->has_child_ids()) {
return;
}
for (const auto& child_id : node->child_ids()) {
const auto* child = tree_->GetNode(child_id);
FX_DCHECK(child);
LogSemanticTreeHelper(child, current_level + 1, tree_log);
}
}
std::string SemanticTreeService::LogSemanticTree() {
const auto* root = tree_->GetNode(::a11y::SemanticTree::kRootNodeId);
std::string tree_log;
if (!root) {
tree_log = "Root Node not found.";
FX_LOGS(ERROR) << tree_log;
return tree_log;
}
LogSemanticTreeHelper(root, ::a11y::SemanticTree::kRootNodeId, &tree_log);
FX_VLOGS(1) << "Semantic Tree:" << std::endl << tree_log;
return tree_log;
}
void SemanticTreeService::InitializeDebugEntry() {
if (debug_dir_) {
// Add Semantic Tree log file in Hub-Debug directory.
debug_dir_->AddEntry(
debug_file_name_,
std::make_unique<vfs::PseudoFile>(
kMaxDebugFileSize, [this](std::vector<uint8_t>* output, size_t max_file_size) {
std::string buffer = LogSemanticTree();
size_t len = buffer.length();
if (len > max_file_size) {
FX_LOGS(WARNING) << "Semantic Tree log file (" << std::to_string(koid_)
<< ") size is:" << len
<< " which is more than max size:" << kMaxDebugFileSize;
len = kMaxDebugFileSize;
}
output->resize(len);
std::copy(buffer.begin(), buffer.begin() + len, output->begin());
return ZX_OK;
}));
}
}
void SemanticTreeService::RemoveDebugEntry() {
FX_DCHECK(debug_dir_);
if (debug_dir_) {
// Remove Semantic Tree log file in the Hub-Debug directory.
debug_dir_->RemoveEntry(debug_file_name_);
}
} // namespace a11y
void SemanticTreeService::PerformHitTesting(
::fuchsia::math::PointF local_point,
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) {
semantic_listener_->HitTest(local_point, std::move(callback));
}
void SemanticTreeService::EnableSemanticsUpdates(bool enabled) {
semantics_manager_enabled_ = enabled;
// If Semantics Manager is disabled, then clear current semantic tree.
if (!enabled) {
tree_->Clear();
}
// Notify Semantic Provider about Semantics Manager's enable state.
fuchsia::accessibility::semantics::SemanticListener::OnSemanticsModeChangedCallback callback =
[]() { FX_LOGS(INFO) << "NotifySemanticsEnabled complete."; };
semantic_listener_->OnSemanticsModeChanged(enabled, std::move(callback));
}
const fxl::WeakPtr<::a11y::SemanticTree> SemanticTreeService::Get() const {
FX_DCHECK(semantic_tree_factory_.get());
return semantic_tree_factory_->GetWeakPtr();
}
} // namespace a11y