[flutter/keyevent, flutter/textinput] Add additional wiring to handle
flutter/keyevent and flutter/textinput channels.

- This should be at the same state for keyboard input as flutter runner
  impl in the engine repo.
- May need more investigating in the future, currently cursor doesn't
  move to end of text in TextField, but text is inputted and displays.

Change-Id: I90f324286acc7dee82f8dd67cbb16fe69ec06649
Reviewed-on: https://fuchsia-review.googlesource.com/c/flutter-embedder/+/761164
Reviewed-by: Ben Bergkamp <benbergkamp@google.com>
Reviewed-by: Alexander Biggs <akbiggs@google.com>
diff --git a/src/embedder/BUILD.bazel b/src/embedder/BUILD.bazel
index 7dc2973..5217c52 100644
--- a/src/embedder/BUILD.bazel
+++ b/src/embedder/BUILD.bazel
@@ -28,6 +28,7 @@
         "main.cc",
         "mouse_delegate.cc",
         "mouse_delegate.h",
+        "platform_message_channels.h",
         "pointer_utility.h",
         "software_surface.cc",
         "software_surface.h",
diff --git a/src/embedder/embedder_state.h b/src/embedder/embedder_state.h
index d08ee83..76cfa47 100644
--- a/src/embedder/embedder_state.h
+++ b/src/embedder/embedder_state.h
@@ -16,6 +16,7 @@
 #include "src/embedder/engine/embedder.h"
 #include "src/embedder/flatland_connection.h"
 #include "src/embedder/software_surface.h"
+#include "src/embedder/text_delegate.h"
 
 namespace embedder {
 
@@ -81,6 +82,10 @@
   /// The accessibility bridge responsible for intermediating
   /// accessibility-related calls between Fuchsia and Flutter
   std::unique_ptr<AccessibilityBridge> accessibility_bridge_;
+
+  /// The text delegate is responsible for handling keyboard input and text editing,
+  /// mediating between Fuchsia's input and Flutter's platform messages.
+  std::unique_ptr<TextDelegate> text_delegate_;
 };
 
 }  // namespace embedder
diff --git a/src/embedder/keyboard.cc b/src/embedder/keyboard.cc
index e2edd61..0a1e3e8 100644
--- a/src/embedder/keyboard.cc
+++ b/src/embedder/keyboard.cc
@@ -30,6 +30,14 @@
 
 namespace {
 
+/// The plane value for the private keys defined by the Fuchsia embedding.
+/// This is used by platform-specific code to generate Flutter key codes.
+///
+/// Make sure to match with the fuchsiaPlane defined in keyboard_key.g.dart
+/// which is a generated file.
+/// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_key.g.dart#L304
+static const uint64_t fuchsiaPlane = 0x01200000000;
+
 // 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
@@ -345,6 +353,8 @@
 
 uint32_t Keyboard::LastHIDUsage() { return GetLastKey() & 0xFFFFFFFF; }
 
+uint64_t Keyboard::LastLogicalKey() { return GetLastKey() | fuchsiaPlane; }
+
 uint16_t Keyboard::LastHIDUsageID() { return GetLastKey() & 0xFFFF; }
 
 uint16_t Keyboard::LastHIDUsagePage() { return (GetLastKey() >> 16) & 0xFFFF; }
diff --git a/src/embedder/keyboard.h b/src/embedder/keyboard.h
index 9c60a25..578f928 100644
--- a/src/embedder/keyboard.h
+++ b/src/embedder/keyboard.h
@@ -28,6 +28,7 @@
   /// the state of the modifier keys.
   uint32_t LastCodePoint();
 
+  /// Returns the value of the last key as a char.
   char* LastCodePointAsChar();
 
   /// Gets the last encountered HID usage.  This is a 32-bit number, with the
@@ -36,8 +37,21 @@
   ///
   /// The key corresponding to A will have the usage 0x7004. This function will
   /// return 0x7004 in that case.
+  ///
+  /// These values also act as Flutter IDs that map to physical key values according
+  /// to
+  /// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_maps.g.dart#L778
   uint32_t LastHIDUsage();
 
+  /// Gets the last encountered HID usage and assigns the Flutter ID for its logical value.
+  ///
+  /// This is done by applying the fuchsiaPlane value, which has higher order bits according to
+  /// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_key.g.dart#L304
+  ///
+  /// and whose values maps to the logical keys assigned according
+  /// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_maps.g.dart#L541
+  uint64_t LastLogicalKey();
+
   /// Gets the last encountered HID usage page.
   ///
   /// The key corresponding to A will have the usage 0x7004. This function will
diff --git a/src/embedder/main.cc b/src/embedder/main.cc
index 548bafb..0f8785d 100644
--- a/src/embedder/main.cc
+++ b/src/embedder/main.cc
@@ -26,6 +26,7 @@
 
 #include <string>
 
+#include "platform_message_channels.h"
 #include "src/embedder/accessibility_bridge.h"
 #include "src/embedder/embedder_state.h"
 #include "src/embedder/engine/embedder.h"
@@ -34,6 +35,7 @@
 #include "src/embedder/fuchsia_logger.h"
 #include "src/embedder/logging.h"
 #include "src/embedder/mouse_delegate.h"
+#include "src/embedder/platform_message_channels.h"
 #include "src/embedder/root_inspect_node.h"
 #include "src/embedder/software_surface.h"
 #include "src/embedder/text_delegate.h"
@@ -42,11 +44,6 @@
 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;
@@ -94,6 +91,11 @@
   if (!strcmp(message->channel, kAccessibilityChannel)) {
     EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
     embedder->accessibility_bridge_->HandlePlatformMessage(message);
+  } else if (!strcmp(message->channel, kTextInputChannel)) {
+    EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
+    embedder->text_delegate_->HandleFlutterTextInputChannelPlatformMessage(message);
+  } else {
+    FX_LOGF(INFO, embedder::kLogTag, "FuchsiaHandlePlatformMessage invoked: %s", message->channel);
   }
 }
 
