[flutter-embedder][keyboard] Port keyboard input functionality from
flutter/engine.

- Key data can be sent to the engine, but doesn't appear as text input
  to textfield in flutter app.
  - flutter/keyevent channel must be taken care of in future prs.
- main.dart - I added a textinput field, but doesn't show key data right
  now.
- brought in rapidjson as a submodule, with anticipation of using in
  future keyboard work.

Change-Id: If8c3e95c189cae1ba1c5d99b7e035cba0a646c0c
Reviewed-on: https://fuchsia-review.googlesource.com/c/flutter-embedder/+/735084
Reviewed-by: Alexander Biggs <akbiggs@google.com>
diff --git a/.gitmodules b/.gitmodules
index 5d0af1d..02517cc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,3 +10,6 @@
 [submodule "third_party/depot_tools"]
 	path = third_party/depot_tools
 	url = https://chromium.googlesource.com/chromium/tools/depot_tools.git
+[submodule "third_party/rapidjson"]
+	path = third_party/rapidjson
+	url = https://fuchsia.googlesource.com/third_party/rapidjson/
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
index a8858de..967fd6d 100644
--- a/WORKSPACE.bazel
+++ b/WORKSPACE.bazel
@@ -58,3 +58,10 @@
     name = "com_google_googletest",
     path = "third_party/googletest",
 )
+
+# rapidjson.
+new_local_repository(
+    name = "rapidjson",
+    build_file = "third_party/BUILD.bazel",
+    path = "third_party/rapidjson",
+)
diff --git a/src/embedder/BUILD.bazel b/src/embedder/BUILD.bazel
index 6d96afe..24d10ed 100644
--- a/src/embedder/BUILD.bazel
+++ b/src/embedder/BUILD.bazel
@@ -14,31 +14,36 @@
 # and runs the Flutter app defined by those assets.
 fuchsia_cc_binary(
     name = "embedder",
-    visibility = ["//visibility:public"],
     srcs = [
         "embedder_state.h",
         "flatland_connection.cc",
         "flatland_connection.h",
         "flatland_ids.h",
         "flatland_view_provider.h",
+        "fuchsia_logger.cc",
+        "fuchsia_logger.h",
+        "keyboard.cc",
+        "keyboard.h",
         "logging.h",
         "main.cc",
+        "mouse_delegate.cc",
+        "mouse_delegate.h",
+        "pointer_utility.h",
         "software_surface.cc",
         "software_surface.h",
-        "mouse_delegate.h",
-        "mouse_delegate.cc",
-        "touch_delegate.h",
+        "text_delegate.cc",
+        "text_delegate.h",
         "touch_delegate.cc",
-        "pointer_utility.h",
-        "fuchsia_logger.h",
-        "fuchsia_logger.cc",
+        "touch_delegate.h",
     ],
+    visibility = ["//visibility:public"],
     deps = [
         "//src/embedder/engine:embedder_header",
         # TODO(akbiggs): Enable switching between debug and profile
         # builds of libflutter_engine.so without manually editing this
         # file.
         "//src/embedder/engine/debug_x64:libflutter_engine",
+        "@rapidjson",
         "@fuchsia_sdk//fidl/fuchsia.fonts:fuchsia.fonts_cc",
         "@fuchsia_sdk//fidl/fuchsia.sysmem:fuchsia.sysmem_cc",
         "@fuchsia_sdk//fidl/fuchsia.ui.app:fuchsia.ui.app_cc",
@@ -57,10 +62,10 @@
 
 fuchsia_component_manifest(
     name = "embedder_manifest",
-    visibility = ["//visibility:public"],
     src = "meta/embedder.cml",
     includes = [
         "@fuchsia_sdk//pkg/syslog:client",
         "@fuchsia_sdk//pkg/vulkan:client",
     ],
+    visibility = ["//visibility:public"],
 )
diff --git a/src/embedder/flatland_view_provider.h b/src/embedder/flatland_view_provider.h
index 4152ea0..d5cdce5 100644
--- a/src/embedder/flatland_view_provider.h
+++ b/src/embedder/flatland_view_provider.h
@@ -8,7 +8,12 @@
 #include <fuchsia/ui/app/cpp/fidl.h>
 #include <lib/syslog/global.h>
 #include <lib/ui/scenic/cpp/view_identity.h>
+#include <lib/ui/scenic/cpp/view_ref_pair.h>
 
+#include <memory>
+
+#include "logging.h"
+#include "src/embedder/engine/embedder.h"
 #include "src/embedder/flatland_connection.h"
 #include "src/embedder/flatland_ids.h"
 #include "src/embedder/logging.h"
@@ -18,9 +23,13 @@
 /// Implements ViewProvider by using Flatland to create a view.
 class FlatlandViewProvider final : public fuchsia::ui::app::ViewProvider {
  public:
-  FlatlandViewProvider(FlatlandConnection* flatland_connection,
-                       fuchsia::ui::composition::ViewBoundProtocols protocols)
-      : flatland_connection_(flatland_connection), flatland_view_protocols_(std::move(protocols)) {}
+  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)};
+  }
   ~FlatlandViewProvider() override {}
 
   FlatlandViewProvider(const FlatlandViewProvider&) = delete;
@@ -40,9 +49,9 @@
 
   void CreateView2(fuchsia::ui::app::CreateView2Args view_args) override {
     auto* flatland = flatland_connection_->flatland();
-    flatland->CreateView2(std::move(*view_args.mutable_view_creation_token()),
-                          scenic::NewViewIdentityOnCreation(), std::move(flatland_view_protocols_),
-                          parent_viewport_watcher_.NewRequest());
+    flatland->CreateView2(
+        std::move(*view_args.mutable_view_creation_token()), std::move(view_identity_),
+        std::move(flatland_view_protocols_) /* protocols */, parent_viewport_watcher_.NewRequest());
 
     flatland->CreateTransform({kRootTransformId});
     flatland->SetRootTransform({kRootTransformId});
@@ -51,6 +60,7 @@
  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_;
 };
 
