[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(&current_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_