@@ -120,9 +122,12 @@
 
 /// 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!");
+  FX_LOG(INFO, embedder::kLogTag, "FlutterKeyEventCallback invoked!");
+}
+
+void FuchsiaFlutterDataCallback(const uint8_t* data, size_t size, void* user_data) {
+  auto message_data = static_cast<const unsigned char*>(data);
+  FX_LOGF(INFO, embedder::kLogTag, "FuchsiaFlutterDataCallback invoked, data: %s", message_data);
 }
 
 /// Fuchsia implementation of the Flutter Engine's software rendering
@@ -131,8 +136,8 @@
                                    uint8_t** allocation, size_t* stride) {
   EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
 
-  // TODO(akbiggs): Anything we need to do to clean up the old software surface
-  // explicitly before reassigning here?
+  // TODO(akbiggs): Anything we need to do to clean up the old
+  // software surface explicitly before reassigning here?
   embedder->software_surface = std::make_unique<SoftwareSurface>(
       embedder->sysmem_allocator, embedder->flatland_allocator,
       Size{.width = static_cast<uint32_t>(width), .height = static_cast<uint32_t>(height)});
@@ -142,8 +147,8 @@
     return false;
   }
 
-  // If we receive an unitialized surface, we need to create a Flatland
-  // image and associate it with the surface.
+  // If we receive an unitialized surface, we need to create a
+  // Flatland image and associate it with the surface.
   if (embedder->software_surface->GetImageId() == 0) {
     auto image_id = embedder->flatland_connection->NextContentId().value;
     const auto surface_size = embedder->software_surface->GetSize();
@@ -196,9 +201,11 @@
   embedder->flatland_connection->Present();
 
   // Signal to the rendered surface that we've finished writing.
-  embedder->software_surface->SignalWritesFinished(/* on_read_finished = */ []() {
-    // TODO(akbiggs): Anything we need to do after the read finishes?
-  });
+  embedder->software_surface->SignalWritesFinished(
+      /* on_read_finished = */ []() {
+        // TODO(akbiggs): Anything we need to do after the read
+        // finishes?
+      });
 
   return true;
 }
@@ -261,14 +268,15 @@
 
   embedder::EmbedderState embedder = {
       // This must be initialized before calling RunFlutterApp, as
-      // this hack https://github.com/flutter/engine/pull/33472 relies
+      // this hack https://github.com/flutter/engine/pull/33472
+      // relies
       // on it.
       .component_context = sys::ComponentContext::Create(),
   };
 
   // Serving the component context must happen after RunFlutterApp