diff --git a/src/embedder/keyboard.cc b/src/embedder/keyboard.cc
new file mode 100644
index 0000000..e2edd61
--- /dev/null
+++ b/src/embedder/keyboard.cc
@@ -0,0 +1,352 @@
+// 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.
+
+#include "src/embedder/keyboard.h"
+
+#include <fuchsia/input/cpp/fidl.h>
+#include <fuchsia/ui/input/cpp/fidl.h>
+#include <fuchsia/ui/input3/cpp/fidl.h>
+#include <lib/syslog/global.h>
+
+#include <cstdint>
+#include <iostream>
+
+#include "logging.h"
+
+namespace embedder {
+
+using fuchsia::input::Key;
+using fuchsia::ui::input::kModifierCapsLock;
+using fuchsia::ui::input::kModifierLeftAlt;
+using fuchsia::ui::input::kModifierLeftControl;
+using fuchsia::ui::input::kModifierLeftShift;
+using fuchsia::ui::input::kModifierNone;
+using fuchsia::ui::input::kModifierRightAlt;
+using fuchsia::ui::input::kModifierRightControl;
+using fuchsia::ui::input::kModifierRightShift;
+using fuchsia::ui::input3::KeyEvent;
+using fuchsia::ui::input3::KeyEventType;
+
+namespace {
+
+// A simple keymap from a QWERTY keyboard to code points. A value 0 means no
+// code point has been assigned for the respective keypress. Column 0 is the
+// code point without a level modifier active, and Column 1 is the code point
+// with a level modifier (e.g. Shift key) active.
+static const uint32_t kQwertyToCodePoints[][2] = {
+    // 0x00
+    {},
+    {},
+    {},
+    {},
+    // 0x04,
+    {'a', 'A'},
+    {'b', 'B'},
+    {'c', 'C'},
+    {'d', 'D'},
+    // 0x08
+    {'e', 'E'},
+    {'f', 'F'},
+    {'g', 'G'},
+    {'h', 'H'},
+    // 0x0c
+    {'i', 'I'},
+    {'j', 'J'},
+    {'k', 'K'},
+    {'l', 'L'},
+    // 0x10
+    {'m', 'M'},
+    {'n', 'N'},
+    {'o', 'O'},
+    {'p', 'P'},
+    // 0x14
+    {'q', 'Q'},
+    {'r', 'R'},
+    {'s', 'S'},
+    {'t', 'T'},
+    // 0x18
+    {'u', 'U'},
+    {'v', 'V'},
+    {'w', 'W'},
+    {'x', 'X'},
+    // 0x1c
+    {'y', 'Y'},
+    {'z', 'Z'},
+    {'1', '!'},
+    {'2', '@'},
+    // 0x20
+    {'3', '#'},
+    {'4', '$'},
+    {'5', '%'},
+    {'6', '^'},
+    // 0x24
+    {'7', '&'},
+    {'8', '*'},
+    {'9', '('},
+    {'0', ')'},
+    // 0x28
+    {},
+    {},
+    {},
+    {},
+    // 0x2c
+    {' ', ' '},
+    {'-', '_'},
+    {'=', '+'},
+    {'[', '{'},
+    // 0x30
+    {']', '}'},
+    {'\\', '|'},
+    {},
+    {';', ':'},
+    // 0x34
+    {'\'', '"'},
+    {'`', '~'},
+    {',', '<'},
+    {'.', '>'},
+    // 0x38
+    {'/', '?'},
+    {},
+    {},
+    {},
+    // 0x3c
+    {},
+    {},
+    {},
+    {},
+    // 0x40
+    {},
+    {},
+    {},
+    {},
+    // 0x44
+    {},
+    {},
+    {},
+    {},
+    // 0x48
+    {},
+    {},
+    {},
+    {},
+    // 0x4c
+    {},
+    {},
+    {},
+    {},
+    // 0x50
+    {},
+    {},
+    {},
+    {},
+    // 0x54
+    {'/', 0},
+    {'*', 0},
+    {'-', 0},
+    {'+', 0},
+    // 0x58
+    {},
+    {'1', 0},
+    {'2', 0},
+    {'3', 0},
+    // 0x5c
+    {'4', 0},
+    {'5', 0},
+    {'6', 0},
+    {'7', 0},
+    // 0x60
+    {'8', 0},
+    {'9', 0},
+    {'0', 0},
+    {'.', 0},
+};
+
+}  // namespace
+
+Keyboard::Keyboard()
+    : any_events_received_(false),
+      stateful_caps_lock_(false),
+      left_shift_(false),
+      right_shift_(false),
+      left_alt_(false),
+      right_alt_(false),
+      left_ctrl_(false),
+      right_ctrl_(false),
+      last_event_() {}
+
+bool Keyboard::ConsumeEvent(KeyEvent event) {
+  if (!event.has_type()) {
+    return false;
+  }
+  if (!event.has_key() && !event.has_key_meaning()) {
+    return false;
+  }
+  // Check if the time sequence of the events is correct.
+  last_event_ = std::move(event);
+  any_events_received_ = true;
+
+  if (!event.has_key()) {
+    // The key only has key meaning.  Key meaning currently can not
+    // update the modifier state, so we just short-circuit the table
+    // below.
+    return true;
+  }
+
+  const Key& key = last_event_.key();
+  const KeyEventType& event_type = last_event_.type();
+  switch (event_type) {
+    // For modifier keys, a SYNC is the same as a press.
+    case KeyEventType::SYNC:
+      switch (key) {
+        case Key::CAPS_LOCK:
+          stateful_caps_lock_ = true;
+          break;
+        case Key::LEFT_ALT:
+          left_alt_ = true;
+          break;
+        case Key::LEFT_CTRL:
+          left_ctrl_ = true;
+          break;
+        case Key::LEFT_SHIFT:
+          left_shift_ = true;
+          break;
+        case Key::RIGHT_ALT:
+          right_alt_ = true;
+          break;
+        case Key::RIGHT_CTRL:
+          right_ctrl_ = true;
+          break;
+        case Key::RIGHT_SHIFT:
+          right_shift_ = true;
+          break;
+        default:
+          // no-op
+          break;
+      }
+      break;
+    case KeyEventType::PRESSED:
+      switch (key) {
+        case Key::CAPS_LOCK:
+          stateful_caps_lock_ = !stateful_caps_lock_;
+          break;
+        case Key::LEFT_ALT:
+          left_alt_ = true;
+          break;
+        case Key::LEFT_CTRL:
+          left_ctrl_ = true;
+          break;
+        case Key::LEFT_SHIFT:
+          left_shift_ = true;
+          break;
+        case Key::RIGHT_ALT:
+          right_alt_ = true;
+          break;
+        case Key::RIGHT_CTRL:
+          right_ctrl_ = true;
+          break;
+        case Key::RIGHT_SHIFT:
+          right_shift_ = true;
+          break;
+        default:
+          // No-op
+          break;
+      }
+      break;
+    case KeyEventType::RELEASED:
+      switch (key) {
+        case Key::CAPS_LOCK:
+          // No-op.
+          break;
+        case Key::LEFT_ALT:
+          left_alt_ = false;
+          break;
+        case Key::LEFT_CTRL:
+          left_ctrl_ = false;
+          break;
+        case Key::LEFT_SHIFT:
+          left_shift_ = false;
+          break;
+        case Key::RIGHT_ALT:
+          right_alt_ = false;
+          break;
+        case Key::RIGHT_CTRL:
+          right_ctrl_ = false;
+          break;
+        case Key::RIGHT_SHIFT:
+          right_shift_ = false;
+          break;
+        default:
+          // No-op
+          break;
+      }
+      break;
+    case KeyEventType::CANCEL:
+      // No-op?
+      break;
+    default:
+      // No-op
+      break;
+  }
+  return true;
+}
+
+bool Keyboard::IsShift() { return left_shift_ | right_shift_ | stateful_caps_lock_; }
+
+bool Keyboard::IsKeys() { return LastHIDUsagePage() == 0x7; }
+
+uint32_t Keyboard::Modifiers() {
+  return kModifierNone + (kModifierLeftShift * left_shift_) + (kModifierLeftAlt * left_alt_) +
+         (kModifierLeftControl * left_ctrl_) + (kModifierRightShift * right_shift_) +
+         (kModifierRightAlt * right_alt_) + (kModifierRightControl * right_ctrl_) +
+         (kModifierCapsLock * stateful_caps_lock_);
+}
+
+uint32_t Keyboard::LastCodePoint() {
+  // If the key has a meaning, and if the meaning is a code point, always have
+  // that code point take precedence over any other keymap.
+  if (last_event_.has_key_meaning()) {
+    const auto& key_meaning = last_event_.key_meaning();
+    if (key_meaning.is_codepoint()) {
+      return key_meaning.codepoint();
+    }
+  }
+
+  static const int qwerty_map_size = sizeof(kQwertyToCodePoints) / sizeof(kQwertyToCodePoints[0]);
+  if (!IsKeys()) {
+    return 0;
+  }
+  const auto usage = LastHIDUsageID();
+  if (usage < qwerty_map_size) {
+    return kQwertyToCodePoints[usage][IsShift() & 1];
+  }
+  // Any other keys don't have a code point.
+  return 0;
+}
+
+char* Keyboard::LastCodePointAsChar() {
+  static char textToWrite[9];
+  uint32_t codepoint = LastCodePoint();
+  sprintf(textToWrite, "%c", codepoint);
+  return textToWrite;
+}
+
+uint32_t Keyboard::GetLastKey() {
+  // For logical key determination, the physical key does not matter as long
+  // as code point is set.
+  // https://github.com/flutter/flutter/blob/570e39d38b799e91abe4f73f120ce494049c4ff0/packages/flutter/lib/src/services/raw_keyboard_fuchsia.dart#L71
+  // It is not quite clear what happens to the physical key, though:
+  // https://github.com/flutter/flutter/blob/570e39d38b799e91abe4f73f120ce494049c4ff0/packages/flutter/lib/src/services/raw_keyboard_fuchsia.dart#L88
+  if (!last_event_.has_key()) {
+    return 0;
+  }
+  return static_cast<uint32_t>(last_event_.key());
+}
+
+uint32_t Keyboard::LastHIDUsage() { return GetLastKey() & 0xFFFFFFFF; }
+
+uint16_t Keyboard::LastHIDUsageID() { return GetLastKey() & 0xFFFF; }
+
+uint16_t Keyboard::LastHIDUsagePage() { return (GetLastKey() >> 16) & 0xFFFF; }
+
+}  // namespace embedder
diff --git a/src/embedder/keyboard.h b/src/embedder/keyboard.h
new file mode 100644
index 0000000..9c60a25
--- /dev/null
+++ b/src/embedder/keyboard.h
@@ -0,0 +1,87 @@
+// 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 FLUTTER_SHELL_PLATFORM_FUCHSIA_KEYBOARD_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_KEYBOARD_H_
+
+#include <fuchsia/ui/input3/cpp/fidl.h>
+
+namespace embedder {
+
+/// Keyboard handles the keyboard signals from fuchsia.ui.input3.  Specifically,
+/// input3 has no notion of a code point, and does not track stateful versions
+/// of the modifier keys.
+class Keyboard final {
+ public:
+  explicit Keyboard();
+
+  /// Consumes the given keyboard event.  Keyboard will adjust the modifier
+  /// state based on the info given in the event.  Returns true if the event has
+  /// been integrated into the internal state successfully, or false otherwise.
+  bool ConsumeEvent(fuchsia::ui::input3::KeyEvent event);
+
+  /// Gets the currently active modifier keys.
+  uint32_t Modifiers();
+
+  /// Gets the last encountered code point.  The reported code point depends on
+  /// the state of the modifier keys.
+  uint32_t LastCodePoint();
+
+  char* LastCodePointAsChar();
+
+  /// Gets the last encountered HID usage.  This is a 32-bit number, with the
+  /// upper 16 bits equal to `LastHidUsagePage()`, and the lower 16 bits equal
+  /// to `LastHIDUsageID()`.
+  ///
+  /// The key corresponding to A will have the usage 0x7004. This function will
+  /// return 0x7004 in that case.
+  uint32_t LastHIDUsage();
+
+  /// Gets the last encountered HID usage page.
+  ///
+  /// The key corresponding to A will have the usage 0x7004. This function will
+  /// return 0x7 in that case.
+  uint16_t LastHIDUsagePage();
+
+  /// Gets the last encountered HID usage ID.
+  ///
+  /// The key corresponding to A will have the usage 0x7004. This function will
+  /// return 0x4 in that case.
+  uint16_t LastHIDUsageID();
+
+ private:
+  /// Return true if any level shift is active.
+  bool IsShift();
+
+  /// Returns true if the last key event was about a key that may have a code
+  /// point associated.
+  bool IsKeys();
+
+  /// Returns the value of the last key as a uint32_t.
+  /// If there isn't such a value (as in the case of on-screen keyboards), this
+  /// will return a 0;
+  uint32_t GetLastKey();
+
+  /// Set to false until any event is received.
+  bool any_events_received_ : 1;
+
+  /// The flags below show the state of the keyboard modifiers after the last
+  /// event has been processed.  Stateful keys remain in the same state after
+  /// a release and require an additional press to toggle.
+  bool stateful_caps_lock_ : 1;
+  bool left_shift_ : 1;
+  bool right_shift_ : 1;
+  bool left_alt_ : 1;
+  bool right_alt_ : 1;
+  bool left_ctrl_ : 1;
+  bool right_ctrl_ : 1;
+
+  /// The last received key event.  If any_events_received_ is not set, this is
+  /// not valid.
+  fuchsia::ui::input3::KeyEvent last_event_;
+};
+
+}  // namespace embedder
+
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_KEYBOARD_H_
diff --git a/src/embedder/main.cc b/src/embedder/main.cc
index fd07f95..b6cd7d5 100644
--- a/src/embedder/main.cc
+++ b/src/embedder/main.cc
@@ -20,6 +20,7 @@
 #include <lib/sys/cpp/component_context.h>
 #include <lib/syslog/global.h>
 #include <lib/ui/scenic/cpp/view_identity.h>
