[embedder] accessibility bridge
Change-Id: Ib1f4b2e33d94aea68c0041e16c7b3dda3b75bd76
Reviewed-on: https://fuchsia-review.googlesource.com/c/flutter-embedder/+/771044
Reviewed-by: Alexander Biggs <akbiggs@google.com>
diff --git a/docs/accessibility.md b/docs/accessibility.md
new file mode 100644
index 0000000..7e6757a
--- /dev/null
+++ b/docs/accessibility.md
@@ -0,0 +1,37 @@
+# Accessibility Design
+
+The purpose of the accessibility code in the embedder repo is to act as an intermediary between the Flutter engine and the Fuchscia Semantics Manager. The [AccessibilityBridge class](https://fuchsia.googlesource.com/flutter-embedder/+/refs/heads/main/src/embedder/accessibility_bridge.h) implements this functionality.
+
+```
+Flutter Engine <--> Accessibility Bridge <--> Fuchsia Semantics Manager
+```
+
+## Responsibilities
+The accessibility bridge must forward information between Flutter and Fuchsia. Its specific responsibilities can be broken down by functionality:
+
+| Functionality | Code Path | A11y Interface | Flutter Engine Interface |
+| ----------------------------- | -------------------------------| ------------------------------------------------------------------ | ------------------------------------- |
+| Enable/Disable Semantics | Fuchsia --> Bridge --> Flutter | OnSemanticsModeChanged | FlutterEngineUpdateSemanticsEnabled |
+| Perform Accessibility Actions | Fuchsia --> Bridge --> Flutter | OnAccessibilityActionRequested | FlutterEngineDispatchSemanticsAction |
+| Add Semantics Update | Flutter --> Bridge --> Fuchsia | UpdateSemanticNodes</br>DeleteSemanticNodes</br>CommitUpdates</br> | update_semantics_callback |
+| Hit Test | Fuchsia --> Bridge | HitTest | N/A |
+| Message Announcement | Flutter --> Bridge --> Fuchsia | RequestAnnounce</br>SendSemanticEvent | FlutterPlatformMessageCallback |
+| Set Pixel Ratio | Fuchsia --> Bridge | SetPixelRatio | N/A |
+
+
+## Notes
+### Pixel Ratio
+This is the factor used to convert between a view’s logical coordinate space (assigned by scenic) and its allocated buffer size.
+
+For example, on Nest Hub Max, Flutter views have a logical size of (1024x640) and an allocated size of (1280x800), so they have a pixel scale of 1.25.
+
+### Testing
+1. Use emulator and dump semantic tree
+ 1. Start emulator
+ 2. `ffx setui accessibility set -s true` (note, this will temporarily disable touch/mouse input)
+ 3. `ffx inspect show core/ui/a11y_manager`
+ 4. Ensure the labels are filled out correctly with the words you see on the screen
+2. Use a smart display device to test the screen reader
+
+### Custom Semantic Actions
+Custom semantic actions are not supported yet.
\ No newline at end of file
diff --git a/src/embedder/BUILD.bazel b/src/embedder/BUILD.bazel
index f6a992a..7dc2973 100644
--- a/src/embedder/BUILD.bazel
+++ b/src/embedder/BUILD.bazel
@@ -35,6 +35,21 @@
"text_delegate.h",
"touch_delegate.cc",
"touch_delegate.h",
+ "accessibility_bridge.cc",
+ "accessibility_bridge.h",
+ "root_inspect_node.cc",
+ "root_inspect_node.h",
+ "standard_message_codec/byte_buffer_streams.h",
+ "standard_message_codec/byte_streams.h",
+ "standard_message_codec/encodable_value.h",
+ "standard_message_codec/message_codec.h",
+ "standard_message_codec/method_call.h",
+ "standard_message_codec/method_codec.h",
+ "standard_message_codec/method_result.h",
+ "standard_message_codec/standard_codec_serializer.h",
+ "standard_message_codec/standard_message_codec.cc",
+ "standard_message_codec/standard_message_codec.h",
+ "standard_message_codec/standard_method_codec.h"
],
visibility = ["//visibility:public"],
deps = [
@@ -45,6 +60,8 @@
"@fuchsia_sdk//fidl/fuchsia.sysmem:fuchsia.sysmem_cc",
"@fuchsia_sdk//fidl/fuchsia.ui.app:fuchsia.ui.app_cc",
"@fuchsia_sdk//fidl/fuchsia.ui.composition:fuchsia.ui.composition_cc",
+ "@fuchsia_sdk//fidl/fuchsia.accessibility.semantics:fuchsia.accessibility.semantics_cc",
+ "@fuchsia_sdk//pkg/sys_inspect_cpp",
"@fuchsia_sdk//pkg/async-loop-default",
"@fuchsia_sdk//pkg/async-loop-cpp",
"@fuchsia_sdk//pkg/fit",
diff --git a/src/embedder/accessibility_bridge.cc b/src/embedder/accessibility_bridge.cc
new file mode 100644
index 0000000..f18ac0e
--- /dev/null
+++ b/src/embedder/accessibility_bridge.cc
@@ -0,0 +1,1045 @@
+// Copyright 2022 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/embedder/accessibility_bridge.h"
+
+#include <lib/inspect/cpp/inspector.h>
+#include <lib/syslog/global.h>
+#include <lib/zx/process.h>
+#include <zircon/status.h>
+#include <zircon/types.h>
+
+#include <algorithm>
+#include <deque>
+
+#include "src/embedder/fuchsia_logger.h"
+#include "src/embedder/logging.h"
+#include "src/embedder/root_inspect_node.h"
+#include "src/embedder/standard_message_codec/encodable_value.h"
+#include "src/embedder/standard_message_codec/standard_message_codec.h"
+
+namespace embedder {
+namespace accessibility_matrix_utility {
+
+/// The FlutterTransformation (defined in embedder.h) can be considered a 3x3 matrix
+///
+/// scaleX | skewY | pers0
+/// skewX | scaleY | pers1
+/// transX | transY | pers2
+///
+/// Casting this object to a 3x3 array greatly simplifies the logic for multiplication
+/// operations between these types
+constexpr size_t kMatrixSize = 3;
+using FlutterMatrix33 = std::array<std::array<double, kMatrixSize>, kMatrixSize>;
+/// Ensure there is no padding and the casting will work properly
+static_assert(sizeof(FlutterMatrix33) == sizeof(FlutterTransformation));
+
+/// A 1x3 Matrix
+struct Scalar {
+ const auto& x() const { return data[0]; }
+ const auto& y() const { return data[1]; }
+ const auto& z() const { return data[2]; }
+ std::array<double, kMatrixSize> data;
+};
+
+/// Initialize a 3x3 matrix from the Scalar's x, y, z values:
+/// x, 0, 0
+/// 0, y, 0
+/// 0, 0, z
+///
+/// Then, cast the 3x3 matrix back into a FlutterTransformation
+FlutterTransformation CreateTransformFromScalar(const Scalar& scalar) {
+ FlutterTransformation transform;
+ memset(&transform, 0, sizeof(FlutterTransformation));
+ transform.scaleX = scalar.x();
+ transform.scaleY = scalar.y();
+ transform.pers2 = scalar.z();
+ return transform;
+}
+
+/// Multiply two 3x3 matrices by each other
+/// Return the result casted back into a FlutterTransformation
+FlutterTransformation MultiplyTransformations(const FlutterTransformation& left,
+ const FlutterTransformation& right) {
+ const FlutterMatrix33& a = *reinterpret_cast<const FlutterMatrix33*>(&left);
+ const FlutterMatrix33& b = *reinterpret_cast<const FlutterMatrix33*>(&right);
+
+ FlutterMatrix33 product;
+
+ auto compute_cell = [&](size_t row, size_t col) {
+ double result = 0.0;
+ for (size_t i = 0; i < kMatrixSize; i++) {
+ result += a[row][i] * b[i][col];
+ }
+ return result;
+ };
+
+ for (size_t row = 0; row < kMatrixSize; row++) {
+ for (size_t col = 0; col < kMatrixSize; col++) {
+ product[row][col] = compute_cell(row, col);
+ }
+ }
+ return *reinterpret_cast<FlutterTransformation*>(&product);
+}
+
+/// Multipy a 1x3 matrix with a 3x3 matrix
+Scalar MapScalarOnTransformation(const Scalar& scalar,
+ const FlutterTransformation& transformation_in) {
+ const FlutterMatrix33& transformation =
+ *reinterpret_cast<const FlutterMatrix33*>(&transformation_in);
+
+ Scalar result;
+
+ for (size_t row = 0; row < kMatrixSize; row++) {
+ for (size_t col = 0; col < kMatrixSize; col++) {
+ result.data[row] += transformation[row][col] * scalar.data[col];
+ }
+ }
+ return result;
+}
+} // namespace accessibility_matrix_utility
+namespace amu = accessibility_matrix_utility;
+
+namespace {
+
+/// Re-arrange the rectangle values so bottom & right
+/// values are largest
+void SortRectangle(FlutterRect& rect) {
+ if (rect.left > rect.right) {
+ std::swap(rect.left, rect.right);
+ }
+ if (rect.top > rect.bottom) {
+ std::swap(rect.top, rect.bottom);
+ }
+}
+
+/// Check if a rectangle contains a given point
+bool RectangleContains(const FlutterRect& rect, int32_t x, int32_t y) {
+ return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
+}
+
+/// Utility to simplify checking whether enum bitfields backed by an integer contain a
+/// particular enum value
+/// (e.g. the 'flags' or 'actions' fields on the FlutterSemanticsNode type)
+template <typename T>
+struct EnumFlagSet {
+ int32_t flags = 0;
+ EnumFlagSet(T _flags) : flags(static_cast<int32_t>(_flags)) {}
+ bool HasFlag(T flag) const { return (flags & static_cast<int32_t>(flag)) != 0; }
+};
+
+static constexpr char kTreeDumpInspectRootName[] = "semantic_tree_root";
+
+// Converts flutter semantic node flags to a string representation.
+std::string NodeFlagsToString(const FlutterSemanticsNode& node) {
+ std::string output;
+ EnumFlagSet<FlutterSemanticsFlag> flag_set(node.flags);
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState)) {
+ output += "kHasCheckedState|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasEnabledState)) {
+ output += "kHasEnabledState|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasImplicitScrolling)) {
+ output += "kHasImplicitScrolling|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState)) {
+ output += "kHasToggledState|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsButton)) {
+ output += "kIsButton|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked)) {
+ output += "kIsChecked|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsEnabled)) {
+ output += "kIsEnabled|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocusable)) {
+ output += "kIsFocusable|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused)) {
+ output += "kIsFocused|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsHeader)) {
+ output += "kIsHeader|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsHidden)) {
+ output += "kIsHidden|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsImage)) {
+ output += "kIsImage|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsInMutuallyExclusiveGroup)) {
+ output += "kIsInMutuallyExclusiveGroup|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsKeyboardKey)) {
+ output += "kIsKeyboardKey|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsLink)) {
+ output += "kIsLink|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsLiveRegion)) {
+ output += "kIsLiveRegion|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsObscured)) {
+ output += "kIsObscured|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly)) {
+ output += "kIsReadOnly|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsSelected)) {
+ output += "kIsSelected|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsSlider)) {
+ output += "kIsSlider|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField)) {
+ output += "kIsTextField|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled)) {
+ output += "kIsToggled|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagNamesRoute)) {
+ output += "kNamesRoute|";
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagScopesRoute)) {
+ output += "kScopesRoute|";
+ }
+
+ return output;
+}
+
+// Converts flutter semantic node actions to a string representation.
+std::string NodeActionsToString(const FlutterSemanticsNode& node) {
+ std::string output;
+
+ EnumFlagSet<FlutterSemanticsAction> action_set(node.actions);
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionCopy)) {
+ output += "kCopy|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionCustomAction)) {
+ output += "kCustomAction|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionCut)) {
+ output += "kCut|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionDecrease)) {
+ output += "kDecrease|";
+ }
+ if (action_set.HasFlag(
+ FlutterSemanticsAction::kFlutterSemanticsActionDidGainAccessibilityFocus)) {
+ output += "kDidGainAccessibilityFocus|";
+ }
+ if (action_set.HasFlag(
+ FlutterSemanticsAction::kFlutterSemanticsActionDidLoseAccessibilityFocus)) {
+ output += "kDidLoseAccessibilityFocus|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionDismiss)) {
+ output += "kDismiss|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionIncrease)) {
+ output += "kIncrease|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionLongPress)) {
+ output += "kLongPress|";
+ }
+ if (action_set.HasFlag(
+ FlutterSemanticsAction::kFlutterSemanticsActionMoveCursorBackwardByCharacter)) {
+ output += "kMoveCursorBackwardByCharacter|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionMoveCursorBackwardByWord)) {
+ output += "kMoveCursorBackwardByWord|";
+ }
+ if (action_set.HasFlag(
+ FlutterSemanticsAction::kFlutterSemanticsActionMoveCursorForwardByCharacter)) {
+ output += "kMoveCursorForwardByCharacter|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionMoveCursorForwardByWord)) {
+ output += "kMoveCursorForwardByWord|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionPaste)) {
+ output += "kPaste|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionScrollDown)) {
+ output += "kScrollDown|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft)) {
+ output += "kScrollLeft|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionScrollRight)) {
+ output += "kScrollRight|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionScrollUp)) {
+ output += "kScrollUp|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionSetSelection)) {
+ output += "kSetSelection|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionSetText)) {
+ output += "kSetText|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen)) {
+ output += "kShowOnScreen|";
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionTap)) {
+ output += "kTap|";
+ }
+
+ return output;
+}
+
+/// Returns a string representation of the flutter semantic node absolute
+/// location.
+std::string NodeLocationToString(const FlutterRect& rect) {
+ auto min_x = rect.left;
+ auto min_y = rect.top;
+ auto max_x = rect.right;
+ auto max_y = rect.bottom;
+ std::string location = "min(" + std::to_string(min_x) + ", " + std::to_string(min_y) + ") max(" +
+ std::to_string(max_x) + ", " + std::to_string(max_y) + ")";
+
+ return location;
+}
+
+/// Returns a string representation of the node's different types of children.
+std::string NodeChildrenToString(const FlutterSemanticsNode& node) {
+ std::stringstream output;
+ if (node.child_count != 0) {
+ output << "children in traversal order:[";
+ for (size_t child = 0; child < node.child_count; child++) {
+ output << node.children_in_traversal_order[child] << ", ";
+ }
+ output << "]\n";
+ output << "children in hit test order:[";
+ for (size_t child = 0; child < node.child_count; child++) {
+ output << node.children_in_hit_test_order[child] << ", ";
+ }
+ output << ']';
+ }
+ return output.str();
+}
+
+} // namespace
+
+AccessibilityBridge::AccessibilityBridge(
+ SetSemanticsEnabledCallback set_semantics_enabled_callback,
+ DispatchSemanticsActionCallback dispatch_semantics_action_callback,
+ fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager,
+ fuchsia::ui::views::ViewRef view_ref, inspect::Node inspect_node)
+ : set_semantics_enabled_callback_(std::move(set_semantics_enabled_callback)),
+ dispatch_semantics_action_callback_(std::move(dispatch_semantics_action_callback)),
+ binding_(this),
+ fuchsia_semantics_manager_(semantics_manager.Bind()),
+ atomic_updates_(std::make_shared<std::queue<FuchsiaAtomicUpdate>>()),
+ inspect_node_(std::move(inspect_node)) {
+ fuchsia_semantics_manager_.set_error_handler([](zx_status_t status) {
+ FX_LOGF(ERROR, kLogTag, "Flutter cannot connect to SemanticsManager with status: %s.",
+ zx_status_get_string(status));
+ });
+
+ fuchsia_semantics_manager_->RegisterViewForSemantics(std::move(view_ref), binding_.NewBinding(),
+ tree_ptr_.NewRequest());
+ // TODO(benbergkamp)
+ // #if DEBUG
+ // The first argument to |CreateLazyValues| is the name of the lazy node, and
+ // will only be displayed if the callback used to generate the node's content
+ // fails. Therefore, we use an error message for this node name.
+ inspect_node_tree_dump_ = inspect_node_.CreateLazyValues("dump_fail", [this]() {
+ inspect::Inspector inspector;
+ if (auto it = nodes_.find(kRootNodeId); it == nodes_.end()) {
+ inspector.GetRoot().CreateString("empty_tree", "this semantic tree is empty", &inspector);
+ } else {
+ FillInspectTree(kRootNodeId, /*current_level=*/1,
+ inspector.GetRoot().CreateChild(kTreeDumpInspectRootName), &inspector);
+ }
+ return fpromise::make_ok_promise(std::move(inspector));
+ });
+ // #endif DEBUG
+}
+
+bool AccessibilityBridge::GetSemanticsEnabled() const { return semantics_enabled_; }
+
+void AccessibilityBridge::SetSemanticsEnabled(bool enabled) {
+ semantics_enabled_ = enabled;
+ if (!enabled) {
+ nodes_.clear();
+ }
+}
+
+fuchsia::ui::gfx::BoundingBox AccessibilityBridge::GetNodeLocation(
+ const FlutterSemanticsNode& node) const {
+ fuchsia::ui::gfx::BoundingBox box;
+ box.min.x = node.rect.left;
+ box.min.y = node.rect.top;
+ box.min.z = static_cast<float>(node.elevation);
+ box.max.x = node.rect.right;
+ box.max.y = node.rect.bottom;
+ box.max.z = static_cast<float>(node.thickness);
+ return box;
+}
+
+fuchsia::ui::gfx::mat4 AccessibilityBridge::GetNodeTransform(
+ const FlutterSemanticsNode& node) const {
+ return ConvertFlutterTransformToMat4(node.transform);
+}
+
+fuchsia::ui::gfx::mat4 AccessibilityBridge::ConvertFlutterTransformToMat4(
+ const FlutterTransformation transform) const {
+ fuchsia::ui::gfx::mat4 value;
+ float* m = value.matrix.data();
+ memcpy(m, &transform, sizeof(transform));
+ return value;
+}
+
+fuchsia::accessibility::semantics::Attributes AccessibilityBridge::GetNodeAttributes(
+ const FlutterSemanticsNode& node, size_t* added_size) const {
+ fuchsia::accessibility::semantics::Attributes attributes;
+
+ // TODO(MI4-2531): Don't truncate.
+ attributes.set_label(std::string(
+ node.label, std::min(strlen(node.label), fuchsia::accessibility::semantics::MAX_LABEL_SIZE)));
+ *added_size += attributes.label().size();
+
+ // Truncate
+ attributes.set_secondary_label(std::string(
+ node.tooltip,
+ std::min(strlen(node.tooltip), fuchsia::accessibility::semantics::MAX_LABEL_SIZE)));
+ *added_size += attributes.secondary_label().size();
+
+ const EnumFlagSet<FlutterSemanticsFlag> flag_set(node.flags);
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsKeyboardKey)) {
+ attributes.set_is_keyboard_key(true);
+ }
+ return attributes;
+}
+
+fuchsia::accessibility::semantics::States AccessibilityBridge::GetNodeStates(
+ const FlutterSemanticsNode& node, size_t* additional_size) const {
+ fuchsia::accessibility::semantics::States states;
+ (*additional_size) += sizeof(fuchsia::accessibility::semantics::States);
+
+ const EnumFlagSet<FlutterSemanticsFlag> node_flag_set(node.flags);
+ // Set checked state.
+ if (!node_flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState)) {
+ states.set_checked_state(fuchsia::accessibility::semantics::CheckedState::NONE);
+ } else {
+ states.set_checked_state(
+ node_flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked)
+ ? fuchsia::accessibility::semantics::CheckedState::CHECKED
+ : fuchsia::accessibility::semantics::CheckedState::UNCHECKED);
+ }
+
+ // Set selected state.
+ states.set_selected(node_flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsSelected));
+
+ // Flutter's definition of a hidden node is different from Fuchsia, so it must
+ // not be set here.
+
+ // Set value.
+ states.set_value(std::string(
+ node.value, std::min(strlen(node.value), fuchsia::accessibility::semantics::MAX_VALUE_SIZE)));
+ *additional_size += states.value().size();
+
+ // Set toggled state.
+ if (node_flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState)) {
+ states.set_toggled_state(
+ node_flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled)
+ ? fuchsia::accessibility::semantics::ToggledState::ON
+ : fuchsia::accessibility::semantics::ToggledState::OFF);
+ }
+
+ return states;
+}
+
+std::vector<fuchsia::accessibility::semantics::Action> AccessibilityBridge::GetNodeActions(
+ const FlutterSemanticsNode& node, size_t* additional_size) const {
+ std::vector<fuchsia::accessibility::semantics::Action> node_actions;
+
+ EnumFlagSet<FlutterSemanticsAction> action_set(node.actions);
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionTap)) {
+ node_actions.push_back(fuchsia::accessibility::semantics::Action::DEFAULT);
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionLongPress)) {
+ node_actions.push_back(fuchsia::accessibility::semantics::Action::SECONDARY);
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen)) {
+ node_actions.push_back(fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionIncrease)) {
+ node_actions.push_back(fuchsia::accessibility::semantics::Action::INCREMENT);
+ }
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionDecrease)) {
+ node_actions.push_back(fuchsia::accessibility::semantics::Action::DECREMENT);
+ }
+
+ *additional_size += node_actions.size() * sizeof(fuchsia::accessibility::semantics::Action);
+ return node_actions;
+}
+
+fuchsia::accessibility::semantics::Role AccessibilityBridge::GetNodeRole(
+ const FlutterSemanticsNode& node) const {
+ EnumFlagSet<FlutterSemanticsFlag> flag_set(node.flags);
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsButton)) {
+ return fuchsia::accessibility::semantics::Role::BUTTON;
+ }
+
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField)) {
+ return fuchsia::accessibility::semantics::Role::TEXT_FIELD;
+ }
+
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsLink)) {
+ return fuchsia::accessibility::semantics::Role::LINK;
+ }
+
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsSlider)) {
+ return fuchsia::accessibility::semantics::Role::SLIDER;
+ }
+
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsHeader)) {
+ return fuchsia::accessibility::semantics::Role::HEADER;
+ }
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsImage)) {
+ return fuchsia::accessibility::semantics::Role::IMAGE;
+ }
+
+ EnumFlagSet<FlutterSemanticsAction> action_set(node.actions);
+ // If a flutter node supports the kIncrease or kDecrease actions, it can be
+ // treated as a slider control by assistive technology. This is important
+ // because users have special gestures to deal with sliders, and Fuchsia API
+ // requires nodes that can receive this kind of action to be a slider control.
+ if (action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionIncrease) ||
+ action_set.HasFlag(FlutterSemanticsAction::kFlutterSemanticsActionDecrease)) {
+ return fuchsia::accessibility::semantics::Role::SLIDER;
+ }
+
+ // If a flutter node has a checked state, then we assume it is either a
+ // checkbox or a radio button. We distinguish between checkboxes and
+ // radio buttons based on membership in a mutually exclusive group.
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState)) {
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsInMutuallyExclusiveGroup)) {
+ return fuchsia::accessibility::semantics::Role::RADIO_BUTTON;
+ } else {
+ return fuchsia::accessibility::semantics::Role::CHECK_BOX;
+ }
+ }
+
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState)) {
+ return fuchsia::accessibility::semantics::Role::TOGGLE_SWITCH;
+ }
+ return fuchsia::accessibility::semantics::Role::UNKNOWN;
+}
+
+std::unordered_set<int32_t> AccessibilityBridge::GetDescendants(int32_t node_id) const {
+ std::unordered_set<int32_t> descendents;
+ std::deque<int32_t> to_process = {node_id};
+ while (!to_process.empty()) {
+ int32_t id = to_process.front();
+ to_process.pop_front();
+ descendents.emplace(id);
+
+ auto it = nodes_.find(id);
+ if (it != nodes_.end()) {
+ const auto& node = it->second.data;
+ for (size_t child = 0; child < node.child_count; child++) {
+ int32_t child_id = node.children_in_hit_test_order[child];
+ if (descendents.find(child_id) == descendents.end()) {
+ to_process.push_back(child_id);
+ } else {
+ // This indicates either a cycle or a child with multiple parents.
+ // Flutter should never let this happen, but the engine API does not
+ // explicitly forbid it right now.
+ // TODO(http://fxbug.dev/75905): Crash flutter accessibility bridge
+ // when a cycle in the tree is found.
+ FX_LOGF(ERROR, kLogTag,
+ "Semantics Node %d has already been listed as"
+ " a child of another node, ignoring for parent %d.",
+ child_id, id);
+ }
+ }
+ }
+ }
+ return descendents;
+}
+
+// The only known usage of a negative number for a node ID is in the embedder
+// API as a sentinel value, which is not expected here. No valid producer of
+// nodes should give us a negative ID.
+static uint32_t FlutterIdToFuchsiaId(int32_t flutter_node_id) {
+ FX_DCHECK(flutter_node_id >= 0);
+ return static_cast<uint32_t>(flutter_node_id);
+}
+
+void AccessibilityBridge::PruneUnreachableNodes(FuchsiaAtomicUpdate* atomic_update) {
+ const auto& reachable_nodes = GetDescendants(kRootNodeId);
+ auto iter = nodes_.begin();
+ while (iter != nodes_.end()) {
+ int32_t id = iter->first;
+ if (reachable_nodes.find(id) == reachable_nodes.end()) {
+ atomic_update->AddNodeDeletion(FlutterIdToFuchsiaId(id));
+ iter = nodes_.erase(iter);
+ } else {
+ iter++;
+ }
+ }
+}
+
+// TODO(FIDL-718) - remove this, handle the error instead in something like
+// set_error_handler.
+static void PrintNodeSizeError(uint32_t node_id) {
+ FX_LOGF(ERROR, kLogTag,
+ "Semantics node with ID %d exceeded the maximum "
+ "FIDL message size and may not be delivered to the accessibility "
+ "manager service.",
+ node_id);
+}
+
+void AccessibilityBridge::AddSemanticsUpdate(const FlutterSemanticsUpdate* update) {
+ AddSemanticsNodeUpdates(update->nodes, update->nodes_count);
+ // Note: Not handling actions, as custom semantics actions are not yet supported
+}
+
+void AccessibilityBridge::AddSemanticsNodeUpdates(const FlutterSemanticsNode* update,
+ size_t count) {
+ bool seen_root = false;
+ for (unsigned i = 0; i < count; i++) {
+ auto& update_ref = update[i];
+
+ // We handle root update separately in GetRootNodeUpdate.
+ // TODO(chunhtai): remove this special case after we remove the inverse
+ // view pixel ratio transformation in scenic view.
+ // TODO(http://fxbug.dev/75908): Investigate flutter a11y bridge refactor
+ // after removal of the inverse view pixel ratio transformation in scenic
+ // view).
+ if (update_ref.id == kRootNodeId) {
+ root_flutter_semantics_node_ = *update; // copy
+ seen_root = true;
+ continue;
+ }
+
+ // "Normal case" where it's neither the root nor the terminal entry
+ size_t this_node_size = sizeof(fuchsia::accessibility::semantics::Node);
+
+ // Store the nodes for later hit testing and logging.
+ nodes_[update_ref.id].data = update_ref; // copy
+ fuchsia::accessibility::semantics::Node fuchsia_node;
+ std::vector<uint32_t> child_ids;
+
+ // Send the nodes in traversal order, so the manager can figure out
+ // traversal.
+ for (size_t child_id = 0; child_id < update_ref.child_count; child_id++) {
+ child_ids.push_back(FlutterIdToFuchsiaId(update_ref.children_in_traversal_order[child_id]));
+ }
+
+ // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
+ // the flutter accessibility bridge.
+ fuchsia_node.set_node_id(update_ref.id)
+ .set_role(GetNodeRole(update_ref))
+ .set_location(GetNodeLocation(update_ref))
+ .set_transform(GetNodeTransform(update_ref))
+ .set_attributes(GetNodeAttributes(update_ref, &this_node_size))
+ .set_states(GetNodeStates(update_ref, &this_node_size))
+ .set_actions(GetNodeActions(update_ref, &this_node_size))
+ .set_child_ids(child_ids);
+ this_node_size += kNodeIdSize * update_ref.child_count;
+ current_atomic_update_.AddNodeUpdate(std::move(fuchsia_node), this_node_size);
+ }
+
+ // Handled a batch of updates without ever getting a root node.
+ FX_DCHECK(nodes_.find(kRootNodeId) != nodes_.end() || seen_root);
+
+ // Handles root node update.
+ if (seen_root || last_seen_view_pixel_ratio_ != next_pixel_ratio_) {
+ last_seen_view_pixel_ratio_ = next_pixel_ratio_;
+ size_t root_node_size;
+ fuchsia::accessibility::semantics::Node root_update = GetRootNodeUpdate(root_node_size);
+ current_atomic_update_.AddNodeUpdate(std::move(root_update), root_node_size);
+ }
+
+ PruneUnreachableNodes(¤t_atomic_update_);
+ UpdateScreenRects();
+
+ atomic_updates_->push(std::move(current_atomic_update_));
+ if (atomic_updates_->size() == 1) {
+ // There were no commits in the queue, so send this one.
+ Apply(&atomic_updates_->front());
+ }
+ current_atomic_update_.Clear();
+}
+
+fuchsia::accessibility::semantics::Node AccessibilityBridge::GetRootNodeUpdate(size_t& node_size) {
+ fuchsia::accessibility::semantics::Node root_fuchsia_node;
+ std::vector<uint32_t> child_ids;
+ node_size = sizeof(fuchsia::accessibility::semantics::Node);
+ for (size_t child = 0; child < root_flutter_semantics_node_.child_count; child++) {
+ int32_t flutter_child_id = root_flutter_semantics_node_.children_in_traversal_order[child];
+ child_ids.push_back(FlutterIdToFuchsiaId(flutter_child_id));
+ }
+
+ float inverse_view_pixel_ratio = 1.f / last_seen_view_pixel_ratio_;
+ amu::Scalar scalar{.data = {inverse_view_pixel_ratio, inverse_view_pixel_ratio, 1.0}};
+ FlutterTransformation inverse_view_pixel_ratio_transform(amu::CreateTransformFromScalar(scalar));
+
+ const auto& result = amu::MultiplyTransformations(root_flutter_semantics_node_.transform,
+ inverse_view_pixel_ratio_transform);
+ nodes_[root_flutter_semantics_node_.id].data = root_flutter_semantics_node_;
+
+ // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
+ // the flutter accessibility bridge.
+ root_fuchsia_node.set_node_id(root_flutter_semantics_node_.id)
+ .set_role(GetNodeRole(root_flutter_semantics_node_))
+ .set_location(GetNodeLocation(root_flutter_semantics_node_))
+ .set_transform(ConvertFlutterTransformToMat4(result))
+ .set_attributes(GetNodeAttributes(root_flutter_semantics_node_, &node_size))
+ .set_states(GetNodeStates(root_flutter_semantics_node_, &node_size))
+ .set_actions(GetNodeActions(root_flutter_semantics_node_, &node_size))
+ .set_child_ids(child_ids);
+ node_size += kNodeIdSize * root_flutter_semantics_node_.child_count;
+ return root_fuchsia_node;
+}
+
+namespace smc = standard_message_codec;
+
+void AccessibilityBridge::HandlePlatformMessage(const FlutterPlatformMessage* message) {
+ const smc::StandardMessageCodec& standard_message_codec =
+ smc::StandardMessageCodec::GetInstance(nullptr);
+ std::unique_ptr<smc::EncodableValue> decoded =
+ standard_message_codec.DecodeMessage(message->message, message->message_size);
+
+ smc::EncodableMap map = std::get<smc::EncodableMap>(*decoded);
+ std::string type = std::get<std::string>(map.at(smc::EncodableValue("type")));
+ if (type == "announce") {
+ smc::EncodableMap data_map = std::get<smc::EncodableMap>(map.at(smc::EncodableValue("data")));
+ std::string text = std::get<std::string>(data_map.at(smc::EncodableValue("message")));
+ RequestAnnounce(text);
+ }
+}
+
+void AccessibilityBridge::RequestAnnounce(const std::string message) {
+ fuchsia::accessibility::semantics::SemanticEvent semantic_event;
+ fuchsia::accessibility::semantics::AnnounceEvent announce_event;
+ announce_event.set_message(message);
+ semantic_event.set_announce(std::move(announce_event));
+
+ tree_ptr_->SendSemanticEvent(std::move(semantic_event), []() {});
+}
+
+void AccessibilityBridge::UpdateScreenRects() {
+ std::unordered_set<int32_t> visited_nodes;
+
+ // The embedder applies a special pixel ratio transform to the root of the
+ // view, and the accessibility bridge applies the inverse of this transform
+ // to the root node. However, this transform is not persisted in the flutter
+ // representation of the root node, so we need to account for it explicitly
+ // here.
+ float inverse_view_pixel_ratio = 1.f / last_seen_view_pixel_ratio_;
+ amu::Scalar scalar{.data = {inverse_view_pixel_ratio, inverse_view_pixel_ratio, 1.0}};
+ FlutterTransformation inverse_view_pixel_ratio_transform(amu::CreateTransformFromScalar(scalar));
+
+ UpdateScreenRects(kRootNodeId, inverse_view_pixel_ratio_transform, &visited_nodes);
+}
+
+void AccessibilityBridge::UpdateScreenRects(int32_t node_id, FlutterTransformation parent_transform,
+ std::unordered_set<int32_t>* visited_nodes) {
+ auto it = nodes_.find(node_id);
+ if (it == nodes_.end()) {
+ FX_LOG(ERROR, kLogTag, "UpdateScreenRects called on unknown node");
+ return;
+ }
+ auto& node = it->second;
+ const auto& current_transform =
+ amu::MultiplyTransformations(parent_transform, node.data.transform);
+
+ const auto& rect = node.data.rect;
+ amu::Scalar dst[2]{amu::MapScalarOnTransformation(amu::Scalar{.data = {rect.left, rect.top, 0}},
+ current_transform),
+ amu::MapScalarOnTransformation(
+ amu::Scalar{.data = {rect.right, rect.bottom, 0}}, current_transform)};
+
+ FlutterRect new_rect{
+ .left = dst[0].x(), .top = dst[0].y(), .right = dst[1].x(), .bottom = dst[1].y()};
+ SortRectangle(new_rect);
+ node.screen_rect = new_rect;
+
+ visited_nodes->emplace(node_id);
+
+ for (uint32_t child = 0; child < node.data.child_count; child++) {
+ int32_t child_id = node.data.children_in_hit_test_order[child];
+ if (visited_nodes->find(child_id) == visited_nodes->end()) {
+ UpdateScreenRects(child_id, current_transform, visited_nodes);
+ }
+ }
+}
+
+std::optional<FlutterSemanticsAction> AccessibilityBridge::GetFlutterSemanticsAction(
+ fuchsia::accessibility::semantics::Action fuchsia_action, uint32_t node_id) {
+ switch (fuchsia_action) {
+ // The default action associated with the element.
+ case fuchsia::accessibility::semantics::Action::DEFAULT:
+ return FlutterSemanticsAction::kFlutterSemanticsActionTap;
+ // The secondary action associated with the element. This may correspond to
+ // a long press (touchscreens) or right click (mouse).
+ case fuchsia::accessibility::semantics::Action::SECONDARY:
+ return FlutterSemanticsAction::kFlutterSemanticsActionLongPress;
+ // Set (input/non-accessibility) focus on this element.
+ case fuchsia::accessibility::semantics::Action::SET_FOCUS:
+ FX_LOGF(WARNING, kLogTag,
+ "Unsupported action SET_FOCUS sent for "
+ "accessibility node %d",
+ node_id);
+ return {};
+ // Set the element's value.
+ case fuchsia::accessibility::semantics::Action::SET_VALUE:
+ FX_LOGF(WARNING, kLogTag,
+ "Unsupported action SET_VALUE sent for "
+ "accessibility node %d",
+ node_id);
+ return {};
+ // Scroll node to make it visible.
+ case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN:
+ return FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen;
+ case fuchsia::accessibility::semantics::Action::INCREMENT:
+ return FlutterSemanticsAction::kFlutterSemanticsActionIncrease;
+ case fuchsia::accessibility::semantics::Action::DECREMENT:
+ return FlutterSemanticsAction::kFlutterSemanticsActionDecrease;
+ default:
+ FX_LOGF(WARNING, kLogTag,
+ "Unexpected action %d sent for "
+ "accessibility node %d",
+ static_cast<int32_t>(fuchsia_action), node_id);
+ return {};
+ }
+}
+
+// |fuchsia::accessibility::semantics::SemanticListener|
+void AccessibilityBridge::OnAccessibilityActionRequested(
+ uint32_t node_id, fuchsia::accessibility::semantics::Action action,
+ fuchsia::accessibility::semantics::SemanticListener::OnAccessibilityActionRequestedCallback
+ callback) {
+ // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
+ // the flutter accessibility bridge.
+ if (nodes_.find(node_id) == nodes_.end()) {
+ FX_LOGF(ERROR, kLogTag,
+ "Attempted to send accessibility action "
+ "%d to unknown node id: %d",
+ static_cast<int32_t>(action), node_id);
+ callback(false);
+ return;
+ }
+
+ std::optional<FlutterSemanticsAction> flutter_action = GetFlutterSemanticsAction(action, node_id);
+ if (!flutter_action.has_value()) {
+ callback(false);
+ return;
+ }
+ dispatch_semantics_action_callback_(static_cast<int32_t>(node_id), flutter_action.value());
+ callback(true);
+}
+
+// |fuchsia::accessibility::semantics::SemanticListener|
+void AccessibilityBridge::HitTest(
+ fuchsia::math::PointF local_point,
+ fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) {
+ auto hit_node_id = GetHitNode(kRootNodeId, local_point.x, local_point.y);
+ FX_DCHECK(hit_node_id.has_value());
+ fuchsia::accessibility::semantics::Hit hit;
+ // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
+ // the flutter accessibility bridge.
+ hit.set_node_id(hit_node_id.value_or(kRootNodeId));
+ callback(std::move(hit));
+}
+
+std::optional<int32_t> AccessibilityBridge::GetHitNode(int32_t node_id, float x, float y) {
+ auto it = nodes_.find(node_id);
+ if (it == nodes_.end()) {
+ FX_LOGF(ERROR, kLogTag, "Attempted to hit test unknown node id: %d", node_id);
+ return {};
+ }
+ auto const& node = it->second;
+ EnumFlagSet<FlutterSemanticsFlag> flag_set(node.data.flags);
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsHidden) || //
+ !RectangleContains(node.screen_rect, x, y)) {
+ return {};
+ }
+
+ for (size_t child = 0; child < node.data.child_count; child++) {
+ int32_t child_id = node.data.children_in_hit_test_order[child];
+ auto candidate = GetHitNode(child_id, x, y);
+ if (candidate) {
+ return candidate;
+ }
+ }
+
+ if (IsFocusable(node.data)) {
+ return node_id;
+ }
+
+ return {};
+}
+
+bool AccessibilityBridge::IsFocusable(const FlutterSemanticsNode& node) const {
+ EnumFlagSet<FlutterSemanticsFlag> flag_set(node.flags);
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagScopesRoute)) {
+ return false;
+ }
+
+ if (flag_set.HasFlag(FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocusable)) {
+ return true;
+ }
+
+ // Always consider platform views focusable.
+ if (node.platform_view_id >
+ -1 /*TODO this is a hack, need to get the -1 value from proper channel*/) {
+ return true;
+ }
+
+ // Always consider actionable nodes focusable.
+ if (node.actions != 0) {
+ return true;
+ }
+
+ // Consider text nodes focusable.
+ return strlen(node.label) > 0 || strlen(node.value) > 0 || strlen(node.hint) > 0;
+}
+
+// |fuchsia::accessibility::semantics::SemanticListener|
+void AccessibilityBridge::OnSemanticsModeChanged(bool enabled,
+ OnSemanticsModeChangedCallback callback) {
+ set_semantics_enabled_callback_(enabled);
+}
+
+void AccessibilityBridge::SetPixelRatio(float ratio) { next_pixel_ratio_ = ratio; }
+
+// TODO(benbergkamp)
+// #if DEBUG
+void AccessibilityBridge::FillInspectTree(int32_t flutter_node_id, int32_t current_level,
+ inspect::Node inspect_node,
+ inspect::Inspector* inspector) const {
+ const auto it = nodes_.find(flutter_node_id);
+ if (it == nodes_.end()) {
+ inspect_node.CreateString(
+ "missing_child", "This node has a parent in the semantic tree but has no value", inspector);
+ inspector->emplace(std::move(inspect_node));
+ return;
+ }
+ const auto& semantic_node = it->second;
+ const auto& data = semantic_node.data;
+
+ inspect_node.CreateInt("id", data.id, inspector);
+ // Even with an empty label, we still want to create the property to
+ // explicetly show that it is empty.
+ inspect_node.CreateString("label", data.label, inspector);
+ if (strlen(data.hint) > 0) {
+ inspect_node.CreateString("hint", data.hint, inspector);
+ }
+ if (strlen(data.value) > 0) {
+ inspect_node.CreateString("value", data.value, inspector);
+ }
+ if (strlen(data.increased_value) > 0) {
+ inspect_node.CreateString("increased_value", data.increased_value, inspector);
+ }
+ if (strlen(data.decreased_value) > 0) {
+ inspect_node.CreateString("decreased_value", data.decreased_value, inspector);
+ }
+
+ if (data.text_direction) {
+ inspect_node.CreateString("text_direction", data.text_direction == 1 ? "RTL" : "LTR",
+ inspector);
+ }
+
+ if (data.flags) {
+ inspect_node.CreateString("flags", NodeFlagsToString(data), inspector);
+ }
+ if (data.actions) {
+ inspect_node.CreateString("actions", NodeActionsToString(data), inspector);
+ }
+
+ inspect_node.CreateString("location", NodeLocationToString(semantic_node.screen_rect), inspector);
+ if (data.child_count > 0) {
+ inspect_node.CreateString("children", NodeChildrenToString(data), inspector);
+ }
+
+ inspect_node.CreateInt("current_level", current_level, inspector);
+
+ for (size_t child = 0; child < semantic_node.data.child_count; child++) {
+ int32_t flutter_child_id = semantic_node.data.children_in_traversal_order[child];
+ const auto inspect_name = "node_" + std::to_string(flutter_child_id);
+ FillInspectTree(flutter_child_id, current_level + 1, inspect_node.CreateChild(inspect_name),
+ inspector);
+ }
+ inspector->emplace(std::move(inspect_node));
+}
+// #endif DEBUG
+
+void AccessibilityBridge::Apply(FuchsiaAtomicUpdate* atomic_update) {
+ size_t begin = 0;
+ auto it = atomic_update->deletions.begin();
+
+ // Process up to kMaxDeletionsPerUpdate deletions at a time.
+ while (it != atomic_update->deletions.end()) {
+ std::vector<uint32_t> to_delete;
+ size_t end = std::min(atomic_update->deletions.size() - begin, kMaxDeletionsPerUpdate);
+ std::copy(std::make_move_iterator(it), std::make_move_iterator(it + end),
+ std::back_inserter(to_delete));
+ tree_ptr_->DeleteSemanticNodes(std::move(to_delete));
+ begin = end;
+ it += end;
+ }
+
+ std::vector<fuchsia::accessibility::semantics::Node> to_update;
+ size_t current_size = 0;
+ for (auto& node_and_size : atomic_update->updates) {
+ if (current_size + node_and_size.second > kMaxMessageSize) {
+ tree_ptr_->UpdateSemanticNodes(std::move(to_update));
+ current_size = 0;
+ to_update.clear();
+ }
+ current_size += node_and_size.second;
+ to_update.push_back(std::move(node_and_size.first));
+ }
+ if (!to_update.empty()) {
+ tree_ptr_->UpdateSemanticNodes(std::move(to_update));
+ }
+
+ // Commit this update and subsequent ones; for flow control wait for a
+ // response between each commit.
+ tree_ptr_->CommitUpdates(
+ [this, atomic_updates = std::weak_ptr<std::queue<FuchsiaAtomicUpdate>>(atomic_updates_)]() {
+ auto atomic_updates_ptr = atomic_updates.lock();
+ if (!atomic_updates_ptr) {
+ // The queue no longer exists, which means that is no longer
+ // necessary.
+ return;
+ }
+ // Removes the update that just went through.
+ atomic_updates_ptr->pop();
+ if (!atomic_updates_ptr->empty()) {
+ Apply(&atomic_updates_ptr->front());
+ }
+ });
+
+ atomic_update->Clear();
+}
+
+void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeUpdate(
+ fuchsia::accessibility::semantics::Node node, size_t size) {
+ if (size > kMaxMessageSize) {
+ // TODO(MI4-2531, FIDL-718): Remove this
+ // This is defensive. If, despite our best efforts, we ended up with a node
+ // that is larger than the max fidl size, we send no updates.
+ PrintNodeSizeError(node.node_id());
+ return;
+ }
+ updates.emplace_back(std::move(node), size);
+}
+
+void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeDeletion(uint32_t id) {
+ deletions.push_back(id);
+}
+
+void AccessibilityBridge::FuchsiaAtomicUpdate::Clear() {
+ updates.clear();
+ deletions.clear();
+}
+} // namespace embedder
diff --git a/src/embedder/accessibility_bridge.h b/src/embedder/accessibility_bridge.h
new file mode 100644
index 0000000..99262ae
--- /dev/null
+++ b/src/embedder/accessibility_bridge.h
@@ -0,0 +1,281 @@
+// Copyright 2022 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.
+
+#ifndef SRC_ACCESSIBILITY_BRIDGE_H_
+#define SRC_ACCESSIBILITY_BRIDGE_H_
+
+#include <fuchsia/accessibility/semantics/cpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
+#include <fuchsia/ui/gfx/cpp/fidl.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/sys/inspect/cpp/component.h>
+#include <zircon/types.h>
+
+#include <memory>
+#include <optional>
+#include <queue>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "src/embedder/engine/embedder.h"
+
+namespace embedder {
+
+/// Accessibility bridge.
+///
+/// This class intermediates accessibility-related calls between Fuchsia and
+/// Flutter, translating and relaying requests between Flutter's
+/// platform-agnostic accessibility APIs and Fuchsia's APIs and behaviour.
+///
+/// This bridge performs the following functions, among others:
+///
+/// * Translates Flutter's semantics node updates to events Fuchsia requires
+/// (e.g. Flutter only sends updates for changed nodes, but Fuchsia requires
+/// the entire flattened subtree to be sent when a node changes.
+class AccessibilityBridge : public fuchsia::accessibility::semantics::SemanticListener {
+ public:
+ using SetSemanticsEnabledCallback = std::function<void(bool)>;
+ using DispatchSemanticsActionCallback = std::function<void(int32_t, FlutterSemanticsAction)>;
+
+ // TODO(MI4-2531, FIDL-718): Remove this. We shouldn't be worried about
+ // batching messages at this level.
+ // FIDL may encode a C++ struct as larger than the sizeof the C++ struct.
+ // This is to make sure we don't send updates that are too large.
+ static constexpr uint32_t kMaxMessageSize = ZX_CHANNEL_MAX_MSG_BYTES / 2;
+
+ static_assert(fuchsia::accessibility::semantics::MAX_LABEL_SIZE < kMaxMessageSize - 1);
+
+ /// Flutter uses signed 32 bit integers for node IDs, while Fuchsia uses
+ /// unsigned 32 bit integers. A change in the size on either one would break
+ /// casts and size tracking logic in the implementation.
+ static constexpr size_t kNodeIdSize = sizeof(FlutterSemanticsNode::id);
+ static_assert(kNodeIdSize == sizeof(fuchsia::accessibility::semantics::Node().node_id()),
+ "FlutterSemanticsNode::id and "
+ "fuchsia::accessibility::semantics::Node::node_id differ in size.");
+
+ /// Maximum number of node ids to be deleted through the Semantics API.
+ static constexpr size_t kMaxDeletionsPerUpdate = kMaxMessageSize / kNodeIdSize;
+
+ /// set_semantics_enabled_callback is the callback to be run following an
+ /// invocation of the OnSemanticsModeChanged FIDL interface from Fuchsia.
+ ///
+ /// dispatch_semantics_action_callback is the callback to be run following an
+ /// invocation of the OnAccessibilityActionRequested FIDL interface from Fuchsia.
+ ///
+ /// semantics_manager is the Fuchsia semantics manager component
+ ///
+ /// view_ref is the accessibility view reference initialized in main.cc
+ ///
+ /// inspect_node is a child node of Fuchsia's ComponentInspector
+ AccessibilityBridge(SetSemanticsEnabledCallback set_semantics_enabled_callback,
+ DispatchSemanticsActionCallback dispatch_semantics_action_callback,
+ fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager,
+ fuchsia::ui::views::ViewRef view_ref, inspect::Node inspect_node);
+
+ /// Disable Copy and Assignment
+ AccessibilityBridge(const AccessibilityBridge&) = delete;
+ AccessibilityBridge& operator=(const AccessibilityBridge&) = delete;
+
+ /// Returns true if accessible navigation is enabled.
+ bool GetSemanticsEnabled() const;
+
+ /// Enables Flutter accessibility navigation features.
+ ///
+ /// Once enabled, any semantics updates in the Flutter application will
+ /// trigger |FuchsiaAccessibility::DispatchAccessibilityEvent| callbacks
+ /// to send events back to the Fuchsia SemanticsManager.
+ void SetSemanticsEnabled(bool enabled);
+
+ /// Handle FlutterSemanticsNode from embedder API
+ void AddSemanticsUpdate(const FlutterSemanticsUpdate* update);
+
+ /// Handle platform message from the flutter engine
+ void HandlePlatformMessage(const FlutterPlatformMessage* message);
+
+ /// Requests a message announcement from the accessibility TTS system.
+ void RequestAnnounce(const std::string message);
+
+ /// Notifies the bridge of a 'hover move' touch exploration event.
+ zx_status_t OnHoverMove(double x, double y);
+
+ /// |fuchsia::accessibility::semantics::SemanticListener|
+ void HitTest(
+ fuchsia::math::PointF local_point,
+ fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) override;
+
+ /// |fuchsia::accessibility::semantics::SemanticListener|
+ void OnAccessibilityActionRequested(
+ uint32_t node_id, fuchsia::accessibility::semantics::Action action,
+ fuchsia::accessibility::semantics::SemanticListener::OnAccessibilityActionRequestedCallback
+ callback) override;
+
+ /// Set the internal pixel ratio
+ ///
+ /// This is the factor used to convert between a view’s logical
+ /// coordinate space (assigned by scenic) and its allocated buffer size
+ void SetPixelRatio(float ratio);
+
+ private:
+ /// Fuchsia's default root semantic node id.
+ static constexpr int32_t kRootNodeId = 0;
+
+ /// Represents an atomic semantic update to Fuchsia, which can contain deletes
+ /// and updates of semantic nodes.
+ ///
+ /// An atomic update is a set of operations that take a semantic tree in a
+ /// valid state to another valid state. Please check the semantics FIDL
+ /// documentation for details.
+ struct FuchsiaAtomicUpdate {
+ FuchsiaAtomicUpdate() = default;
+ ~FuchsiaAtomicUpdate() = default;
+ FuchsiaAtomicUpdate(FuchsiaAtomicUpdate&&) = default;
+
+ /// Adds a node to be updated. |size| contains the
+ /// size in bytes of the node to be updated.
+ void AddNodeUpdate(fuchsia::accessibility::semantics::Node node, size_t size);
+
+ /// Adds a node to be deleted.
+ void AddNodeDeletion(uint32_t id);
+
+ /// clear the updates and deletions vectors
+ void Clear();
+
+ std::vector<std::pair<fuchsia::accessibility::semantics::Node, size_t>> updates;
+ std::vector<uint32_t> deletions;
+ };
+
+ /// Holds a flutter semantic node and some extra info.
+ /// In particular, it adds a screen_rect field to FlutterSemanticsNode.
+ struct SemanticsNode {
+ FlutterSemanticsNode data;
+ FlutterRect screen_rect;
+ };
+
+ /// Adds a semantics node update to the buffer of node updates to apply.
+ void AddSemanticsNodeUpdates(const FlutterSemanticsNode* update, size_t count);
+
+ fuchsia::accessibility::semantics::Node GetRootNodeUpdate(size_t& node_size);
+
+ /// Derives the BoundingBox of a Flutter semantics node from its
+ /// rect and elevation.
+ fuchsia::ui::gfx::BoundingBox GetNodeLocation(const FlutterSemanticsNode& node) const;
+
+ /// Gets mat4 transformation from a Flutter semantics node.
+ fuchsia::ui::gfx::mat4 GetNodeTransform(const FlutterSemanticsNode& node) const;
+
+ /// Converts a Flutter semantics node's transformation to a mat4.
+ fuchsia::ui::gfx::mat4 ConvertFlutterTransformToMat4(const FlutterTransformation transform) const;
+
+ /// Derives the attributes for a Fuchsia semantics node from a Flutter
+ /// semantics node.
+ fuchsia::accessibility::semantics::Attributes GetNodeAttributes(const FlutterSemanticsNode& node,
+ size_t* added_size) const;
+
+ /// Derives the states for a Fuchsia semantics node from a Flutter semantics
+ /// node.
+ fuchsia::accessibility::semantics::States GetNodeStates(const FlutterSemanticsNode& node,
+ size_t* additional_size) const;
+
+ /// Derives the set of supported actions for a Fuchsia semantics node from
+ /// a Flutter semantics node.
+ std::vector<fuchsia::accessibility::semantics::Action> GetNodeActions(
+ const FlutterSemanticsNode& node, size_t* additional_size) const;
+
+ /// Derives the role for a Fuchsia semantics node from a Flutter
+ /// semantics node.
+ fuchsia::accessibility::semantics::Role GetNodeRole(const FlutterSemanticsNode& node) const;
+
+ /// Gets the set of reachable descendants from the given node id.
+ std::unordered_set<int32_t> GetDescendants(int32_t node_id) const;
+
+ /// Removes internal references to any dangling nodes from previous
+ /// updates, and adds the nodes to be deleted to the current |atomic_update|.
+ ///
+ /// The node ids to be deleted are only collected at this point, and will be
+ /// committed in the next call to |Apply()|.
+ void PruneUnreachableNodes(FuchsiaAtomicUpdate* atomic_update);
+
+ /// Updates the on-screen positions of accessibility elements,
+ /// starting from the root element with an identity matrix.
+ ///
+ /// This should be called from Update.
+ void UpdateScreenRects();
+
+ /// Updates the on-screen positions of accessibility elements, starting
+ /// from node_id and using the specified transform.
+ ///
+ /// Update calls this via UpdateScreenRects().
+ void UpdateScreenRects(int32_t node_id, FlutterTransformation parent_transform,
+ std::unordered_set<int32_t>* visited_nodes);
+
+ /// Traverses the semantics tree to find the node_id hit by the given x,y
+ /// point.
+ ///
+ /// Assumes that SemanticsNode::screen_rect is up to date.
+ std::optional<int32_t> GetHitNode(int32_t node_id, float x, float y);
+
+ /// Returns whether the node is considered focusable.
+ bool IsFocusable(const FlutterSemanticsNode& node) const;
+
+ /// Converts a fuchsia::accessibility::semantics::Action to a
+ /// flutterSemanticsAction.
+ ///
+ /// The node_id parameter is used for printing warnings about unsupported
+ /// action types.
+ std::optional<FlutterSemanticsAction> GetFlutterSemanticsAction(
+ fuchsia::accessibility::semantics::Action fuchsia_action, uint32_t node_id);
+
+ /// Applies the updates and deletions in |atomic_update|, sending them via
+ /// |tree_ptr|.
+ void Apply(FuchsiaAtomicUpdate* atomic_update);
+
+ /// |fuchsia::accessibility::semantics::SemanticListener|
+ void OnSemanticsModeChanged(bool enabled, OnSemanticsModeChangedCallback callback) override;
+
+ // TODO(benbergkamp)
+ // #if DEBUG
+ /// Fills the inspect tree with debug information about the semantic tree.
+ void FillInspectTree(int32_t flutter_node_id, int32_t current_level, inspect::Node inspect_node,
+ inspect::Inspector* inspector) const;
+ // #endif // DEBUG
+
+ SetSemanticsEnabledCallback set_semantics_enabled_callback_;
+ DispatchSemanticsActionCallback dispatch_semantics_action_callback_;
+ FlutterSemanticsNode root_flutter_semantics_node_;
+ /// The pixel ratio used in the most recent AddSemanticsNodeUpdates function call
+ float last_seen_view_pixel_ratio_ = 1.f;
+ /// The pixel ratio that will be used in the next function call to AddSemanticsNodeUpdates
+ /// Need to differenciate from last_seen, because there is special logic for handling a
+ /// change in the pixel ratio
+ float next_pixel_ratio_ = 1.f;
+
+ fidl::Binding<fuchsia::accessibility::semantics::SemanticListener> binding_;
+ fuchsia::accessibility::semantics::SemanticsManagerPtr fuchsia_semantics_manager_;
+ fuchsia::accessibility::semantics::SemanticTreePtr tree_ptr_;
+
+ /// This is the cache of all nodes we've sent to Fuchsia's SemanticsManager.
+ /// Assists with pruning unreachable nodes and hit testing.
+ std::unordered_map<int32_t, SemanticsNode> nodes_;
+ bool semantics_enabled_;
+
+ /// Queue of atomic updates to be sent to Fuchsia.
+ std::shared_ptr<std::queue<FuchsiaAtomicUpdate>> atomic_updates_;
+ FuchsiaAtomicUpdate current_atomic_update_;
+
+ /// Node to publish inspect data.
+ inspect::Node inspect_node_;
+
+ // TODO(benbergkamp)
+ // #if DEBUG
+ /// Inspect node to store a dump of the semantic tree. Note that this only gets
+ /// computed if requested, so it does not use memory to store the dump unless
+ /// an explicit request is made.
+ inspect::LazyNode inspect_node_tree_dump_;
+ // #endif // DEBUG
+};
+
+} // namespace embedder
+
+#endif // SRC_ACCESSIBILITY_BRIDGE_H_
diff --git a/src/embedder/embedder_state.h b/src/embedder/embedder_state.h
index 00839e5..d08ee83 100644
--- a/src/embedder/embedder_state.h
+++ b/src/embedder/embedder_state.h
@@ -12,6 +12,7 @@
#include <optional>
+#include "src/embedder/accessibility_bridge.h"
#include "src/embedder/engine/embedder.h"
#include "src/embedder/flatland_connection.h"
#include "src/embedder/software_surface.h"
@@ -76,6 +77,10 @@
/// that supports multiple surfaces. See
/// https://github.com/flutter/engine/blob/main/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc#L108.
std::unique_ptr<SoftwareSurface> software_surface;
+
+ /// The accessibility bridge responsible for intermediating
+ /// accessibility-related calls between Fuchsia and Flutter
+ std::unique_ptr<AccessibilityBridge> accessibility_bridge_;
};
} // namespace embedder
diff --git a/src/embedder/engine/debug_x64/libflutter_engine.so b/src/embedder/engine/debug_x64/libflutter_engine.so
index 242e0f5..c2c4fe3 100755
--- a/src/embedder/engine/debug_x64/libflutter_engine.so
+++ b/src/embedder/engine/debug_x64/libflutter_engine.so
Binary files differ
diff --git a/src/embedder/flatland_view_provider.h b/src/embedder/flatland_view_provider.h
index d5cdce5..144c78b 100644
--- a/src/embedder/flatland_view_provider.h
+++ b/src/embedder/flatland_view_provider.h
@@ -23,12 +23,15 @@
/// Implements ViewProvider by using Flatland to create a view.
class FlatlandViewProvider final : public fuchsia::ui::app::ViewProvider {
public:
+ using PixelRatioCallback = std::function<void(float)>;
+
FlatlandViewProvider(FlatlandConnection* flatland_connection, scenic::ViewRefPair view_ref_pair,
fuchsia::ui::composition::ViewBoundProtocols flatland_view_protocols)
: flatland_connection_(flatland_connection),
flatland_view_protocols_(std::move(flatland_view_protocols)) {
view_identity_ = {.view_ref = std::move(view_ref_pair.view_ref),
.view_ref_control = std::move(view_ref_pair.control_ref)};
+ pixel_ratio_callback_ = [](float) {};
}
~FlatlandViewProvider() override {}
@@ -55,13 +58,25 @@
flatland->CreateTransform({kRootTransformId});
flatland->SetRootTransform({kRootTransformId});
+
+ parent_viewport_watcher_->GetLayout(fit::bind_member(this, &FlatlandViewProvider::OnGetLayout));
}
+ void OnGetLayout(fuchsia::ui::composition::LayoutInfo info) {
+ if (info.has_device_pixel_ratio()) {
+ pixel_ratio_callback_(info.device_pixel_ratio().x);
+ }
+ parent_viewport_watcher_->GetLayout(fit::bind_member(this, &FlatlandViewProvider::OnGetLayout));
+ }
+
+ void SetPixelRatioCallback(PixelRatioCallback callback) { pixel_ratio_callback_ = callback; }
+
private:
FlatlandConnection* flatland_connection_ = nullptr;
fuchsia::ui::composition::ViewBoundProtocols flatland_view_protocols_;
fuchsia::ui::views::ViewIdentityOnCreation view_identity_;
fuchsia::ui::composition::ParentViewportWatcherPtr parent_viewport_watcher_;
+ PixelRatioCallback pixel_ratio_callback_;
};
} // namespace embedder
diff --git a/src/embedder/main.cc b/src/embedder/main.cc
index e6750c1..548bafb 100644
--- a/src/embedder/main.cc
+++ b/src/embedder/main.cc
@@ -6,6 +6,7 @@
//
// Usage: ./main <path_to_flutter_asset_bundle>
+#include <fuchsia/accessibility/semantics/cpp/fidl.h>
#include <fuchsia/fonts/cpp/fidl.h>
#include <fuchsia/scenic/scheduling/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
@@ -25,20 +26,27 @@
#include <string>
+#include "src/embedder/accessibility_bridge.h"
#include "src/embedder/embedder_state.h"
#include "src/embedder/engine/embedder.h"
#include "src/embedder/flatland_ids.h"
#include "src/embedder/flatland_view_provider.h"
+#include "src/embedder/fuchsia_logger.h"
#include "src/embedder/logging.h"
#include "src/embedder/mouse_delegate.h"
+#include "src/embedder/root_inspect_node.h"
#include "src/embedder/software_surface.h"
#include "src/embedder/text_delegate.h"
#include "src/embedder/touch_delegate.h"
-#include "src/embedder/fuchsia_logger.h"
namespace embedder {
namespace {
+/// Platform messages sent from the flutter engine include a
+/// channel to indicate what type of message they are. This is
+/// the name for the accessibility channel
+constexpr char kAccessibilityChannel[] = "flutter/accessibility";
+
// TODO(akbiggs): Don't hardcode this screen size, get it from
// an event instead.
constexpr int kScreenWidth = 1280;
@@ -72,6 +80,29 @@
return info.koid;
}
+/// Return kernel object id for fuchsia view ref
+zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) {
+ zx_handle_t handle = view_ref.reference.get();
+ zx_info_handle_basic_t info;
+ zx_status_t status =
+ zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
+ return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
+}
+
+/// Handle platform message from dart application, forwarded from flutter engine
+void FuchsiaHandlePlatformMessage(const FlutterPlatformMessage* message, void* user_data) {
+ if (!strcmp(message->channel, kAccessibilityChannel)) {
+ EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
+ embedder->accessibility_bridge_->HandlePlatformMessage(message);
+ }
+}
+
+/// Fuchsia implementation of the Flutter Embedder update_semantics_callback
+void FuchsiaHandleSemanticsUpdate(const FlutterSemanticsUpdate* update, void* user_data) {
+ EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
+ embedder->accessibility_bridge_->AddSemanticsUpdate(update);
+}
+
/// Fuchsia implementation of the Flutter Engine's |vsync_callback|.
void FuchsiaVsync(void* user_data, intptr_t baton) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
@@ -199,13 +230,12 @@
FlutterProjectArgs project_args = {
.struct_size = sizeof(FlutterProjectArgs),
.assets_path = assets_path,
-
+ .platform_message_callback = FuchsiaHandlePlatformMessage,
.vsync_callback = FuchsiaVsync,
.log_message_callback = FuchsiaLogMessage,
-
.log_tag = "flutter_app",
- .font_initialization_data = sync_font_provider.Unbind().TakeChannel().release(),
- };
+ .update_semantics_callback = FuchsiaHandleSemanticsUpdate,
+ .font_initialization_data = sync_font_provider.Unbind().TakeChannel().release()};
FlutterEngineResult result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config,
&project_args, embedder, &embedder->engine);
@@ -324,6 +354,8 @@
fuchsia::ui::views::ViewRef platform_view_ref;
view_ref_pair.view_ref.Clone(&platform_view_ref);
+ fuchsia::ui::views::ViewRef accessibility_view_ref;
+ view_ref_pair.view_ref.Clone(&accessibility_view_ref);
// Keyboard/Text input.
auto text_delegate = std::make_unique<embedder::TextDelegate>(
@@ -398,7 +430,42 @@
return EXIT_FAILURE;
}
- // TODO(benbergkamp): temporary workaround
+ embedder::AccessibilityBridge::SetSemanticsEnabledCallback set_semantics_enabled_callback =
+ [&embedder](bool enabled) { FlutterEngineUpdateSemanticsEnabled(embedder.engine, enabled); };
+
+ embedder::AccessibilityBridge::DispatchSemanticsActionCallback
+ dispatch_semantics_action_callback =
+ [&embedder](int32_t node_id, FlutterSemanticsAction action) {
+ FlutterEngineDispatchSemanticsAction(embedder.engine, node_id, action, nullptr, 0);
+ };
+
+ const std::string accessibility_inspect_name =
+ std::to_string(embedder::GetKoid(accessibility_view_ref));
+
+ // Connect to SemanticsManager service.
+ fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager;
+
+ status = embedder.component_context->svc()
+ ->Connect<fuchsia::accessibility::semantics::SemanticsManager>(
+ semantics_manager.NewRequest());
+
+ if (status != ZX_OK) {
+ FX_LOGF(ERROR, embedder::kLogTag,
+ "fuchsia::accessibility::semantics::SemanticsManager connection failed: %s",
+ zx_status_get_string(status));
+ }
+
+ dart_utils::RootInspectNode::Initialize(embedder.component_context.get());
+
+ embedder.accessibility_bridge_ = std::make_unique<embedder::AccessibilityBridge>(
+ std::move(set_semantics_enabled_callback), std::move(dispatch_semantics_action_callback),
+ std::move(semantics_manager), std::move(accessibility_view_ref),
+ dart_utils::RootInspectNode::CreateRootChild(std::move(accessibility_inspect_name)));
+
+ view_provider.SetPixelRatioCallback(
+ [&embedder](float ratio) { embedder.accessibility_bridge_->SetPixelRatio(ratio); });
+
+ // TODO(benbergkamp): temporary a workaround
FlutterEngineRunMessageLoop();
FX_LOG(INFO, embedder::kLogTag, "Done looping.");
diff --git a/src/embedder/meta/embedder.cml b/src/embedder/meta/embedder.cml
index 97c2788..2662592 100644
--- a/src/embedder/meta/embedder.cml
+++ b/src/embedder/meta/embedder.cml
@@ -62,6 +62,7 @@
// For connecting to the Keyboard service.
"fuchsia.ui.input3.Keyboard",
"fuchsia.ui.scenic.Scenic",
+ "fuchsia.accessibility.semantics.SemanticsManager",
]
},
],
diff --git a/src/embedder/root_inspect_node.cc b/src/embedder/root_inspect_node.cc
new file mode 100644
index 0000000..d5e3a8d
--- /dev/null
+++ b/src/embedder/root_inspect_node.cc
@@ -0,0 +1,26 @@
+// Copyright 2022 The Flutter 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 "root_inspect_node.h"
+
+namespace dart_utils {
+
+std::unique_ptr<sys::ComponentInspector> RootInspectNode::inspector_;
+std::mutex RootInspectNode::mutex_;
+
+void RootInspectNode::Initialize(sys::ComponentContext* context) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (!inspector_) {
+ inspector_ = std::make_unique<sys::ComponentInspector>(context);
+ }
+}
+
+inspect::Node RootInspectNode::CreateRootChild(const std::string& name) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return inspector_->inspector()->GetRoot().CreateChild(name);
+}
+
+inspect::Inspector* RootInspectNode::GetInspector() { return inspector_->inspector(); }
+
+} // namespace dart_utils
diff --git a/src/embedder/root_inspect_node.h b/src/embedder/root_inspect_node.h
new file mode 100644
index 0000000..32f77d3
--- /dev/null
+++ b/src/embedder/root_inspect_node.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_ROOT_INSPECT_NODE_H_
+#define SRC_EMBEDDER_ROOT_INSPECT_NODE_H_
+
+#include <lib/sys/inspect/cpp/component.h>
+
+#include <memory>
+#include <mutex>
+
+namespace dart_utils {
+
+// This singleton object offers a way to create a new inspect node under the
+// root node in a thread safe way.
+//
+// Usage notes:
+// RootInspectNode::Initialize() must be invoked once in the program's
+// main before trying to obtain a child node.
+class RootInspectNode {
+ private:
+ RootInspectNode() = default;
+ ~RootInspectNode() = default;
+
+ public:
+ // Initializes the underlying component inspector. Must be invoked at least
+ // once before calling CreateRootChild().
+ static void Initialize(sys::ComponentContext* context);
+
+ // Creates an inspect node which is a child of the component's root inspect
+ // node with the provided |name|.
+ static inspect::Node CreateRootChild(const std::string& name);
+
+ // Gets an instance to the component's inspector.
+ static inspect::Inspector* GetInspector();
+
+ private:
+ static std::unique_ptr<sys::ComponentInspector> inspector_;
+ static std::mutex mutex_;
+};
+
+} // namespace dart_utils
+
+#endif // SRC_EMBEDDER_ROOT_INSPECT_NODE_H_
diff --git a/src/embedder/standard_message_codec/byte_buffer_streams.h b/src/embedder/standard_message_codec/byte_buffer_streams.h
new file mode 100644
index 0000000..3293b37
--- /dev/null
+++ b/src/embedder/standard_message_codec/byte_buffer_streams.h
@@ -0,0 +1,98 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_BYTE_BUFFER_STREAMS_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_BYTE_BUFFER_STREAMS_H_
+
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <iostream>
+#include <vector>
+
+#include "byte_streams.h"
+
+namespace standard_message_codec {
+
+// Implementation of ByteStreamReader base on a byte array.
+class ByteBufferStreamReader : public ByteStreamReader {
+ public:
+ // Createa a reader reading from |bytes|, which must have a length of |size|.
+ // |bytes| must remain valid for the lifetime of this object.
+ explicit ByteBufferStreamReader(const uint8_t* bytes, size_t size) : bytes_(bytes), size_(size) {}
+
+ virtual ~ByteBufferStreamReader() = default;
+
+ // |ByteStreamReader|
+ uint8_t ReadByte() override {
+ if (location_ >= size_) {
+ std::cerr << "Invalid read in StandardCodecByteStreamReader" << std::endl;
+ return 0;
+ }
+ return bytes_[location_++];
+ }
+
+ // |ByteStreamReader|
+ void ReadBytes(uint8_t* buffer, size_t length) override {
+ if (location_ + length > size_) {
+ std::cerr << "Invalid read in StandardCodecByteStreamReader" << std::endl;
+ return;
+ }
+ std::memcpy(buffer, &bytes_[location_], length);
+ location_ += length;
+ }
+
+ // |ByteStreamReader|
+ void ReadAlignment(uint8_t alignment) override {
+ uint8_t mod = location_ % alignment;
+ if (mod) {
+ location_ += alignment - mod;
+ }
+ }
+
+ private:
+ // The buffer to read from.
+ const uint8_t* bytes_;
+ // The total size of the buffer.
+ size_t size_;
+ // The current read location.
+ size_t location_ = 0;
+};
+
+// Implementation of ByteStreamWriter based on a byte array.
+class ByteBufferStreamWriter : public ByteStreamWriter {
+ public:
+ // Creates a writer that writes into |buffer|.
+ // |buffer| must remain valid for the lifetime of this object.
+ explicit ByteBufferStreamWriter(std::vector<uint8_t>* buffer) : bytes_(buffer) { assert(buffer); }
+
+ virtual ~ByteBufferStreamWriter() = default;
+
+ // |ByteStreamWriter|
+ void WriteByte(uint8_t byte) { bytes_->push_back(byte); }
+
+ // |ByteStreamWriter|
+ void WriteBytes(const uint8_t* bytes, size_t length) {
+ assert(length > 0);
+ bytes_->insert(bytes_->end(), bytes, bytes + length);
+ }
+
+ // |ByteStreamWriter|
+ void WriteAlignment(uint8_t alignment) {
+ uint8_t mod = bytes_->size() % alignment;
+ if (mod) {
+ for (int i = 0; i < alignment - mod; ++i) {
+ WriteByte(0);
+ }
+ }
+ }
+
+ private:
+ // The buffer to write to.
+ std::vector<uint8_t>* bytes_;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_BYTE_BUFFER_STREAMS_H_
diff --git a/src/embedder/standard_message_codec/byte_streams.h b/src/embedder/standard_message_codec/byte_streams.h
new file mode 100644
index 0000000..11652c2
--- /dev/null
+++ b/src/embedder/standard_message_codec/byte_streams.h
@@ -0,0 +1,79 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_BYTE_STREAMS_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_BYTE_STREAMS_H_
+
+// Interfaces for interacting with a stream of bytes, for use in codecs.
+
+namespace standard_message_codec {
+
+// An interface for a class that reads from a byte stream.
+class ByteStreamReader {
+ public:
+ explicit ByteStreamReader() = default;
+ virtual ~ByteStreamReader() = default;
+
+ // Reads and returns the next byte from the stream.
+ virtual uint8_t ReadByte() = 0;
+
+ // Reads the next |length| bytes from the stream into |buffer|. The caller
+ // is responsible for ensuring that |buffer| is large enough.
+ virtual void ReadBytes(uint8_t* buffer, size_t length) = 0;
+
+ // Advances the read cursor to the next multiple of |alignment| relative to
+ // the start of the stream, unless it is already aligned.
+ virtual void ReadAlignment(uint8_t alignment) = 0;
+
+ // Reads and returns the next 32-bit integer from the stream.
+ int32_t ReadInt32() {
+ int32_t value = 0;
+ ReadBytes(reinterpret_cast<uint8_t*>(&value), 4);
+ return value;
+ }
+
+ // Reads and returns the next 64-bit integer from the stream.
+ int64_t ReadInt64() {
+ int64_t value = 0;
+ ReadBytes(reinterpret_cast<uint8_t*>(&value), 8);
+ return value;
+ }
+
+ // Reads and returns the next 64-bit floating point number from the stream.
+ double ReadDouble() {
+ double value = 0;
+ ReadBytes(reinterpret_cast<uint8_t*>(&value), 8);
+ return value;
+ }
+};
+
+// An interface for a class that writes to a byte stream.
+class ByteStreamWriter {
+ public:
+ explicit ByteStreamWriter() = default;
+ virtual ~ByteStreamWriter() = default;
+
+ // Writes |byte| to the stream.
+ virtual void WriteByte(uint8_t byte) = 0;
+
+ // Writes the next |length| bytes from |bytes| to the stream
+ virtual void WriteBytes(const uint8_t* bytes, size_t length) = 0;
+
+ // Writes 0s until the next multiple of |alignment| relative to the start
+ // of the stream, unless the write positition is already aligned.
+ virtual void WriteAlignment(uint8_t alignment) = 0;
+
+ // Writes the given 32-bit int to the stream.
+ void WriteInt32(int32_t value) { WriteBytes(reinterpret_cast<const uint8_t*>(&value), 4); }
+
+ // Writes the given 64-bit int to the stream.
+ void WriteInt64(int64_t value) { WriteBytes(reinterpret_cast<const uint8_t*>(&value), 8); }
+
+ // Writes the given 36-bit double to the stream.
+ void WriteDouble(double value) { WriteBytes(reinterpret_cast<const uint8_t*>(&value), 8); }
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_BYTE_STREAMS_H_
diff --git a/src/embedder/standard_message_codec/encodable_value.h b/src/embedder/standard_message_codec/encodable_value.h
new file mode 100644
index 0000000..09c005b
--- /dev/null
+++ b/src/embedder/standard_message_codec/encodable_value.h
@@ -0,0 +1,208 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_ENCODABLE_VALUE_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_ENCODABLE_VALUE_H_
+
+#include <any>
+#include <cassert>
+#include <cstdint>
+#include <map>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+// Unless overridden, attempt to detect the RTTI state from the compiler.
+#ifndef FLUTTER_ENABLE_RTTI
+#if defined(_MSC_VER)
+#ifdef _CPPRTTI
+#define FLUTTER_ENABLE_RTTI 1
+#endif
+#elif defined(__clang__)
+#if __has_feature(cxx_rtti)
+#define FLUTTER_ENABLE_RTTI 1
+#endif
+#elif defined(__GNUC__)
+#ifdef __GXX_RTTI
+#define FLUTTER_ENABLE_RTTI 1
+#endif
+#endif
+#endif // #ifndef FLUTTER_ENABLE_RTTI
+
+namespace standard_message_codec {
+
+static_assert(sizeof(double) == 8, "EncodableValue requires a 64-bit double");
+
+// A container for arbitrary types in EncodableValue.
+//
+// This is used in conjunction with StandardCodecExtension to allow using other
+// types with a StandardMethodCodec/StandardMessageCodec. It is implicitly
+// convertible to EncodableValue, so constructing an EncodableValue from a
+// custom type can generally be written as:
+// CustomEncodableValue(MyType(...))
+// rather than:
+// EncodableValue(CustomEncodableValue(MyType(...)))
+//
+// For extracting received custom types, it is implicitly convertible to
+// std::any. For example:
+// const MyType& my_type_value =
+// std::any_cast<MyType>(std::get<CustomEncodableValue>(value));
+//
+// If RTTI is enabled, different extension types can be checked with type():
+// if (custom_value->type() == typeid(SomeData)) { ... }
+// Clients that wish to disable RTTI would need to decide on another approach
+// for distinguishing types (e.g., in StandardCodecExtension::WriteValueOfType)
+// if multiple custom types are needed. For instance, wrapping all of the
+// extension types in an EncodableValue-style variant, and only ever storing
+// that variant in CustomEncodableValue.
+class CustomEncodableValue {
+ public:
+ explicit CustomEncodableValue(const std::any& value) : value_(value) {}
+ ~CustomEncodableValue() = default;
+
+ // Allow implicit conversion to std::any to allow direct use of any_cast.
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ operator std::any&() { return value_; }
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ operator const std::any&() const { return value_; }
+
+#if defined(FLUTTER_ENABLE_RTTI) && FLUTTER_ENABLE_RTTI
+ // Passthrough to std::any's type().
+ const std::type_info& type() const noexcept { return value_.type(); }
+#endif
+
+ // This operator exists only to provide a stable ordering for use as a
+ // std::map key, to satisfy the compiler requirements for EncodableValue.
+ // It does not attempt to provide useful ordering semantics, and using a
+ // custom value as a map key is not recommended.
+ bool operator<(const CustomEncodableValue& other) const { return this < &other; }
+ bool operator==(const CustomEncodableValue& other) const { return this == &other; }
+
+ private:
+ std::any value_;
+};
+
+class EncodableValue;
+
+// Convenience type aliases.
+using EncodableList = std::vector<EncodableValue>;
+using EncodableMap = std::map<EncodableValue, EncodableValue>;
+
+namespace internal {
+// The base class for EncodableValue. Do not use this directly; it exists only
+// for EncodableValue to inherit from.
+//
+// Do not change the order or indexes of the items here; see the comment on
+// EncodableValue
+using EncodableValueVariant =
+ std::variant<std::monostate, bool, int32_t, int64_t, double, std::string, std::vector<uint8_t>,
+ std::vector<int32_t>, std::vector<int64_t>, std::vector<double>, EncodableList,
+ EncodableMap, CustomEncodableValue, std::vector<float>>;
+} // namespace internal
+
+// An object that can contain any value or collection type supported by
+// Flutter's standard method codec.
+//
+// For details, see:
+// https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html
+//
+// As an example, the following Dart structure:
+// {
+// 'flag': true,
+// 'name': 'Thing',
+// 'values': [1, 2.0, 4],
+// }
+// would correspond to:
+// EncodableValue(EncodableMap{
+// {EncodableValue("flag"), EncodableValue(true)},
+// {EncodableValue("name"), EncodableValue("Thing")},
+// {EncodableValue("values"), EncodableValue(EncodableList{
+// EncodableValue(1),
+// EncodableValue(2.0),
+// EncodableValue(4),
+// })},
+// })
+//
+// The primary API surface for this object is std::variant. For instance,
+// getting a string value from an EncodableValue, with type checking:
+// if (std::holds_alternative<std::string>(value)) {
+// std::string some_string = std::get<std::string>(value);
+// }
+//
+// The order/indexes of the variant types is part of the API surface, and is
+// guaranteed not to change.
+//
+// The variant types are mapped with Dart types in following ways:
+// std::monostate -> null
+// bool -> bool
+// int32_t -> int
+// int64_t -> int
+// double -> double
+// std::string -> String
+// std::vector<uint8_t> -> Uint8List
+// std::vector<int32_t> -> Int32List
+// std::vector<int64_t> -> Int64List
+// std::vector<float> -> Float32List
+// std::vector<double> -> Float64List
+// EncodableList -> List
+// EncodableMap -> Map
+class EncodableValue : public internal::EncodableValueVariant {
+ public:
+ // Rely on std::variant for most of the constructors/operators.
+ using super = internal::EncodableValueVariant;
+ using super::super;
+ using super::operator=;
+
+ explicit EncodableValue() = default;
+
+ // Avoid the C++17 pitfall of conversion from char* to bool. Should not be
+ // needed for C++20.
+ explicit EncodableValue(const char* string) : super(std::string(string)) {}
+ EncodableValue& operator=(const char* other) {
+ *this = std::string(other);
+ return *this;
+ }
+
+ // Allow implicit conversion from CustomEncodableValue; the only reason to
+ // make a CustomEncodableValue (which can only be constructed explicitly) is
+ // to use it with EncodableValue, so the risk of unintended conversions is
+ // minimal, and it avoids the need for the verbose:
+ // EncodableValue(CustomEncodableValue(...)).
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ EncodableValue(const CustomEncodableValue& v) : super(v) {}
+
+ // Override the conversion constructors from std::variant to make them
+ // explicit, to avoid implicit conversion.
+ //
+ // While implicit conversion can be convenient in some cases, it can have very
+ // surprising effects. E.g., calling a function that takes an EncodableValue
+ // but accidentally passing an EncodableValue* would, instead of failing to
+ // compile, go through a pointer->bool->EncodableValue(bool) chain and
+ // silently call the function with a temp-constructed EncodableValue(true).
+ template <class T>
+ constexpr explicit EncodableValue(T&& t) noexcept : super(t) {}
+
+ // Returns true if the value is null. Convenience wrapper since unlike the
+ // other types, std::monostate uses aren't self-documenting.
+ bool IsNull() const { return std::holds_alternative<std::monostate>(*this); }
+
+ // Convenience method to simplify handling objects received from Flutter
+ // where the values may be larger than 32-bit, since they have the same type
+ // on the Dart side, but will be either 32-bit or 64-bit here depending on
+ // the value.
+ //
+ // Calling this method if the value doesn't contain either an int32_t or an
+ // int64_t will throw an exception.
+ int64_t LongValue() const {
+ if (std::holds_alternative<int32_t>(*this)) {
+ return std::get<int32_t>(*this);
+ }
+ return std::get<int64_t>(*this);
+ }
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_ENCODABLE_VALUE_H_
diff --git a/src/embedder/standard_message_codec/message_codec.h b/src/embedder/standard_message_codec/message_codec.h
new file mode 100644
index 0000000..1b472c0
--- /dev/null
+++ b/src/embedder/standard_message_codec/message_codec.h
@@ -0,0 +1,58 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_MESSAGE_CODEC_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_MESSAGE_CODEC_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace standard_message_codec {
+
+// Translates between a binary message and higher-level method call and
+// response/error objects.
+template <typename T>
+class MessageCodec {
+ public:
+ MessageCodec() = default;
+
+ virtual ~MessageCodec() = default;
+
+ // Prevent copying.
+ MessageCodec(MessageCodec<T> const&) = delete;
+ MessageCodec& operator=(MessageCodec<T> const&) = delete;
+
+ // Returns the message encoded in |binary_message|, or nullptr if it cannot be
+ // decoded by this codec.
+ std::unique_ptr<T> DecodeMessage(const uint8_t* binary_message, const size_t message_size) const {
+ return std::move(DecodeMessageInternal(binary_message, message_size));
+ }
+
+ // Returns the message encoded in |binary_message|, or nullptr if it cannot be
+ // decoded by this codec.
+ std::unique_ptr<T> DecodeMessage(const std::vector<uint8_t>& binary_message) const {
+ size_t size = binary_message.size();
+ const uint8_t* data = size > 0 ? &binary_message[0] : nullptr;
+ return std::move(DecodeMessageInternal(data, size));
+ }
+
+ // Returns a binary encoding of the given |message|, or nullptr if the
+ // message cannot be serialized by this codec.
+ std::unique_ptr<std::vector<uint8_t>> EncodeMessage(const T& message) const {
+ return std::move(EncodeMessageInternal(message));
+ }
+
+ protected:
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual std::unique_ptr<T> DecodeMessageInternal(const uint8_t* binary_message,
+ const size_t message_size) const = 0;
+
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual std::unique_ptr<std::vector<uint8_t>> EncodeMessageInternal(const T& message) const = 0;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_MESSAGE_CODEC_H_
diff --git a/src/embedder/standard_message_codec/method_call.h b/src/embedder/standard_message_codec/method_call.h
new file mode 100644
index 0000000..f86a00b
--- /dev/null
+++ b/src/embedder/standard_message_codec/method_call.h
@@ -0,0 +1,43 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_CALL_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_CALL_H_
+
+#include <memory>
+#include <string>
+
+namespace standard_message_codec {
+
+class EncodableValue;
+
+// An object encapsulating a method call from Flutter whose arguments are of
+// type T.
+template <typename T = EncodableValue>
+class MethodCall {
+ public:
+ // Creates a MethodCall with the given name and arguments.
+ MethodCall(const std::string& method_name, std::unique_ptr<T> arguments)
+ : method_name_(method_name), arguments_(std::move(arguments)) {}
+
+ virtual ~MethodCall() = default;
+
+ // Prevent copying.
+ MethodCall(MethodCall<T> const&) = delete;
+ MethodCall& operator=(MethodCall<T> const&) = delete;
+
+ // The name of the method being called.
+ const std::string& method_name() const { return method_name_; }
+
+ // The arguments to the method call, or NULL if there are none.
+ const T* arguments() const { return arguments_.get(); }
+
+ private:
+ std::string method_name_;
+ std::unique_ptr<T> arguments_;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_CALL_H_
diff --git a/src/embedder/standard_message_codec/method_codec.h b/src/embedder/standard_message_codec/method_codec.h
new file mode 100644
index 0000000..2a22652
--- /dev/null
+++ b/src/embedder/standard_message_codec/method_codec.h
@@ -0,0 +1,101 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_CODEC_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_CODEC_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "method_call.h"
+#include "method_result.h"
+
+namespace standard_message_codec {
+
+// Translates between a binary message and higher-level method call and
+// response/error objects.
+template <typename T>
+class MethodCodec {
+ public:
+ MethodCodec() = default;
+
+ virtual ~MethodCodec() = default;
+
+ // Prevent copying.
+ MethodCodec(MethodCodec<T> const&) = delete;
+ MethodCodec& operator=(MethodCodec<T> const&) = delete;
+
+ // Returns the MethodCall encoded in |message|, or nullptr if it cannot be
+ // decoded.
+ std::unique_ptr<MethodCall<T>> DecodeMethodCall(const uint8_t* message,
+ size_t message_size) const {
+ return std::move(DecodeMethodCallInternal(message, message_size));
+ }
+
+ // Returns the MethodCall encoded in |message|, or nullptr if it cannot be
+ // decoded.
+ std::unique_ptr<MethodCall<T>> DecodeMethodCall(const std::vector<uint8_t>& message) const {
+ size_t size = message.size();
+ const uint8_t* data = size > 0 ? &message[0] : nullptr;
+ return std::move(DecodeMethodCallInternal(data, size));
+ }
+
+ // Returns a binary encoding of the given |method_call|, or nullptr if the
+ // method call cannot be serialized by this codec.
+ std::unique_ptr<std::vector<uint8_t>> EncodeMethodCall(const MethodCall<T>& method_call) const {
+ return std::move(EncodeMethodCallInternal(method_call));
+ }
+
+ // Returns a binary encoding of |result|. |result| must be a type supported
+ // by the codec.
+ std::unique_ptr<std::vector<uint8_t>> EncodeSuccessEnvelope(const T* result = nullptr) const {
+ return std::move(EncodeSuccessEnvelopeInternal(result));
+ }
+
+ // Returns a binary encoding of |error|. The |error_details| must be a type
+ // supported by the codec.
+ std::unique_ptr<std::vector<uint8_t>> EncodeErrorEnvelope(
+ const std::string& error_code, const std::string& error_message = "",
+ const T* error_details = nullptr) const {
+ return std::move(EncodeErrorEnvelopeInternal(error_code, error_message, error_details));
+ }
+
+ // Decodes the response envelope encoded in |response|, calling the
+ // appropriate method on |result|.
+ //
+ // Returns false if |response| cannot be decoded. In that case the caller is
+ // responsible for calling a |result| method.
+ bool DecodeAndProcessResponseEnvelope(const uint8_t* response, size_t response_size,
+ MethodResult<T>* result) const {
+ return DecodeAndProcessResponseEnvelopeInternal(response, response_size, result);
+ }
+
+ protected:
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual std::unique_ptr<MethodCall<T>> DecodeMethodCallInternal(const uint8_t* message,
+ size_t message_size) const = 0;
+
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual std::unique_ptr<std::vector<uint8_t>> EncodeMethodCallInternal(
+ const MethodCall<T>& method_call) const = 0;
+
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual std::unique_ptr<std::vector<uint8_t>> EncodeSuccessEnvelopeInternal(
+ const T* result) const = 0;
+
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual std::unique_ptr<std::vector<uint8_t>> EncodeErrorEnvelopeInternal(
+ const std::string& error_code, const std::string& error_message,
+ const T* error_details) const = 0;
+
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual bool DecodeAndProcessResponseEnvelopeInternal(const uint8_t* response,
+ size_t response_size,
+ MethodResult<T>* result) const = 0;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_CODEC_H_
diff --git a/src/embedder/standard_message_codec/method_result.h b/src/embedder/standard_message_codec/method_result.h
new file mode 100644
index 0000000..cbf7e62
--- /dev/null
+++ b/src/embedder/standard_message_codec/method_result.h
@@ -0,0 +1,73 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_RESULT_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_RESULT_H_
+
+#include <string>
+
+namespace standard_message_codec {
+
+class EncodableValue;
+
+// Encapsulates a result returned from a MethodCall. Only one method should be
+// called on any given instance.
+template <typename T = EncodableValue>
+class MethodResult {
+ public:
+ MethodResult() = default;
+
+ virtual ~MethodResult() = default;
+
+ // Prevent copying.
+ MethodResult(MethodResult const&) = delete;
+ MethodResult& operator=(MethodResult const&) = delete;
+
+ // Sends a success response, indicating that the call completed successfully
+ // with the given result.
+ void Success(const T& result) { SuccessInternal(&result); }
+
+ // Sends a success response, indicating that the call completed successfully
+ // with no result.
+ void Success() { SuccessInternal(nullptr); }
+
+ // Sends an error response, indicating that the call was understood but
+ // handling failed in some way.
+ //
+ // error_code: A string error code describing the error.
+ // error_message: A user-readable error message.
+ // error_details: Arbitrary extra details about the error.
+ void Error(const std::string& error_code, const std::string& error_message,
+ const T& error_details) {
+ ErrorInternal(error_code, error_message, &error_details);
+ }
+
+ // Sends an error response, indicating that the call was understood but
+ // handling failed in some way.
+ //
+ // error_code: A string error code describing the error.
+ // error_message: A user-readable error message (optional).
+ void Error(const std::string& error_code, const std::string& error_message = "") {
+ ErrorInternal(error_code, error_message, nullptr);
+ }
+
+ // Sends a not-implemented response, indicating that the method either was not
+ // recognized, or has not been implemented.
+ void NotImplemented() { NotImplementedInternal(); }
+
+ protected:
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual void SuccessInternal(const T* result) = 0;
+
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual void ErrorInternal(const std::string& error_code, const std::string& error_message,
+ const T* error_details) = 0;
+
+ // Implementation of the public interface, to be provided by subclasses.
+ virtual void NotImplementedInternal() = 0;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_METHOD_RESULT_H_
diff --git a/src/embedder/standard_message_codec/standard_codec_serializer.h b/src/embedder/standard_message_codec/standard_codec_serializer.h
new file mode 100644
index 0000000..29a8bf8
--- /dev/null
+++ b/src/embedder/standard_message_codec/standard_codec_serializer.h
@@ -0,0 +1,74 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_CODEC_SERIALIZER_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_CODEC_SERIALIZER_H_
+
+#include "byte_streams.h"
+#include "encodable_value.h"
+
+namespace standard_message_codec {
+
+// Encapsulates the logic for encoding/decoding EncodableValues to/from the
+// standard codec binary representation.
+//
+// This can be subclassed to extend the standard codec with support for new
+// types.
+class StandardCodecSerializer {
+ public:
+ virtual ~StandardCodecSerializer();
+
+ // Returns the shared serializer instance.
+ static const StandardCodecSerializer& GetInstance();
+
+ // Prevent copying.
+ StandardCodecSerializer(StandardCodecSerializer const&) = delete;
+ StandardCodecSerializer& operator=(StandardCodecSerializer const&) = delete;
+
+ // Reads and returns the next value from |stream|.
+ EncodableValue ReadValue(ByteStreamReader* stream) const;
+
+ // Writes the encoding of |value| to |stream|, including the initial type
+ // discrimination byte.
+ //
+ // Can be overridden by a subclass to extend the codec.
+ virtual void WriteValue(const EncodableValue& value, ByteStreamWriter* stream) const;
+
+ protected:
+ // Codecs require long-lived serializers, so clients should always use
+ // GetInstance().
+ StandardCodecSerializer();
+
+ // Reads and returns the next value from |stream|, whose discrimination byte
+ // was |type|.
+ //
+ // The discrimination byte will already have been read from the stream when
+ // this is called.
+ //
+ // Can be overridden by a subclass to extend the codec.
+ virtual EncodableValue ReadValueOfType(uint8_t type, ByteStreamReader* stream) const;
+
+ // Reads the variable-length size from the current position in |stream|.
+ size_t ReadSize(ByteStreamReader* stream) const;
+
+ // Writes the variable-length size encoding to |stream|.
+ void WriteSize(size_t size, ByteStreamWriter* stream) const;
+
+ private:
+ // Reads a fixed-type list whose values are of type T from the current
+ // position in |stream|, and returns it as the corresponding EncodableValue.
+ // |T| must correspond to one of the supported list value types of
+ // EncodableValue.
+ template <typename T>
+ EncodableValue ReadVector(ByteStreamReader* stream) const;
+
+ // Writes |vector| to |stream| as a fixed-type list. |T| must correspond to
+ // one of the supported list value types of EncodableValue.
+ template <typename T>
+ void WriteVector(const std::vector<T> vector, ByteStreamWriter* stream) const;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_CODEC_SERIALIZER_H_
diff --git a/src/embedder/standard_message_codec/standard_message_codec.cc b/src/embedder/standard_message_codec/standard_message_codec.cc
new file mode 100644
index 0000000..2b440b5
--- /dev/null
+++ b/src/embedder/standard_message_codec/standard_message_codec.cc
@@ -0,0 +1,442 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains what would normally be standard_codec_serializer.cc,
+// standard_message_codec.cc, and standard_method_codec.cc. They are grouped
+// together to simplify use of the client wrapper, since the common case is
+// that any client that needs one of these files needs all three.
+
+#include "standard_message_codec.h"
+
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "byte_buffer_streams.h"
+#include "standard_codec_serializer.h"
+#include "standard_method_codec.h"
+
+namespace standard_message_codec {
+
+// ===== standard_codec_serializer.h =====
+
+namespace {
+
+// The order/values here must match the constants in message_codecs.dart.
+enum class EncodedType {
+ kNull = 0,
+ kTrue,
+ kFalse,
+ kInt32,
+ kInt64,
+ kLargeInt, // No longer used. If encountered, treat as kString.
+ kFloat64,
+ kString,
+ kUInt8List,
+ kInt32List,
+ kInt64List,
+ kFloat64List,
+ kList,
+ kMap,
+ kFloat32List,
+};
+
+// Returns the encoded type that should be written when serializing |value|.
+EncodedType EncodedTypeForValue(const EncodableValue& value) {
+ switch (value.index()) {
+ case 0:
+ return EncodedType::kNull;
+ case 1:
+ return std::get<bool>(value) ? EncodedType::kTrue : EncodedType::kFalse;
+ case 2:
+ return EncodedType::kInt32;
+ case 3:
+ return EncodedType::kInt64;
+ case 4:
+ return EncodedType::kFloat64;
+ case 5:
+ return EncodedType::kString;
+ case 6:
+ return EncodedType::kUInt8List;
+ case 7:
+ return EncodedType::kInt32List;
+ case 8:
+ return EncodedType::kInt64List;
+ case 9:
+ return EncodedType::kFloat64List;
+ case 10:
+ return EncodedType::kList;
+ case 11:
+ return EncodedType::kMap;
+ case 13:
+ return EncodedType::kFloat32List;
+ }
+ assert(false);
+ return EncodedType::kNull;
+}
+
+} // namespace
+
+StandardCodecSerializer::StandardCodecSerializer() = default;
+
+StandardCodecSerializer::~StandardCodecSerializer() = default;
+
+const StandardCodecSerializer& StandardCodecSerializer::GetInstance() {
+ static StandardCodecSerializer sInstance;
+ return sInstance;
+}
+
+EncodableValue StandardCodecSerializer::ReadValue(ByteStreamReader* stream) const {
+ uint8_t type = stream->ReadByte();
+ return ReadValueOfType(type, stream);
+}
+
+void StandardCodecSerializer::WriteValue(const EncodableValue& value,
+ ByteStreamWriter* stream) const {
+ stream->WriteByte(static_cast<uint8_t>(EncodedTypeForValue(value)));
+ // TODO: Consider replacing this this with a std::visitor.
+ switch (value.index()) {
+ case 0:
+ case 1:
+ // Null and bool are encoded directly in the type.
+ break;
+ case 2:
+ stream->WriteInt32(std::get<int32_t>(value));
+ break;
+ case 3:
+ stream->WriteInt64(std::get<int64_t>(value));
+ break;
+ case 4:
+ stream->WriteAlignment(8);
+ stream->WriteDouble(std::get<double>(value));
+ break;
+ case 5: {
+ const auto& string_value = std::get<std::string>(value);
+ size_t size = string_value.size();
+ WriteSize(size, stream);
+ if (size > 0) {
+ stream->WriteBytes(reinterpret_cast<const uint8_t*>(string_value.data()), size);
+ }
+ break;
+ }
+ case 6:
+ WriteVector(std::get<std::vector<uint8_t>>(value), stream);
+ break;
+ case 7:
+ WriteVector(std::get<std::vector<int32_t>>(value), stream);
+ break;
+ case 8:
+ WriteVector(std::get<std::vector<int64_t>>(value), stream);
+ break;
+ case 9:
+ WriteVector(std::get<std::vector<double>>(value), stream);
+ break;
+ case 10: {
+ const auto& list = std::get<EncodableList>(value);
+ WriteSize(list.size(), stream);
+ for (const auto& item : list) {
+ WriteValue(item, stream);
+ }
+ break;
+ }
+ case 11: {
+ const auto& map = std::get<EncodableMap>(value);
+ WriteSize(map.size(), stream);
+ for (const auto& pair : map) {
+ WriteValue(pair.first, stream);
+ WriteValue(pair.second, stream);
+ }
+ break;
+ }
+ case 12:
+ std::cerr << "Unhandled custom type in StandardCodecSerializer::WriteValue. "
+ << "Custom types require codec extensions." << std::endl;
+ break;
+ case 13: {
+ WriteVector(std::get<std::vector<float>>(value), stream);
+ break;
+ }
+ }
+}
+
+EncodableValue StandardCodecSerializer::ReadValueOfType(uint8_t type,
+ ByteStreamReader* stream) const {
+ switch (static_cast<EncodedType>(type)) {
+ case EncodedType::kNull:
+ return EncodableValue();
+ case EncodedType::kTrue:
+ return EncodableValue(true);
+ case EncodedType::kFalse:
+ return EncodableValue(false);
+ case EncodedType::kInt32:
+ return EncodableValue(stream->ReadInt32());
+ case EncodedType::kInt64:
+ return EncodableValue(stream->ReadInt64());
+ case EncodedType::kFloat64:
+ stream->ReadAlignment(8);
+ return EncodableValue(stream->ReadDouble());
+ case EncodedType::kLargeInt:
+ case EncodedType::kString: {
+ size_t size = ReadSize(stream);
+ std::string string_value;
+ string_value.resize(size);
+ stream->ReadBytes(reinterpret_cast<uint8_t*>(&string_value[0]), size);
+ return EncodableValue(string_value);
+ }
+ case EncodedType::kUInt8List:
+ return ReadVector<uint8_t>(stream);
+ case EncodedType::kInt32List:
+ return ReadVector<int32_t>(stream);
+ case EncodedType::kInt64List:
+ return ReadVector<int64_t>(stream);
+ case EncodedType::kFloat64List:
+ return ReadVector<double>(stream);
+ case EncodedType::kList: {
+ size_t length = ReadSize(stream);
+ EncodableList list_value;
+ list_value.reserve(length);
+ for (size_t i = 0; i < length; ++i) {
+ list_value.push_back(ReadValue(stream));
+ }
+ return EncodableValue(list_value);
+ }
+ case EncodedType::kMap: {
+ size_t length = ReadSize(stream);
+ EncodableMap map_value;
+ for (size_t i = 0; i < length; ++i) {
+ EncodableValue key = ReadValue(stream);
+ EncodableValue value = ReadValue(stream);
+ map_value.emplace(std::move(key), std::move(value));
+ }
+ return EncodableValue(map_value);
+ }
+ case EncodedType::kFloat32List: {
+ return ReadVector<float>(stream);
+ }
+ }
+ std::cerr << "Unknown type in StandardCodecSerializer::ReadValueOfType: "
+ << static_cast<int>(type) << std::endl;
+ return EncodableValue();
+}
+
+size_t StandardCodecSerializer::ReadSize(ByteStreamReader* stream) const {
+ uint8_t byte = stream->ReadByte();
+ if (byte < 254) {
+ return byte;
+ } else if (byte == 254) {
+ uint16_t value = 0;
+ stream->ReadBytes(reinterpret_cast<uint8_t*>(&value), 2);
+ return value;
+ } else {
+ uint32_t value = 0;
+ stream->ReadBytes(reinterpret_cast<uint8_t*>(&value), 4);
+ return value;
+ }
+}
+
+void StandardCodecSerializer::WriteSize(size_t size, ByteStreamWriter* stream) const {
+ if (size < 254) {
+ stream->WriteByte(static_cast<uint8_t>(size));
+ } else if (size <= 0xffff) {
+ stream->WriteByte(254);
+ uint16_t value = static_cast<uint16_t>(size);
+ stream->WriteBytes(reinterpret_cast<uint8_t*>(&value), 2);
+ } else {
+ stream->WriteByte(255);
+ uint32_t value = static_cast<uint32_t>(size);
+ stream->WriteBytes(reinterpret_cast<uint8_t*>(&value), 4);
+ }
+}
+
+template <typename T>
+EncodableValue StandardCodecSerializer::ReadVector(ByteStreamReader* stream) const {
+ size_t count = ReadSize(stream);
+ std::vector<T> vector;
+ vector.resize(count);
+ uint8_t type_size = static_cast<uint8_t>(sizeof(T));
+ if (type_size > 1) {
+ stream->ReadAlignment(type_size);
+ }
+ stream->ReadBytes(reinterpret_cast<uint8_t*>(vector.data()), count * type_size);
+ return EncodableValue(vector);
+}
+
+template <typename T>
+void StandardCodecSerializer::WriteVector(const std::vector<T> vector,
+ ByteStreamWriter* stream) const {
+ size_t count = vector.size();
+ WriteSize(count, stream);
+ if (count == 0) {
+ return;
+ }
+ uint8_t type_size = static_cast<uint8_t>(sizeof(T));
+ if (type_size > 1) {
+ stream->WriteAlignment(type_size);
+ }
+ stream->WriteBytes(reinterpret_cast<const uint8_t*>(vector.data()), count * type_size);
+}
+
+// ===== standard_message_codec.h =====
+
+// static
+const StandardMessageCodec& StandardMessageCodec::GetInstance(
+ const StandardCodecSerializer* serializer) {
+ if (!serializer) {
+ serializer = &StandardCodecSerializer::GetInstance();
+ }
+ static auto* sInstances =
+ new std::map<const StandardCodecSerializer*, std::unique_ptr<StandardMessageCodec>>;
+ auto it = sInstances->find(serializer);
+ if (it == sInstances->end()) {
+ // Uses new due to private constructor (to prevent API clients from
+ // accidentally passing temporary codec instances to channels).
+ auto emplace_result = sInstances->emplace(
+ serializer, std::unique_ptr<StandardMessageCodec>(new StandardMessageCodec(serializer)));
+ it = emplace_result.first;
+ }
+ return *(it->second);
+}
+
+StandardMessageCodec::StandardMessageCodec(const StandardCodecSerializer* serializer)
+ : serializer_(serializer) {}
+
+StandardMessageCodec::~StandardMessageCodec() = default;
+
+std::unique_ptr<EncodableValue> StandardMessageCodec::DecodeMessageInternal(
+ const uint8_t* binary_message, size_t message_size) const {
+ if (!binary_message) {
+ return std::make_unique<EncodableValue>();
+ }
+ ByteBufferStreamReader stream(binary_message, message_size);
+ return std::make_unique<EncodableValue>(serializer_->ReadValue(&stream));
+}
+
+std::unique_ptr<std::vector<uint8_t>> StandardMessageCodec::EncodeMessageInternal(
+ const EncodableValue& message) const {
+ auto encoded = std::make_unique<std::vector<uint8_t>>();
+ ByteBufferStreamWriter stream(encoded.get());
+ serializer_->WriteValue(message, &stream);
+ return encoded;
+}
+
+// ===== standard_method_codec.h =====
+
+// static
+const StandardMethodCodec& StandardMethodCodec::GetInstance(
+ const StandardCodecSerializer* serializer) {
+ if (!serializer) {
+ serializer = &StandardCodecSerializer::GetInstance();
+ }
+ static auto* sInstances =
+ new std::map<const StandardCodecSerializer*, std::unique_ptr<StandardMethodCodec>>;
+ auto it = sInstances->find(serializer);
+ if (it == sInstances->end()) {
+ // Uses new due to private constructor (to prevent API clients from
+ // accidentally passing temporary codec instances to channels).
+ auto emplace_result = sInstances->emplace(
+ serializer, std::unique_ptr<StandardMethodCodec>(new StandardMethodCodec(serializer)));
+ it = emplace_result.first;
+ }
+ return *(it->second);
+}
+
+StandardMethodCodec::StandardMethodCodec(const StandardCodecSerializer* serializer)
+ : serializer_(serializer) {}
+
+StandardMethodCodec::~StandardMethodCodec() = default;
+
+std::unique_ptr<MethodCall<EncodableValue>> StandardMethodCodec::DecodeMethodCallInternal(
+ const uint8_t* message, size_t message_size) const {
+ ByteBufferStreamReader stream(message, message_size);
+ EncodableValue method_name_value = serializer_->ReadValue(&stream);
+ const auto* method_name = std::get_if<std::string>(&method_name_value);
+ if (!method_name) {
+ std::cerr << "Invalid method call; method name is not a string." << std::endl;
+ return nullptr;
+ }
+ auto arguments = std::make_unique<EncodableValue>(serializer_->ReadValue(&stream));
+ return std::make_unique<MethodCall<EncodableValue>>(*method_name, std::move(arguments));
+}
+
+std::unique_ptr<std::vector<uint8_t>> StandardMethodCodec::EncodeMethodCallInternal(
+ const MethodCall<EncodableValue>& method_call) const {
+ auto encoded = std::make_unique<std::vector<uint8_t>>();
+ ByteBufferStreamWriter stream(encoded.get());
+ serializer_->WriteValue(EncodableValue(method_call.method_name()), &stream);
+ if (method_call.arguments()) {
+ serializer_->WriteValue(*method_call.arguments(), &stream);
+ } else {
+ serializer_->WriteValue(EncodableValue(), &stream);
+ }
+ return encoded;
+}
+
+std::unique_ptr<std::vector<uint8_t>> StandardMethodCodec::EncodeSuccessEnvelopeInternal(
+ const EncodableValue* result) const {
+ auto encoded = std::make_unique<std::vector<uint8_t>>();
+ ByteBufferStreamWriter stream(encoded.get());
+ stream.WriteByte(0);
+ if (result) {
+ serializer_->WriteValue(*result, &stream);
+ } else {
+ serializer_->WriteValue(EncodableValue(), &stream);
+ }
+ return encoded;
+}
+
+std::unique_ptr<std::vector<uint8_t>> StandardMethodCodec::EncodeErrorEnvelopeInternal(
+ const std::string& error_code, const std::string& error_message,
+ const EncodableValue* error_details) const {
+ auto encoded = std::make_unique<std::vector<uint8_t>>();
+ ByteBufferStreamWriter stream(encoded.get());
+ stream.WriteByte(1);
+ serializer_->WriteValue(EncodableValue(error_code), &stream);
+ if (error_message.empty()) {
+ serializer_->WriteValue(EncodableValue(), &stream);
+ } else {
+ serializer_->WriteValue(EncodableValue(error_message), &stream);
+ }
+ if (error_details) {
+ serializer_->WriteValue(*error_details, &stream);
+ } else {
+ serializer_->WriteValue(EncodableValue(), &stream);
+ }
+ return encoded;
+}
+
+bool StandardMethodCodec::DecodeAndProcessResponseEnvelopeInternal(
+ const uint8_t* response, size_t response_size, MethodResult<EncodableValue>* result) const {
+ ByteBufferStreamReader stream(response, response_size);
+ uint8_t flag = stream.ReadByte();
+ switch (flag) {
+ case 0: {
+ EncodableValue value = serializer_->ReadValue(&stream);
+ if (value.IsNull()) {
+ result->Success();
+ } else {
+ result->Success(value);
+ }
+ return true;
+ }
+ case 1: {
+ EncodableValue code = serializer_->ReadValue(&stream);
+ EncodableValue message = serializer_->ReadValue(&stream);
+ EncodableValue details = serializer_->ReadValue(&stream);
+ const std::string& message_string = message.IsNull() ? "" : std::get<std::string>(message);
+ if (details.IsNull()) {
+ result->Error(std::get<std::string>(code), message_string);
+ } else {
+ result->Error(std::get<std::string>(code), message_string, details);
+ }
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+} // namespace standard_message_codec
diff --git a/src/embedder/standard_message_codec/standard_message_codec.h b/src/embedder/standard_message_codec/standard_message_codec.h
new file mode 100644
index 0000000..8962838
--- /dev/null
+++ b/src/embedder/standard_message_codec/standard_message_codec.h
@@ -0,0 +1,56 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_MESSAGE_CODEC_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_MESSAGE_CODEC_H_
+
+#include <memory>
+
+#include "encodable_value.h"
+#include "message_codec.h"
+#include "standard_codec_serializer.h"
+
+namespace standard_message_codec {
+
+// A binary message encoding/decoding mechanism for communications to/from the
+// Flutter engine via message channels.
+class StandardMessageCodec : public MessageCodec<EncodableValue> {
+ public:
+ // Returns an instance of the codec, optionally using a custom serializer to
+ // add support for more types.
+ //
+ // If provided, |serializer| must be long-lived. If no serializer is provided,
+ // the default will be used.
+ //
+ // The instance returned for a given |serializer| will be shared, and
+ // any instance returned from this will be long-lived, and can be safely
+ // passed to, e.g., channel constructors.
+ static const StandardMessageCodec& GetInstance(
+ const StandardCodecSerializer* serializer = nullptr);
+
+ ~StandardMessageCodec();
+
+ // Prevent copying.
+ StandardMessageCodec(StandardMessageCodec const&) = delete;
+ StandardMessageCodec& operator=(StandardMessageCodec const&) = delete;
+
+ protected:
+ // |flutter::MessageCodec|
+ std::unique_ptr<EncodableValue> DecodeMessageInternal(const uint8_t* binary_message,
+ const size_t message_size) const override;
+
+ // |flutter::MessageCodec|
+ std::unique_ptr<std::vector<uint8_t>> EncodeMessageInternal(
+ const EncodableValue& message) const override;
+
+ private:
+ // Instances should be obtained via GetInstance.
+ explicit StandardMessageCodec(const StandardCodecSerializer* serializer);
+
+ const StandardCodecSerializer* serializer_;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_MESSAGE_CODEC_H_
diff --git a/src/embedder/standard_message_codec/standard_method_codec.h b/src/embedder/standard_message_codec/standard_method_codec.h
new file mode 100644
index 0000000..e8c65da
--- /dev/null
+++ b/src/embedder/standard_message_codec/standard_method_codec.h
@@ -0,0 +1,70 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_METHOD_CODEC_H_
+#define SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_METHOD_CODEC_H_
+
+#include <memory>
+
+#include "encodable_value.h"
+#include "method_call.h"
+#include "method_codec.h"
+#include "standard_codec_serializer.h"
+
+namespace standard_message_codec {
+
+// An implementation of MethodCodec that uses a binary serialization.
+class StandardMethodCodec : public MethodCodec<EncodableValue> {
+ public:
+ // Returns an instance of the codec, optionally using a custom serializer to
+ // add support for more types.
+ //
+ // If provided, |serializer| must be long-lived. If no serializer is provided,
+ // the default will be used.
+ //
+ // The instance returned for a given |extension| will be shared, and
+ // any instance returned from this will be long-lived, and can be safely
+ // passed to, e.g., channel constructors.
+ static const StandardMethodCodec& GetInstance(
+ const StandardCodecSerializer* serializer = nullptr);
+
+ ~StandardMethodCodec();
+
+ // Prevent copying.
+ StandardMethodCodec(StandardMethodCodec const&) = delete;
+ StandardMethodCodec& operator=(StandardMethodCodec const&) = delete;
+
+ protected:
+ // |flutter::MethodCodec|
+ std::unique_ptr<MethodCall<EncodableValue>> DecodeMethodCallInternal(
+ const uint8_t* message, size_t message_size) const override;
+
+ // |flutter::MethodCodec|
+ std::unique_ptr<std::vector<uint8_t>> EncodeMethodCallInternal(
+ const MethodCall<EncodableValue>& method_call) const override;
+
+ // |flutter::MethodCodec|
+ std::unique_ptr<std::vector<uint8_t>> EncodeSuccessEnvelopeInternal(
+ const EncodableValue* result) const override;
+
+ // |flutter::MethodCodec|
+ std::unique_ptr<std::vector<uint8_t>> EncodeErrorEnvelopeInternal(
+ const std::string& error_code, const std::string& error_message,
+ const EncodableValue* error_details) const override;
+
+ // |flutter::MethodCodec|
+ bool DecodeAndProcessResponseEnvelopeInternal(
+ const uint8_t* response, size_t response_size,
+ MethodResult<EncodableValue>* result) const override;
+
+ private:
+ // Instances should be obtained via GetInstance.
+ explicit StandardMethodCodec(const StandardCodecSerializer* serializer);
+
+ const StandardCodecSerializer* serializer_;
+};
+
+} // namespace standard_message_codec
+
+#endif // SRC_EMBEDDER_STANDARD_MESSAGE_CODEC_STANDARD_METHOD_CODEC_H_