-  // because the embedder platform has a hack that adds inspect data into the
-  // Dart VM using the component context
+  // because the embedder platform has a hack that adds inspect data
+  // into the Dart VM using the component context
   // (https://github.com/flutter/engine/pull/33472).
   if (!embedder::RunFlutterApp(assets_path, &embedder)) {
     return EXIT_FAILURE;
@@ -326,11 +334,11 @@
             zx_status_get_string(status));
     return EXIT_FAILURE;
   }
-  // TODO(akbiggs): Create this on the raster thread per FlatlandConnection's
-  // documentation.
+  // TODO(akbiggs): Create this on the raster thread per
+  // FlatlandConnection's documentation.
   embedder.flatland_connection = std::make_unique<embedder::FlatlandConnection>(
-      // TODO(akbiggs): Pick a more appropriate debug name based on the Flutter
-      // app name.
+      // TODO(akbiggs): Pick a more appropriate debug name based
+      // on the Flutter app name.
       "FlutterEmbedder" /* debug_name */, std::move(flatland_handle),
       /* on_error_callback = */
       []() {
@@ -338,11 +346,18 @@
       },
       /* on_frame_presented_callback = */
       [](fuchsia::scenic::scheduling::FramePresentedInfo info) {
-        // TODO(akbiggs): What should we do after a frame finishes presenting?
+        // TODO(akbiggs): What should we do after a frame
+        // finishes presenting?
       });
 
+  // Focuser for programatically transfering View focus.
+  fuchsia::ui::views::FocuserHandle focuser;
+  fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused;
+
   // The protocol endpoints bound to a Flatland ViewCreationToken.
   fuchsia::ui::composition::ViewBoundProtocols flatland_view_protocols;
+  flatland_view_protocols.set_view_focuser(focuser.NewRequest());
+  flatland_view_protocols.set_view_ref_focused(view_ref_focused.NewRequest());
 
   // Mouse & Touch source
   fuchsia::ui::pointer::MouseSourceHandle mouse_source;
@@ -357,21 +372,36 @@
   fuchsia::ui::views::ViewRef accessibility_view_ref;
   view_ref_pair.view_ref.Clone(&accessibility_view_ref);
 
+  // Response handle attached to a FlutterPlatformMessage to be called in the engine.
+  FlutterPlatformMessageResponseHandle* response_handle;
+  FlutterPlatformMessageCreateResponseHandle(embedder.engine, embedder::FuchsiaFlutterDataCallback,
+                                             &embedder, &response_handle);
+
   // Keyboard/Text input.
-  auto text_delegate = std::make_unique<embedder::TextDelegate>(
+  embedder.text_delegate_ = std::make_unique<embedder::TextDelegate>(
       std::move(platform_view_ref), std::move(ime_service), std::move(keyboard),
+      /* key_event_dispatch_callback: [flutter/keydata] */
       [&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");
+        if (result != kSuccess) {
+          FX_LOGF(INFO, embedder::kLogTag, "FlutterEngineSendKeyEvent failed for the %s channel.",
+                  embedder::kKeyDataChannel);
         }
-      });
+      },
+      /*platform_dispatch_callback: [flutter/keyevent, flutter/textinput] */
+      [&embedder](const FlutterPlatformMessage* message) {
+        // Send platform message to the engine.
+        FlutterEngineResult result = FlutterEngineSendPlatformMessage(embedder.engine, message);
+
+        if (result != kSuccess) {
+          FX_LOGF(INFO, embedder::kLogTag,
+                  "FlutterEngineSendPlatformMessage failed for the %s channel.", message->channel);
+        }
+      },
+      response_handle);
 
   embedder::FlatlandViewProvider view_provider(embedder.flatland_connection.get(),
                                                std::move(view_ref_pair),
@@ -401,8 +431,9 @@
     return EXIT_FAILURE;
   }
 