+#include <lib/ui/scenic/cpp/view_ref_pair.h>
 #include <zircon/status.h>
 
 #include <string>
@@ -31,6 +32,7 @@
 #include "src/embedder/logging.h"
 #include "src/embedder/mouse_delegate.h"
 #include "src/embedder/software_surface.h"
+#include "src/embedder/text_delegate.h"
 #include "src/embedder/touch_delegate.h"
 
 namespace embedder {
@@ -84,6 +86,13 @@
   FX_LOG(INFO, tag, message);
 }
 
+/// Callback for the embedder's FlutterEngineSendKeyEvent function.
+void FuchsiaFlutterKeyEventCallback(bool handled, void* user_data) {
+  // TODO(naudzghebre): Currently this callback isn't being fired. Do something meaningful
+  // here.
+  FX_LOG(ERROR, embedder::kLogTag, "FlutterKeyEventCallback invoked!");
+}
+
 /// Fuchsia implementation of the Flutter Engine's software rendering
 /// |surface_acquire_callback|.
 bool FuchsiaAcquireSoftwareSurface(void* user_data, size_t width, size_t height,
@@ -263,6 +272,27 @@
   };
   FlutterEngineSendWindowMetricsEvent(embedder.engine, &window_metrics_event);
 
+  // Connect to Keyboard service.
+  fuchsia::ui::input3::KeyboardHandle keyboard;
+  zx_status_t keyboard_status =
+      embedder.component_context->svc()->Connect<fuchsia::ui::input3::Keyboard>(
+          keyboard.NewRequest());
+  if (keyboard_status != ZX_OK) {
+    FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia::ui::input3::Keyboard: %s",
+            zx_status_get_string(keyboard_status));
+    return EXIT_FAILURE;
+  }
+
+  // Connect to ImeService service.
+  fuchsia::ui::input::ImeServiceHandle ime_service;
+  zx_status_t ime_status =
+      embedder.component_context->svc()->Connect<fuchsia::ui::input::ImeService>(
+          ime_service.NewRequest());
+  if (ime_status != ZX_OK) {
+    FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia::ui::input::ImeService: %s",
+            zx_status_get_string(ime_status));
+  }
+
   // Connect to Flatland.
   fuchsia::ui::composition::FlatlandHandle flatland_handle;
   zx_status_t status =