-  // Our run loop's dispatcher must be used to serve the component context in
-  // order for requests' handlers to get called while we're looping.
+  // Our run loop's dispatcher must be used to serve the component
+  // context in order for requests' handlers to get called while
+  // we're looping.
   status = embedder.component_context->outgoing()->ServeFromStartupInfo();
   if (status != ZX_OK) {
     FX_LOGF(ERROR, embedder::kLogTag, "Failed to serve component context: %s",
diff --git a/src/embedder/platform_message_channels.h b/src/embedder/platform_message_channels.h
new file mode 100644
index 0000000..b183a53
--- /dev/null
+++ b/src/embedder/platform_message_channels.h
@@ -0,0 +1,30 @@
+// 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_EMBEDDER_PLATFORM_MESSAGE_CHANNELS_H_
+#define SRC_EMBEDDER_PLATFORM_MESSAGE_CHANNELS_H_
+
+namespace embedder {
+
+/// Platform messages sent from the flutter engine include a
+/// channel to indicate what type of message they are.
+
+// Accessibility channel.
+constexpr char kAccessibilityChannel[] = "flutter/accessibility";
+
+// Key data channel for sending key input events. This is the preferred
+// channel for sending these events in the future and flutter/keyevent will
+// be deprecated. For now, we implement both.
+constexpr char kKeyDataChannel[] = "flutter/keydata";
+
+/// The channel name used for key event platform messages. This chanel is used for sending raw
+/// native events and will be deprecated in the future.
+constexpr char kKeyEventChannel[] = "flutter/keyevent";
+
+/// The channel name used for text editing platform messages.
+constexpr char kTextInputChannel[] = "flutter/textinput";
+
+}  // namespace embedder
+
+#endif  // SRC_EMBEDDER_PLATFORM_MESSAGE_CHANNELS_H_
diff --git a/src/embedder/text_delegate.cc b/src/embedder/text_delegate.cc
index e211b5b..a2e3c55 100644
--- a/src/embedder/text_delegate.cc
+++ b/src/embedder/text_delegate.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#define RAPIDJSON_HAS_STDSTRING 1
+
 #include "src/embedder/text_delegate.h"
 
 #include <fuchsia/ui/input/cpp/fidl.h>
@@ -11,15 +13,20 @@
 #include <lib/syslog/global.h>
 #include <zircon/status.h>
 
+#include "include/rapidjson/document.h"
+#include "include/rapidjson/rapidjson.h"
+#include "include/rapidjson/stringbuffer.h"
+#include "include/rapidjson/writer.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"
+#include "src/embedder/platform_message_channels.h"
 
 namespace embedder {
 
-// static constexpr char kInputActionKey[] = "inputAction";
+static constexpr char kInputActionKey[] = "inputAction";
 
 // See: https://api.flutter.dev/flutter/services/TextInputAction.html
 // Only the actions relevant for Fuchsia are listed here.
@@ -113,11 +120,15 @@
   });
 }
 
-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),
+TextDelegate::TextDelegate(
+    fuchsia::ui::views::ViewRef view_ref, fuchsia::ui::input::ImeServiceHandle ime_service,
+    fuchsia::ui::input3::KeyboardHandle keyboard,
+    std::function<void(const FlutterKeyEvent*)> key_event_dispatch_callback,
+    std::function<void(const FlutterPlatformMessage*)> platform_dispatch_callback,
+    FlutterPlatformMessageResponseHandle* response_handle)
+    : key_event_dispatch_callback_(key_event_dispatch_callback),
+      platform_dispatch_callback_(platform_dispatch_callback),
+      response_handle_(response_handle),
       ime_client_(this),
       text_sync_service_(ime_service.Bind()),
       keyboard_listener_binding_(this),
@@ -159,15 +170,227 @@
   }
 }
 
-// |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.");
+  rapidjson::Document document;
+  auto& allocator = document.GetAllocator();
+  rapidjson::Value encoded_state(rapidjson::kObjectType);
+  encoded_state.AddMember("text", state.text, allocator);
+  encoded_state.AddMember("selectionBase", state.selection.base, allocator);
+  encoded_state.AddMember("selectionExtent", state.selection.extent, allocator);
+  switch (state.selection.affinity) {
+    case fuchsia::ui::input::TextAffinity::UPSTREAM:
+      encoded_state.AddMember("selectionAffinity", rapidjson::Value("TextAffinity.upstream"),
+                              allocator);
+      break;
+    case fuchsia::ui::input::TextAffinity::DOWNSTREAM:
+      encoded_state.AddMember("selectionAffinity", rapidjson::Value("TextAffinity.downstream"),
+                              allocator);
+      break;
+  }
+  encoded_state.AddMember("selectionIsDirectional", true, allocator);
+  encoded_state.AddMember("composingBase", state.composing.start, allocator);
+  encoded_state.AddMember("composingExtent", state.composing.end, allocator);
+
+  rapidjson::Value args(rapidjson::kArrayType);
+  args.PushBack(current_text_input_client_, allocator);
+  args.PushBack(encoded_state, allocator);
+
+  document.SetObject();
+  document.AddMember("method", rapidjson::Value("TextInputClient.updateEditingState"), allocator);
+  document.AddMember("args", args, allocator);
+
+  rapidjson::StringBuffer buffer;
+  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+  document.Accept(writer);
+
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
+  FlutterPlatformMessage flutter_platform_message = {.struct_size = sizeof(FlutterPlatformMessage),
+                                                     .channel = kTextInputChannel,
+                                                     .message = data,
+                                                     .message_size = buffer.GetSize(),
+                                                     .response_handle = response_handle_};
+  platform_dispatch_callback_(&flutter_platform_message);
+  last_text_state_ = std::move(state);
 }
 
-// |fuchsia::ui::input::InputMethodEditorClient|
 void TextDelegate::OnAction(fuchsia::ui::input::InputMethodAction action) {
-  FX_LOG(ERROR, kLogTag, "TextDelegate::OnAction not implemented.");
+  rapidjson::Document document;
+  auto& allocator = document.GetAllocator();
+
+  rapidjson::Value args(rapidjson::kArrayType);
+  args.PushBack(current_text_input_client_, allocator);
+
+  const std::string action_string = IntoTextInputAction(action);
+  args.PushBack(rapidjson::Value{}.SetString(action_string.c_str(), action_string.length()),
+                allocator);
+
+  document.SetObject();
+  document.AddMember("method", rapidjson::Value("TextInputClient.performAction"), allocator);
+  document.AddMember("args", args, allocator);
+
+  rapidjson::StringBuffer buffer;
+  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+  document.Accept(writer);
+
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
+  FlutterPlatformMessage flutter_platform_message = {.struct_size = sizeof(FlutterPlatformMessage),
+                                                     .channel = kTextInputChannel,
+                                                     .message = data,
+                                                     .message_size = buffer.GetSize(),
+                                                     .response_handle = response_handle_};
+  platform_dispatch_callback_(&flutter_platform_message);
+}
+
+// Channel handler for kTextInputChannel
+bool TextDelegate::HandleFlutterTextInputChannelPlatformMessage(
+    const FlutterPlatformMessage* message) {
+  FX_DCHECK(!strcmp(message->channel, kTextInputChannel));
+
+  const auto& data = message->message;
+
+  rapidjson::Document document;
+  document.Parse(reinterpret_cast<const char*>(data), message->message_size);
+  if (document.HasParseError()) {
+    return false;
+  }
+
+  if (!document.IsObject()) {
+    return false;
+  }
+
+  auto root = document.GetObject();
+  auto method = root.FindMember("method");
+  if (method == root.MemberEnd() || !method->value.IsString()) {
+    return false;
+  }
+
+  if (method->value == "TextInput.show") {
+    if (ime_) {
+      text_sync_service_->ShowKeyboard();
+    }
+  } else if (method->value == "TextInput.hide") {
+    if (ime_) {
+      text_sync_service_->HideKeyboard();
+    }
+  } else if (method->value == "TextInput.setClient") {
+    // Sample "setClient" message:
+    //
+    // {
+    //   "method": "TextInput.setClient",
+    //   "args": [
+    //      7,
+    //      {
+    //        "inputType": {
+    //          "name": "TextInputType.multiline",
+    //          "signed":null,
+    //          "decimal":null
+    //        },
+    //        "readOnly": false,
+    //        "obscureText": false,
+    //        "autocorrect":true,
+    //        "smartDashesType":"1",
+    //        "smartQuotesType":"1",
+    //        "enableSuggestions":true,
+    //        "enableInteractiveSelection":true,
+    //        "actionLabel":null,
+    //        "inputAction":"TextInputAction.newline",
+    //        "textCapitalization":"TextCapitalization.none",
+    //        "keyboardAppearance":"Brightness.dark",
+    //        "enableIMEPersonalizedLearning":true,
+    //        "enableDeltaModel":false
+    //     }
+    //  ]
+    // }
+
+    current_text_input_client_ = 0;
+    DeactivateIme();
+    auto args = root.FindMember("args");
+    if (args == root.MemberEnd() || !args->value.IsArray() || args->value.Size() != 2)
+      return false;
+    const auto& configuration = args->value[1];
+    if (!configuration.IsObject()) {
+      return false;
+    }
+    // TODO(abarth): Read the keyboard type from the configuration.
+    current_text_input_client_ = args->value[0].GetInt();
+
+    auto initial_text_input_state = fuchsia::ui::input::TextInputState{};
+    initial_text_input_state.text = "";
+    last_text_state_ = std::move(initial_text_input_state);
+
+    const auto configuration_object = configuration.GetObject();
+    if (!configuration_object.HasMember(kInputActionKey)) {
+      return false;
+    }
+    const auto& action_object = configuration_object[kInputActionKey];
+    if (!action_object.IsString()) {
+      return false;
+    }
+    const auto action_string =
+        std::string(action_object.GetString(), action_object.GetStringLength());
+    ActivateIme(IntoInputMethodAction(std::move(action_string)));
+  } else if (method->value == "TextInput.setEditingState") {
+    if (ime_) {
+      auto args_it = root.FindMember("args");
+      if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
+        return false;
+      }
+      const auto& args = args_it->value;
+      fuchsia::ui::input::TextInputState state;
+      state.text = "";
+      // TODO(abarth): Deserialize state.
+      auto text = args.FindMember("text");
+      if (text != args.MemberEnd() && text->value.IsString()) {
+        state.text = text->value.GetString();
+      }
+      auto selection_base = args.FindMember("selectionBase");
+      if (selection_base != args.MemberEnd() && selection_base->value.IsInt()) {
+        state.selection.base = selection_base->value.GetInt();
+      }
+      auto selection_extent = args.FindMember("selectionExtent");
+      if (selection_extent != args.MemberEnd() && selection_extent->value.IsInt()) {
+        state.selection.extent = selection_extent->value.GetInt();
+      }
+      auto selection_affinity = args.FindMember("selectionAffinity");
+      if (selection_affinity != args.MemberEnd() && selection_affinity->value.IsString() &&
+          selection_affinity->value == "TextAffinity.upstream") {
+        state.selection.affinity = fuchsia::ui::input::TextAffinity::UPSTREAM;
+      } else {
+        state.selection.affinity = fuchsia::ui::input::TextAffinity::DOWNSTREAM;
+      }
+      // We ignore selectionIsDirectional because that concept doesn't exist on
+      // Fuchsia.
+      auto composing_base = args.FindMember("composingBase");
+      if (composing_base != args.MemberEnd() && composing_base->value.IsInt()) {
+        state.composing.start = composing_base->value.GetInt();
+      }
+      auto composing_extent = args.FindMember("composingExtent");
+      if (composing_extent != args.MemberEnd() && composing_extent->value.IsInt()) {
+        state.composing.end = composing_extent->value.GetInt();
+      }
+      ime_->SetState(std::move(state));
+    }
+  } else if (method->value == "TextInput.clearClient") {
+    current_text_input_client_ = 0;
+    last_text_state_ = std::nullopt;
+    requested_text_action_ = std::nullopt;
+    DeactivateIme();
+  } else if (method->value == "TextInput.setCaretRect" ||
+             method->value == "TextInput.setEditableSizeAndTransform" ||
+             method->value == "TextInput.setMarkedTextRect" ||
+             method->value == "TextInput.setStyle") {
+    // We don't have these methods implemented and they get
+    // sent a lot during text input, so we create an empty case for them
+    // here to avoid "Unknown flutter/textinput method TextInput.*"
+    // log spam.
+    //
+    // TODO(fxb/101619): We should implement these.
+  } else {
+    FX_LOGF(ERROR, kLogTag, "Unknown %s method %s", message->channel, method->value.GetString());
+  }
+  // Complete with an empty response.
+  return false;
 }
 
 // |fuchsia::ui:input3::KeyboardListener|