@@ -288,14 +318,39 @@
         // TODO(akbiggs): What should we do after a frame finishes presenting?
       });
 
+  // The protocol endpoints bound to a Flatland ViewCreationToken.
+  fuchsia::ui::composition::ViewBoundProtocols flatland_view_protocols;
+
   // Mouse & Touch source
   fuchsia::ui::pointer::MouseSourceHandle mouse_source;
   fuchsia::ui::pointer::TouchSourceHandle touch_source;
-  fuchsia::ui::composition::ViewBoundProtocols protocols;
-  protocols.set_touch_source(touch_source.NewRequest());
-  protocols.set_mouse_source(mouse_source.NewRequest());
+  flatland_view_protocols.set_touch_source(touch_source.NewRequest());
+  flatland_view_protocols.set_mouse_source(mouse_source.NewRequest());
+
+  auto view_ref_pair = scenic::ViewRefPair::New();
+
+  fuchsia::ui::views::ViewRef platform_view_ref;
+  view_ref_pair.view_ref.Clone(&platform_view_ref);
+
+  // Keyboard/Text input.
+  auto text_delegate = std::make_unique<embedder::TextDelegate>(
+      std::move(platform_view_ref), std::move(ime_service), std::move(keyboard),
+      [&embedder](const FlutterKeyEvent* event) {
+        // Send key event to the engine.
+        FlutterEngineResult result = FlutterEngineSendKeyEvent(
+            embedder.engine, event, embedder::FuchsiaFlutterKeyEventCallback, &embedder);
+
+        // This is here for logging success/failure. Eventually remove.
+        if (result == kSuccess) {
+          FX_LOG(INFO, embedder::kLogTag, "FlutterEngineResult: SUCCESS");
+        } else {
+          FX_LOG(INFO, embedder::kLogTag, "FlutterEngineResult: FAIL");
+        }
+      });
+
   embedder::FlatlandViewProvider view_provider(embedder.flatland_connection.get(),
-                                               std::move(protocols));
+                                               std::move(view_ref_pair),
+                                               std::move(flatland_view_protocols));
 
   auto send_pointer_events_to_engine = [&embedder](std::vector<FlutterPointerEvent> events) {
     if (events.empty())
diff --git a/src/embedder/meta/embedder.cml b/src/embedder/meta/embedder.cml
index 1a14109..97c2788 100644
--- a/src/embedder/meta/embedder.cml
+++ b/src/embedder/meta/embedder.cml
@@ -57,6 +57,11 @@
                 "fuchsia.ui.composition.Allocator",
                 // For compositing/presenting views.
                 "fuchsia.ui.composition.Flatland",
+                // For connecting to the Ime service.
+                "fuchsia.ui.input.ImeService",
+                // For connecting to the Keyboard service.
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
             ]
         },
     ],