@@ -212,24 +435,49 @@
   const uint64_t now_nanos = FlutterEngineGetCurrentTime();
   const uint64_t now_micro = now_nanos * 1e3;
 
-  FlutterKeyEvent flutter_key_event = {
+  // The character representation for key-up strokes should be null as the
+  // true character is already sent to the engine on the key-down stroke.
+  static char keyUp[1] = {'\0'};
+
+  // Key message to be passed via flutter's flutter/keydata channel.
+  FlutterKeyEvent flutter_key_data_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()),
+      .logical = static_cast<uint64_t>(keyboard_translator_.LastLogicalKey()),
       .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
+                       : keyUp,  // Only assign the character on keydown strokes and
+      .synthesized = false};
 
-  // Send key event to engine.
-  dispatch_callback_(&flutter_key_event);
+  // Send key data event to engine.
+  key_event_dispatch_callback_(&flutter_key_data_event);
 
-  // Notify the key event was handled by this keyboard listener.
+  // Key message to be passed via flutter's flutter/keyevent channel. To be deprecated in the
+  // future in favor of flutter/keydata channel.
+  rapidjson::Document document;
+  auto& allocator = document.GetAllocator();
+  document.SetObject();
+  document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator);
+  document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator);
+  document.AddMember("hidUsage", keyboard_translator_.LastHIDUsage(), allocator);
+  document.AddMember("codePoint", keyboard_translator_.LastCodePoint(), allocator);
+  document.AddMember("modifiers", keyboard_translator_.Modifiers(), allocator);
+  rapidjson::StringBuffer buffer;
+  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+  document.Accept(writer);
+
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
+  FlutterPlatformMessage flutter_raw_event = {.struct_size = sizeof(FlutterPlatformMessage),
+                                              .channel = kKeyEventChannel,
+                                              .message = data,
+                                              .message_size = buffer.GetSize(),
+                                              .response_handle = response_handle_};
+
+  // Send raw key event to engine.
+  platform_dispatch_callback_(&flutter_raw_event);
+
   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