diff --git a/src/embedder/software_surface.cc b/src/embedder/software_surface.cc
index 6f72aaf..9ee30ed 100644
--- a/src/embedder/software_surface.cc
+++ b/src/embedder/software_surface.cc
@@ -311,10 +311,11 @@
   }
 
   if (read_finished_callback_ != nullptr) {
-    FX_LOG(ERROR, kLogTag,
-           "Attempted to signal a write on the surface when the "
-           "previous write has not yet been acknowledged by the "
-           "compositor.");
+    // Commented out until we figure out why this is being triggered. It's spamming the logs.
+    // FX_LOG(ERROR, kLogTag,
+    //        "Attempted to signal a write on the surface when the "
+    //        "previous write has not yet been acknowledged by the "
+    //        "compositor.");
     return;
   }
   read_finished_callback_ = on_read_finished;
diff --git a/src/embedder/text_delegate.cc b/src/embedder/text_delegate.cc
new file mode 100644
index 0000000..e211b5b
--- /dev/null
+++ b/src/embedder/text_delegate.cc
@@ -0,0 +1,235 @@
+// 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.
+
+#include "src/embedder/text_delegate.h"
+
+#include <fuchsia/ui/input/cpp/fidl.h>
+#include <fuchsia/ui/input3/cpp/fidl.h>
+#include <fuchsia/ui/views/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/syslog/global.h>
+#include <zircon/status.h>
+
+#include "logging.h"
+#include "src/embedder/engine/embedder.h"
+#include "src/embedder/fuchsia_logger.h"
+#include "src/embedder/keyboard.h"
+#include "src/embedder/logging.h"
+
+namespace embedder {
+
+// static constexpr char kInputActionKey[] = "inputAction";
+
+// See: https://api.flutter.dev/flutter/services/TextInputAction.html
+// Only the actions relevant for Fuchsia are listed here.
+static constexpr char kTextInputActionDone[] = "TextInputAction.done";
+static constexpr char kTextInputActionNewline[] = "TextInputAction.newline";
+static constexpr char kTextInputActionGo[] = "TextInputAction.go";
+static constexpr char kTextInputActionNext[] = "TextInputAction.next";
+static constexpr char kTextInputActionPrevious[] = "TextInputAction.previous";
+static constexpr char kTextInputActionNone[] = "TextInputAction.none";
+static constexpr char kTextInputActionSearch[] = "TextInputAction.search";
+static constexpr char kTextInputActionSend[] = "TextInputAction.send";
+static constexpr char kTextInputActionUnspecified[] = "TextInputAction.unspecified";
+
+[[maybe_unused]]
+// Converts Flutter TextInputAction to Fuchsia action enum.
+static fuchsia::ui::input::InputMethodAction
+IntoInputMethodAction(const std::string action_string) {
+  if (action_string == kTextInputActionNewline) {
+    return fuchsia::ui::input::InputMethodAction::NEWLINE;
+  } else if (action_string == kTextInputActionDone) {
+    return fuchsia::ui::input::InputMethodAction::DONE;
+  } else if (action_string == kTextInputActionGo) {
+    return fuchsia::ui::input::InputMethodAction::GO;
+  } else if (action_string == kTextInputActionNext) {
+    return fuchsia::ui::input::InputMethodAction::NEXT;
+  } else if (action_string == kTextInputActionPrevious) {
+    return fuchsia::ui::input::InputMethodAction::PREVIOUS;
+  } else if (action_string == kTextInputActionNone) {
+    return fuchsia::ui::input::InputMethodAction::NONE;
+  } else if (action_string == kTextInputActionSearch) {
+    return fuchsia::ui::input::InputMethodAction::SEARCH;
+  } else if (action_string == kTextInputActionSend) {
+    return fuchsia::ui::input::InputMethodAction::SEND;
+  } else if (action_string == kTextInputActionUnspecified) {
+    return fuchsia::ui::input::InputMethodAction::UNSPECIFIED;
+  }
+  // If this message comes along it means we should really add the missing 'if'
+  // above.
+  FX_LOGF(INFO, kLogTag, "unexpected action_string: %s", action_string.c_str());
+  // Substituting DONE for an unexpected action string will probably be OK.
+  return fuchsia::ui::input::InputMethodAction::DONE;
+}
+
+[[maybe_unused]]
+// Converts the Fuchsia action enum into Flutter TextInputAction.
+static const std::string
+IntoTextInputAction(fuchsia::ui::input::InputMethodAction action) {
+  if (action == fuchsia::ui::input::InputMethodAction::NEWLINE) {
+    return kTextInputActionNewline;
+  } else if (action == fuchsia::ui::input::InputMethodAction::DONE) {
+    return kTextInputActionDone;
+  } else if (action == fuchsia::ui::input::InputMethodAction::GO) {
+    return kTextInputActionGo;
+  } else if (action == fuchsia::ui::input::InputMethodAction::NEXT) {
+    return kTextInputActionNext;
+  } else if (action == fuchsia::ui::input::InputMethodAction::PREVIOUS) {
+    return kTextInputActionPrevious;
+  } else if (action == fuchsia::ui::input::InputMethodAction::NONE) {
+    return kTextInputActionNone;
+  } else if (action == fuchsia::ui::input::InputMethodAction::SEARCH) {
+    return kTextInputActionSearch;
+  } else if (action == fuchsia::ui::input::InputMethodAction::SEND) {
+    return kTextInputActionSend;
+  } else if (action == fuchsia::ui::input::InputMethodAction::UNSPECIFIED) {
+    return kTextInputActionUnspecified;
+  }
+  // If this message comes along it means we should really add the missing 'if'
+  // above.
+  FX_LOGF(INFO, kLogTag, "unexpected action: %d", static_cast<uint32_t>(action));
+
+  // Substituting "done" for an unexpected text input action will probably
+  // be OK.
+  return kTextInputActionDone;
+}
+
+// TODO(fxbug.dev/8868): Terminate embedder if Fuchsia system FIDL connections
+// have error.
+template <class T>
+void SetInterfaceErrorHandler(fidl::InterfacePtr<T>& interface, std::string name) {
+  interface.set_error_handler([name](zx_status_t status) {
+    FX_LOGF(ERROR, kLogTag, "Interface error on: %s, status: %s", name.c_str(),
+            zx_status_get_string(status));
+  });
+}
+
+template <class T>
+void SetInterfaceErrorHandler(fidl::Binding<T>& binding, std::string name) {
+  binding.set_error_handler([name](zx_status_t status) {
+    FX_LOGF(ERROR, kLogTag, "Binding error on: %s, status: %s", name.c_str(),
+            zx_status_get_string(status));
+  });
+}
+
+TextDelegate::TextDelegate(fuchsia::ui::views::ViewRef view_ref,
+                           fuchsia::ui::input::ImeServiceHandle ime_service,
+                           fuchsia::ui::input3::KeyboardHandle keyboard,
+                           std::function<void(const FlutterKeyEvent* event)> dispatch_callback)
+    : dispatch_callback_(dispatch_callback),
+      ime_client_(this),
+      text_sync_service_(ime_service.Bind()),
+      keyboard_listener_binding_(this),
+      keyboard_(keyboard.Bind()) {
+  // Register all error handlers.
+  SetInterfaceErrorHandler(ime_, "Input Method Editor");
+  SetInterfaceErrorHandler(ime_client_, "IME Client");
+  SetInterfaceErrorHandler(text_sync_service_, "Text Sync Service");
+  SetInterfaceErrorHandler(keyboard_listener_binding_, "Keyboard Listener");
+  SetInterfaceErrorHandler(keyboard_, "Keyboard");
+
+  // Configure keyboard listener.
+  keyboard_->AddListener(std::move(view_ref), keyboard_listener_binding_.NewBinding(), [] {});
+}
+
+void TextDelegate::ActivateIme() {
+  ActivateIme(requested_text_action_.value_or(fuchsia::ui::input::InputMethodAction::DONE));
+}
+
+void TextDelegate::ActivateIme(fuchsia::ui::input::InputMethodAction action) {
+  FX_DCHECK(last_text_state_.has_value());
+
+  requested_text_action_ = action;
+  text_sync_service_->GetInputMethodEditor(fuchsia::ui::input::KeyboardType::TEXT,  // keyboard type
+                                           action,                    // input method action
+                                           last_text_state_.value(),  // initial state
+                                           ime_client_.NewBinding(),  // client
+                                           ime_.NewRequest()          // editor
+  );
+}
+
+void TextDelegate::DeactivateIme() {
+  if (ime_) {
+    text_sync_service_->HideKeyboard();
+    ime_ = nullptr;
+  }
+  if (ime_client_.is_bound()) {
+    ime_client_.Unbind();
+  }
+}
+
+// |fuchsia::ui::input::InputMethodEditorClient|
+void TextDelegate::DidUpdateState(fuchsia::ui::input::TextInputState state,
+                                  std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) {
+  FX_LOG(ERROR, kLogTag, "TextDelegate::DidUpdateState not implemented.");
+}
+
+// |fuchsia::ui::input::InputMethodEditorClient|
+void TextDelegate::OnAction(fuchsia::ui::input::InputMethodAction action) {
+  FX_LOG(ERROR, kLogTag, "TextDelegate::OnAction not implemented.");
+}
+
+// |fuchsia::ui:input3::KeyboardListener|
+void TextDelegate::OnKeyEvent(
+    fuchsia::ui::input3::KeyEvent key_event,
+    fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback on_key_event_callback) {
+  const char* type = nullptr;
+  FlutterKeyEventType flutter_key_type;
+  switch (key_event.type()) {
+    case fuchsia::ui::input3::KeyEventType::PRESSED:
+      type = "keydown";
+      flutter_key_type = FlutterKeyEventType::kFlutterKeyEventTypeDown;
+      break;
+    case fuchsia::ui::input3::KeyEventType::RELEASED:
+      type = "keyup";
+      flutter_key_type = FlutterKeyEventType::kFlutterKeyEventTypeUp;
+      break;
+    case fuchsia::ui::input3::KeyEventType::SYNC:
+      // SYNC means the key was pressed while focus was not on this application.
+      // This should possibly behave like PRESSED in the future, though it
+      // doesn't hurt to ignore it today.
+    case fuchsia::ui::input3::KeyEventType::CANCEL:
+      // CANCEL means the key was released while focus was not on this
+      // application.
+      // This should possibly behave like RELEASED in the future to ensure that
+      // a key is not repeated forever if it is pressed while focus is lost.
+    default:
+      break;
+  }
+
+  if (type == nullptr) {
+    FX_LOGF(INFO, kLogTag, "Unknown key event phase.");
+
+    // Notify the key event wasn't handled by this keyboard listener.
+    on_key_event_callback(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED);
+    return;
+  }
+
+  keyboard_translator_.ConsumeEvent(std::move(key_event));
+
+  // Encourage the Engine to vsync for a 60Hz display.
+  const uint64_t now_nanos = FlutterEngineGetCurrentTime();
+  const uint64_t now_micro = now_nanos * 1e3;
+
+  FlutterKeyEvent flutter_key_event = {
+      .struct_size = sizeof(FlutterKeyEvent),
+      .timestamp = static_cast<double>(now_micro),
+      .type = flutter_key_type,
+      .physical = static_cast<uint64_t>(keyboard_translator_.LastHIDUsage()),
+      .logical = static_cast<uint64_t>(keyboard_translator_.LastCodePoint()),
+      .character = strcmp(type, "keydown") == 0
+                       ? keyboard_translator_.LastCodePointAsChar()
+                       : nullptr,  // Only assign the character on keydown strokes.
+      .synthesized =
+          true};  // For now we want the hardware keyboard to handle this event, and therefore the
+                  // synthesized field is set to true and needs to take this codepath:
+                  // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/hardware_keyboard.dart#L873
+
+  // Send key event to engine.
+  dispatch_callback_(&flutter_key_event);
+
+  // Notify the key event was handled by this keyboard listener.
+  on_key_event_callback(fuchsia::ui::input3::KeyEventStatus::HANDLED);
+}
+}  // namespace embedder
diff --git a/src/embedder/text_delegate.h b/src/embedder/text_delegate.h
new file mode 100644
index 0000000..d688e1c
--- /dev/null
+++ b/src/embedder/text_delegate.h
@@ -0,0 +1,143 @@
+// 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_TEXT_DELEGATE_H_
+#define SRC_EMBEDDER_TEXT_DELEGATE_H_
+
+#include <fuchsia/ui/input/cpp/fidl.h>
+#include <fuchsia/ui/input3/cpp/fidl.h>
+#include <fuchsia/ui/views/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+
+#include <memory>
+
+#include "src/embedder/engine/embedder.h"
+#include "src/embedder/keyboard.h"
+#include "src/embedder/logging.h"
+
+namespace embedder {
+
+/// The channel name used for text editing platofrm messages.
+constexpr char kTextInputChannel[] = "flutter/textinput";
+
+/// The channel name used for key event platform messages.
+constexpr char kKeyEventChannel[] = "flutter/keyevent";
+
+/// TextDelegate handles keyboard input and text editing.
+///
+/// It mediates between Fuchsia's input and Flutter's platform messages. When it
+/// is initialized, it contacts `fuchsia.ui.input.Keyboard` to register itself
+/// as listener of key events.
+///
+/// Whenever a text editing request comes from the
+/// Flutter app, it will activate Fuchsia's input method editor, and will send
+/// text edit actions coming from the Fuchsia platform over to the Flutter app,
+/// by converting FIDL messages (`fuchsia.ui.input.InputMethodEditorClient`
+/// calls) to appropriate text editing Flutter platform messages.
+///
+/// For details refer to:
+///   * Flutter side:
+///   https://api.flutter.dev/javadoc/io/flutter/embedding/engine/systemchannels/TextInputChannel.html
+///   * Fuchsia side: https://fuchsia.dev/reference/fidl/fuchsia.ui.input
+/// TODO(naudzghebre):
+///   1. Handle 'flutter/keyevent' platform messages
+///   2. Handle 'flutter/textinput' platform messages
+class TextDelegate : public fuchsia::ui::input3::KeyboardListener,
+                     public fuchsia::ui::input::InputMethodEditorClient {
+ public:
+  /// Creates a new TextDelegate.
+  ///
+  /// Args:
+  ///   view_ref: the reference to the app's view. Required for registration
+  ///     with Fuchsia.
+  ///   ime_service: a handle to Fuchsia's input method service.
+  ///   keyboard: the keyboard listener, gets notified of key presses and
+  ///     releases.
+  ///   dispatch_callback: a function used to send a Flutter platform message.
+  TextDelegate(fuchsia::ui::views::ViewRef view_ref,
+               fuchsia::ui::input::ImeServiceHandle ime_service,
+               fuchsia::ui::input3::KeyboardHandle keyboard,
+               std::function<void(const FlutterKeyEvent*)> dispatch_callback);
+  TextDelegate(const TextDelegate&) = delete;
+  TextDelegate& operator=(const TextDelegate&) = delete;
+
+  /// |fuchsia.ui.input3.KeyboardListener|
+  /// Called by the embedder every time there is a key event to process.
+  void OnKeyEvent(fuchsia::ui::input3::KeyEvent key_event,
+                  fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback callback) override;
+
+  /// |fuchsia::ui::input::InputMethodEditorClient|
+  /// Called by the embedder every time the edit state is updated.
+  void DidUpdateState(fuchsia::ui::input::TextInputState state,
+                      std::unique_ptr<fuchsia::ui::input::InputEvent> event) override;
+
+  /// |fuchsia::ui::input::InputMethodEditorClient|
+  /// Called by the embedder when the action key is pressed, and the requested
+  /// action is supplied to Flutter.
+  void OnAction(fuchsia::ui::input::InputMethodAction action) override;
+
+  /// Gets a new input method editor from the input connection. Run when both
+  /// Scenic has focus and Flutter has requested input with setClient.
+  void ActivateIme();
+
+  /// Detaches the input method editor connection, ending the edit session and
+  /// closing the onscreen keyboard. Call when input is no longer desired,
+  /// either because Scenic says we lost focus or when Flutter no longer has a
+  /// text field focused.
+  void DeactivateIme();
+
+  /// Returns true if there is a text state (i.e. if some text editing is in
+  /// progress).
+  bool HasTextState() { return last_text_state_.has_value(); }
+
+ private:
+  /// Activates the input method editor, assigning |action| to the "enter" key.
+  /// This action will be reported by |OnAction| above when the "enter" key is
+  /// pressed. Note that in the case of multi-line text editors, |OnAction| will
+  /// never be called: instead, the text editor will insert a newline into the
+  /// edited text.
+  void ActivateIme(fuchsia::ui::input::InputMethodAction action);
+
+  /// Converts Fuchsia platform key codes into Flutter key codes.
+  Keyboard keyboard_translator_;
+
+  /// A callback for sending a single Flutter platform message.
+  std::function<void(const FlutterKeyEvent* event)> dispatch_callback_;
+
+  /// TextDelegate server-side binding.  Methods called when the text edit state
+  /// is updated.
+  fidl::Binding<fuchsia::ui::input::InputMethodEditorClient> ime_client_;
+
+  /// An interface for interacting with a text input control.
+  fuchsia::ui::input::InputMethodEditorPtr ime_;
+
+  /// An interface for requesting the InputMethodEditor.
+  fuchsia::ui::input::ImeServicePtr text_sync_service_;
+
+  /// The locally-unique identifier of the text input currently in use. Flutter
+  /// usually uses only one at a time.
+  /// int current_text_input_client_ = 0;
+
+  /// TextDelegate server side binding. Methods called when a key is pressed.
+  fidl::Binding<fuchsia::ui::input3::KeyboardListener> keyboard_listener_binding_;
+
+  /// The client-side stub for calling the Keyboard protocol.
+  fuchsia::ui::input3::KeyboardPtr keyboard_;
+
+  /// last_text_state_ is the last state of the text input as reported by the IME
+  /// or initialized by Flutter. We set it to null if Flutter doesn't want any
+  /// input, since then there is no text input state at all.
+  /// If nullptr, then no editing is in progress.
+  std::optional<fuchsia::ui::input::TextInputState> last_text_state_;
+
+  /// The action that Flutter expects to happen when the user presses the "enter"
+  /// key.  For example, it could be `InputMethodAction::DONE` which would cause
+  /// text editing to stop and the current text to be accepted.
+  /// If set to std::nullopt, then no editing is in progress.
+  std::optional<fuchsia::ui::input::InputMethodAction> requested_text_action_;
+};
+
+}  // namespace embedder
+
+#endif  // SRC_EMBEDDER_TEXT_DELEGATE_H_
diff --git a/src/examples/hello_flutter/lib/main.dart b/src/examples/hello_flutter/lib/main.dart
index 6e82b47..e66ea45 100644
--- a/src/examples/hello_flutter/lib/main.dart
+++ b/src/examples/hello_flutter/lib/main.dart
@@ -1,4 +1,5 @@
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 
 void main() {
   print("Hello from Dart!");
@@ -51,6 +52,25 @@
 
 class _MyHomePageState extends State<MyHomePage> {
   int _counter = 0;
+  late TextEditingController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = TextEditingController();
+
+    // Temporary hack to display key events to the textfield.
+    HardwareKeyboard.instance.addHandler((event) {
+      _controller.text = _controller.text + (event.character ?? "");
+      return true;
+    });
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
 
   void _incrementCounter() {
     setState(() {
@@ -104,6 +124,19 @@
               '$_counter',
               style: Theme.of(context).textTheme.headline4,
             ),
+            Padding(
+              padding: EdgeInsets.all(15),
+              child: SizedBox(
+                width: 200,
+                child: TextField(
+                  controller: _controller,
+                  decoration: InputDecoration(
+                    border: OutlineInputBorder(),
+                    labelText: 'Input Text',
+                  ),
+                ),
+              ),
+            ),
           ],
         ),
       ),
diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel
new file mode 100644
index 0000000..04f2c62
--- /dev/null
+++ b/third_party/BUILD.bazel
@@ -0,0 +1,9 @@
+# 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.
+
+cc_library(
+    name = "rapidjson",
+    srcs = glob(["rapidjson/include/**/*.h"]),
+    visibility = ["//visibility:public"],
+)
diff --git a/third_party/rapidjson b/third_party/rapidjson
new file mode 160000
index 0000000..ef8fd47
--- /dev/null
+++ b/third_party/rapidjson
@@ -0,0 +1 @@
+Subproject commit ef8fd47794c25c265d978c66ab99ada5a3343f00