index d688e1c..345693c 100644
--- a/src/embedder/text_delegate.h
+++ b/src/embedder/text_delegate.h
@@ -18,12 +18,6 @@
 
 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
@@ -40,9 +34,6 @@
 ///   * 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:
@@ -58,7 +49,9 @@
   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);
+               std::function<void(const FlutterKeyEvent*)> key_event_dispatch_callback,
+               std::function<void(const FlutterPlatformMessage*)> platform_dispatch_callback,
+               FlutterPlatformMessageResponseHandle* response_handle);
   TextDelegate(const TextDelegate&) = delete;
   TextDelegate& operator=(const TextDelegate&) = delete;
 
@@ -77,6 +70,8 @@
   /// action is supplied to Flutter.
   void OnAction(fuchsia::ui::input::InputMethodAction action) override;
 
+  bool HandleFlutterTextInputChannelPlatformMessage(const FlutterPlatformMessage* message);
+
   /// 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();
@@ -102,8 +97,10 @@
   /// 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_;
+  std::function<void(const FlutterKeyEvent*)> key_event_dispatch_callback_;
+  std::function<void(const FlutterPlatformMessage*)> platform_dispatch_callback_;
+
+  FlutterPlatformMessageResponseHandle* response_handle_;
 
   /// TextDelegate server-side binding.  Methods called when the text edit state
   /// is updated.
@@ -117,7 +114,7 @@
 
   /// 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;
+  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_;
diff --git a/src/examples/hello_flutter/lib/main.dart b/src/examples/hello_flutter/lib/main.dart
index e66ea45..69c6870 100644
--- a/src/examples/hello_flutter/lib/main.dart
+++ b/src/examples/hello_flutter/lib/main.dart
@@ -58,12 +58,6 @@
   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
diff --git a/src/examples/hello_flutter/pubspec.lock b/src/examples/hello_flutter/pubspec.lock
index 913be73..bc4f11c 100644
--- a/src/examples/hello_flutter/pubspec.lock
+++ b/src/examples/hello_flutter/pubspec.lock
@@ -156,10 +156,10 @@
     dependency: transitive
     description:
       name: string_scanner
-      sha256: "862015c5db1f3f3c4ea3b94dc2490363a84262994b88902315ed74be1155612f"
+      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
       url: "https://pub.dev"
     source: hosted
-    version: "1.1.1"
+    version: "1.2.0"
   term_glyph:
     dependency: transitive
     description:
@@ -172,10 +172,10 @@
     dependency: transitive
     description:
       name: test_api
-      sha256: c9aba3b3dbfe8878845dfab5fa096eb8de7b62231baeeb1cea8e3ee81ca8c6d8
+      sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
       url: "https://pub.dev"
     source: hosted
-    version: "0.4.15"
+    version: "0.4.16"
   vector_math:
     dependency: transitive
     description:
diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel
index 04f2c62..b998032 100644
--- a/third_party/BUILD.bazel
+++ b/third_party/BUILD.bazel
@@ -4,6 +4,6 @@
 
 cc_library(
     name = "rapidjson",
-    srcs = glob(["rapidjson/include/**/*.h"]),
+    srcs = glob(["**/*.h"]),
     visibility = ["//visibility:public"],
 )