Split FlKeyboardManager out of FlKeyboardHandler (#55892)

They continue to share a FlKeyboardViewDelegate, but this will be split
too in a later change as part of moving these classes from FlView to
FlEngine.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index fd9a96d..283a93a 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -44799,6 +44799,9 @@
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout_test.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager_test.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc + ../../../flutter/LICENSE
@@ -47699,6 +47702,9 @@
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout.h
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout_test.cc
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.cc
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.h
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc
diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn
index 22c192e..a28e375 100644
--- a/shell/platform/linux/BUILD.gn
+++ b/shell/platform/linux/BUILD.gn
@@ -81,6 +81,7 @@
              "fl_dart_project_private.h",
              "fl_engine_private.h",
              "fl_keyboard_handler.h",
+             "fl_keyboard_manager.h",
              "fl_keyboard_pending_event.h",
              "fl_keyboard_view_delegate.h",
              "fl_key_event.h",
@@ -118,6 +119,7 @@
     "fl_key_responder.cc",
     "fl_keyboard_handler.cc",
     "fl_keyboard_layout.cc",
+    "fl_keyboard_manager.cc",
     "fl_keyboard_pending_event.cc",
     "fl_keyboard_view_delegate.cc",
     "fl_message_codec.cc",
@@ -217,6 +219,7 @@
     "fl_key_embedder_responder_test.cc",
     "fl_keyboard_handler_test.cc",
     "fl_keyboard_layout_test.cc",
+    "fl_keyboard_manager_test.cc",
     "fl_message_codec_test.cc",
     "fl_method_channel_test.cc",
     "fl_method_codec_test.cc",
diff --git a/shell/platform/linux/fl_keyboard_handler.cc b/shell/platform/linux/fl_keyboard_handler.cc
index e914925..266b9b1 100644
--- a/shell/platform/linux/fl_keyboard_handler.cc
+++ b/shell/platform/linux/fl_keyboard_handler.cc
@@ -4,376 +4,23 @@
 
 #include "flutter/shell/platform/linux/fl_keyboard_handler.h"
 
-#include <array>
-#include <cinttypes>
-#include <memory>
-#include <string>
-
-#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
-#include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
-#include "flutter/shell/platform/linux/fl_keyboard_layout.h"
-#include "flutter/shell/platform/linux/fl_keyboard_pending_event.h"
-#include "flutter/shell/platform/linux/key_mapping.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
 
-// Turn on this flag to print complete layout data when switching IMEs. The data
-// is used in unit tests.
-#define DEBUG_PRINT_LAYOUT
-
 static constexpr char kChannelName[] = "flutter/keyboard";
 static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
 
-/* Declarations of private classes */
-
-G_DECLARE_FINAL_TYPE(FlKeyboardHandlerUserData,
-                     fl_keyboard_handler_user_data,
-                     FL,
-                     KEYBOARD_HANDLER_USER_DATA,
-                     GObject);
-
-/* End declarations */
-
-namespace {
-
-static bool is_eascii(uint16_t character) {
-  return character < 256;
-}
-
-#ifdef DEBUG_PRINT_LAYOUT
-// Prints layout entries that will be parsed by `MockLayoutData`.
-void debug_format_layout_data(std::string& debug_layout_data,
-                              uint16_t keycode,
-                              uint16_t clue1,
-                              uint16_t clue2) {
-  if (keycode % 4 == 0) {
-    debug_layout_data.append("    ");
-  }
-
-  constexpr int kBufferSize = 30;
-  char buffer[kBufferSize];
-  buffer[0] = 0;
-  buffer[kBufferSize - 1] = 0;
-
-  snprintf(buffer, kBufferSize, "0x%04x, 0x%04x, ", clue1, clue2);
-  debug_layout_data.append(buffer);
-
-  if (keycode % 4 == 3) {
-    snprintf(buffer, kBufferSize, " // 0x%02x", keycode);
-    debug_layout_data.append(buffer);
-  }
-}
-#endif
-
-}  // namespace
-
-/* Define FlKeyboardHandlerUserData */
-
-/**
- * FlKeyboardHandlerUserData:
- * The user_data used when #FlKeyboardHandler sends event to
- * responders.
- */
-
-struct _FlKeyboardHandlerUserData {
-  GObject parent_instance;
-
-  // The owner handler.
-  GWeakRef handler;
-  uint64_t sequence_id;
-};
-
-G_DEFINE_TYPE(FlKeyboardHandlerUserData,
-              fl_keyboard_handler_user_data,
-              G_TYPE_OBJECT)
-
-static void fl_keyboard_handler_user_data_dispose(GObject* object) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER_USER_DATA(object));
-  FlKeyboardHandlerUserData* self = FL_KEYBOARD_HANDLER_USER_DATA(object);
-
-  g_weak_ref_clear(&self->handler);
-
-  G_OBJECT_CLASS(fl_keyboard_handler_user_data_parent_class)->dispose(object);
-}
-
-static void fl_keyboard_handler_user_data_class_init(
-    FlKeyboardHandlerUserDataClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_user_data_dispose;
-}
-
-static void fl_keyboard_handler_user_data_init(
-    FlKeyboardHandlerUserData* self) {}
-
-// Creates a new FlKeyboardHandlerUserData private class with all information.
-static FlKeyboardHandlerUserData* fl_keyboard_handler_user_data_new(
-    FlKeyboardHandler* handler,
-    uint64_t sequence_id) {
-  FlKeyboardHandlerUserData* self = FL_KEYBOARD_HANDLER_USER_DATA(
-      g_object_new(fl_keyboard_handler_user_data_get_type(), nullptr));
-
-  g_weak_ref_init(&self->handler, handler);
-  self->sequence_id = sequence_id;
-  return self;
-}
-
-/* Define FlKeyboardHandler */
-
 struct _FlKeyboardHandler {
   GObject parent_instance;
 
   GWeakRef view_delegate;
 
-  // An array of #FlKeyResponder. Elements are added with
-  // #fl_keyboard_handler_add_responder immediately after initialization and are
-  // automatically released on dispose.
-  GPtrArray* responder_list;
-
-  // An array of #FlKeyboardPendingEvent.
-  //
-  // Its elements are *not* unreferenced when removed. When FlKeyboardHandler is
-  // disposed, this array will be set with a free_func so that the elements are
-  // unreferenced when removed.
-  GPtrArray* pending_responds;
-
-  // An array of #FlKeyboardPendingEvent.
-  //
-  // Its elements are unreferenced when removed.
-  GPtrArray* pending_redispatches;
-
-  // The last sequence ID used. Increased by 1 by every use.
-  uint64_t last_sequence_id;
-
-  // Record the derived layout.
-  //
-  // It is cleared when the platform reports a layout switch. Each entry,
-  // which corresponds to a group, is only initialized on the arrival of the
-  // first event for that group that has a goal keycode.
-  FlKeyboardLayout* derived_layout;
-
-  // A static map from keycodes to all layout goals.
-  //
-  // It is set up when the handler is initialized and is not changed ever after.
-  std::unique_ptr<std::map<uint16_t, const LayoutGoal*>> keycode_to_goals;
-
-  // A static map from logical keys to all mandatory layout goals.
-  //
-  // It is set up when the handler is initialized and is not changed ever after.
-  std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
-      logical_to_mandatory_goals;
-
   // The channel used by the framework to query the keyboard pressed state.
   FlMethodChannel* channel;
 };
 
 G_DEFINE_TYPE(FlKeyboardHandler, fl_keyboard_handler, G_TYPE_OBJECT);
 
-// This is an exact copy of g_ptr_array_find_with_equal_func.  Somehow CI
-// reports that can not find symbol g_ptr_array_find_with_equal_func, despite
-// the fact that it runs well locally.
-static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack,
-                                                  gconstpointer needle,
-                                                  GEqualFunc equal_func,
-                                                  guint* index_) {
-  guint i;
-  g_return_val_if_fail(haystack != NULL, FALSE);
-  if (equal_func == NULL) {
-    equal_func = g_direct_equal;
-  }
-  for (i = 0; i < haystack->len; i++) {
-    if (equal_func(g_ptr_array_index(haystack, i), needle)) {
-      if (index_ != NULL) {
-        *index_ = i;
-      }
-      return TRUE;
-    }
-  }
-
-  return FALSE;
-}
-
-// Compare a #FlKeyboardPendingEvent with the given sequence_id.
-static gboolean compare_pending_by_sequence_id(gconstpointer a,
-                                               gconstpointer b) {
-  FlKeyboardPendingEvent* pending =
-      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
-  uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(b);
-  return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id;
-}
-
-// Compare a #FlKeyboardPendingEvent with the given hash.
-static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) {
-  FlKeyboardPendingEvent* pending =
-      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
-  uint64_t hash = *reinterpret_cast<const uint64_t*>(b);
-  return fl_keyboard_pending_event_get_hash(pending) == hash;
-}
-
-// Try to remove a pending event from `pending_redispatches` with the target
-// hash.
-//
-// Returns true if the event is found and removed.
-static bool fl_keyboard_handler_remove_redispatched(FlKeyboardHandler* self,
-                                                    uint64_t hash) {
-  guint result_index;
-  gboolean found = g_ptr_array_find_with_equal_func1(
-      self->pending_redispatches, static_cast<const uint64_t*>(&hash),
-      compare_pending_by_hash, &result_index);
-  if (found) {
-    // The removed object is freed due to `pending_redispatches`'s free_func.
-    g_ptr_array_remove_index_fast(self->pending_redispatches, result_index);
-    return TRUE;
-  } else {
-    return FALSE;
-  }
-}
-
-// The callback used by a responder after the event was dispatched.
-static void responder_handle_event_callback(bool handled,
-                                            gpointer user_data_ptr) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER_USER_DATA(user_data_ptr));
-  FlKeyboardHandlerUserData* user_data =
-      FL_KEYBOARD_HANDLER_USER_DATA(user_data_ptr);
-
-  g_autoptr(FlKeyboardHandler) self =
-      FL_KEYBOARD_HANDLER(g_weak_ref_get(&user_data->handler));
-  if (self == nullptr) {
-    return;
-  }
-
-  g_autoptr(FlKeyboardViewDelegate) view_delegate =
-      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
-  if (view_delegate == nullptr) {
-    return;
-  }
-
-  guint result_index = -1;
-  gboolean found = g_ptr_array_find_with_equal_func1(
-      self->pending_responds, &user_data->sequence_id,
-      compare_pending_by_sequence_id, &result_index);
-  g_return_if_fail(found);
-  FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT(
-      g_ptr_array_index(self->pending_responds, result_index));
-  g_return_if_fail(pending != nullptr);
-  fl_keyboard_pending_event_mark_replied(pending, handled);
-  // All responders have replied.
-  if (fl_keyboard_pending_event_is_complete(pending)) {
-    g_object_unref(user_data_ptr);
-    gpointer removed =
-        g_ptr_array_remove_index_fast(self->pending_responds, result_index);
-    g_return_if_fail(removed == pending);
-    bool should_redispatch =
-        !fl_keyboard_pending_event_get_any_handled(pending) &&
-        !fl_keyboard_view_delegate_text_filter_key_press(
-            view_delegate, fl_keyboard_pending_event_get_event(pending));
-    if (should_redispatch) {
-      g_ptr_array_add(self->pending_redispatches, pending);
-      fl_keyboard_view_delegate_redispatch_event(
-          view_delegate,
-          FL_KEY_EVENT(fl_keyboard_pending_event_get_event(pending)));
-    } else {
-      g_object_unref(pending);
-    }
-  }
-}
-
-static uint16_t convert_key_to_char(FlKeyboardViewDelegate* view_delegate,
-                                    guint keycode,
-                                    gint group,
-                                    gint level) {
-  GdkKeymapKey key = {keycode, group, level};
-  constexpr int kBmpMax = 0xD7FF;
-  guint origin = fl_keyboard_view_delegate_lookup_key(view_delegate, &key);
-  return origin < kBmpMax ? origin : 0xFFFF;
-}
-
-// Make sure that Flutter has derived the layout for the group of the event,
-// if the event contains a goal keycode.
-static void guarantee_layout(FlKeyboardHandler* self, FlKeyEvent* event) {
-  g_autoptr(FlKeyboardViewDelegate) view_delegate =
-      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
-  if (view_delegate == nullptr) {
-    return;
-  }
-
-  guint8 group = fl_key_event_get_group(event);
-  if (fl_keyboard_layout_has_group(self->derived_layout, group)) {
-    return;
-  }
-  if (self->keycode_to_goals->find(fl_key_event_get_keycode(event)) ==
-      self->keycode_to_goals->end()) {
-    return;
-  }
-
-  // Clone all mandatory goals. Each goal is removed from this cloned map when
-  // fulfilled, and the remaining ones will be assigned to a default position.
-  std::map<uint64_t, const LayoutGoal*> remaining_mandatory_goals =
-      *self->logical_to_mandatory_goals;
-
-#ifdef DEBUG_PRINT_LAYOUT
-  std::string debug_layout_data;
-  for (uint16_t keycode = 0; keycode < 128; keycode += 1) {
-    std::vector<uint16_t> this_key_clues = {
-        convert_key_to_char(view_delegate, keycode, group, 0),
-        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
-    };
-    debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0],
-                             this_key_clues[1]);
-  }
-#endif
-
-  // It's important to only traverse layout goals instead of all keycodes.
-  // Some key codes outside of the standard keyboard also gives alpha-numeric
-  // letters, and will therefore take over mandatory goals from standard
-  // keyboard keys if they come first. Example: French keyboard digit 1.
-  for (const LayoutGoal& keycode_goal : layout_goals) {
-    uint16_t keycode = keycode_goal.keycode;
-    std::vector<uint16_t> this_key_clues = {
-        convert_key_to_char(view_delegate, keycode, group, 0),
-        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
-    };
-
-    // The logical key should be the first available clue from below:
-    //
-    //  - Mandatory goal, if it matches any clue. This ensures that all alnum
-    //    keys can be found somewhere.
-    //  - US layout, if neither clue of the key is EASCII. This ensures that
-    //    there are no non-latin logical keys.
-    //  - A value derived on the fly from keycode & keyval.
-    for (uint16_t clue : this_key_clues) {
-      auto matching_goal = remaining_mandatory_goals.find(clue);
-      if (matching_goal != remaining_mandatory_goals.end()) {
-        // Found a key that produces a mandatory char. Use it.
-        g_return_if_fail(fl_keyboard_layout_get_logical_key(
-                             self->derived_layout, group, keycode) == 0);
-        fl_keyboard_layout_set_logical_key(self->derived_layout, group, keycode,
-                                           clue);
-        remaining_mandatory_goals.erase(matching_goal);
-        break;
-      }
-    }
-    bool has_any_eascii =
-        is_eascii(this_key_clues[0]) || is_eascii(this_key_clues[1]);
-    // See if any produced char meets the requirement as a logical key.
-    if (fl_keyboard_layout_get_logical_key(self->derived_layout, group,
-                                           keycode) == 0 &&
-        !has_any_eascii) {
-      auto found_us_layout = self->keycode_to_goals->find(keycode);
-      if (found_us_layout != self->keycode_to_goals->end()) {
-        fl_keyboard_layout_set_logical_key(
-            self->derived_layout, group, keycode,
-            found_us_layout->second->logical_key);
-      }
-    }
-  }
-
-  // Ensure all mandatory goals are assigned.
-  for (const auto mandatory_goal_iter : remaining_mandatory_goals) {
-    const LayoutGoal* goal = mandatory_goal_iter.second;
-    fl_keyboard_layout_set_logical_key(self->derived_layout, group,
-                                       goal->keycode, goal->logical_key);
-  }
-}
-
 // Returns the keyboard pressed state.
 static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) {
   g_autoptr(FlValue) result = fl_value_new_map();
@@ -423,15 +70,7 @@
   FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(object);
 
   g_weak_ref_clear(&self->view_delegate);
-
-  self->keycode_to_goals.reset();
-  self->logical_to_mandatory_goals.reset();
-
-  g_ptr_array_free(self->responder_list, TRUE);
-  g_ptr_array_set_free_func(self->pending_responds, g_object_unref);
-  g_ptr_array_free(self->pending_responds, TRUE);
-  g_ptr_array_free(self->pending_redispatches, TRUE);
-  g_clear_object(&self->derived_layout);
+  g_clear_object(&self->channel);
 
   G_OBJECT_CLASS(fl_keyboard_handler_parent_class)->dispose(object);
 }
@@ -440,27 +79,7 @@
   G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose;
 }
 
-static void fl_keyboard_handler_init(FlKeyboardHandler* self) {
-  self->derived_layout = fl_keyboard_layout_new();
-
-  self->keycode_to_goals =
-      std::make_unique<std::map<uint16_t, const LayoutGoal*>>();
-  self->logical_to_mandatory_goals =
-      std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
-  for (const LayoutGoal& goal : layout_goals) {
-    (*self->keycode_to_goals)[goal.keycode] = &goal;
-    if (goal.mandatory) {
-      (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
-    }
-  }
-
-  self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
-
-  self->pending_responds = g_ptr_array_new();
-  self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
-
-  self->last_sequence_id = 1;
-}
+static void fl_keyboard_handler_init(FlKeyboardHandler* self) {}
 
 FlKeyboardHandler* fl_keyboard_handler_new(
     FlBinaryMessenger* messenger,
@@ -472,27 +91,6 @@
 
   g_weak_ref_init(&self->view_delegate, view_delegate);
 
-  // The embedder responder must be added before the channel responder.
-  g_ptr_array_add(
-      self->responder_list,
-      FL_KEY_RESPONDER(fl_key_embedder_responder_new(
-          [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback,
-             void* callback_user_data, void* send_key_event_user_data) {
-            FlKeyboardHandler* self =
-                FL_KEYBOARD_HANDLER(send_key_event_user_data);
-            g_autoptr(FlKeyboardViewDelegate) view_delegate =
-                FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
-            if (view_delegate == nullptr) {
-              return;
-            }
-            fl_keyboard_view_delegate_send_key_event(
-                view_delegate, event, callback, callback_user_data);
-          },
-          self)));
-  g_ptr_array_add(self->responder_list,
-                  FL_KEY_RESPONDER(fl_key_channel_responder_new(
-                      fl_keyboard_view_delegate_get_messenger(view_delegate))));
-
   // Setup the flutter/keyboard channel.
   g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
   self->channel =
@@ -501,70 +99,3 @@
                                             self, nullptr);
   return self;
 }
-
-gboolean fl_keyboard_handler_handle_event(FlKeyboardHandler* self,
-                                          FlKeyEvent* event) {
-  g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), FALSE);
-  g_return_val_if_fail(event != nullptr, FALSE);
-
-  guarantee_layout(self, event);
-
-  uint64_t incoming_hash = fl_key_event_hash(event);
-  if (fl_keyboard_handler_remove_redispatched(self, incoming_hash)) {
-    return FALSE;
-  }
-
-  FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new(
-      event, ++self->last_sequence_id, self->responder_list->len);
-
-  g_ptr_array_add(self->pending_responds, pending);
-  FlKeyboardHandlerUserData* user_data = fl_keyboard_handler_user_data_new(
-      self, fl_keyboard_pending_event_get_sequence_id(pending));
-  uint64_t specified_logical_key = fl_keyboard_layout_get_logical_key(
-      self->derived_layout, fl_key_event_get_group(event),
-      fl_key_event_get_keycode(event));
-  for (guint i = 0; i < self->responder_list->len; i++) {
-    FlKeyResponder* responder =
-        FL_KEY_RESPONDER(g_ptr_array_index(self->responder_list, i));
-    fl_key_responder_handle_event(responder, event,
-                                  responder_handle_event_callback, user_data,
-                                  specified_logical_key);
-  }
-
-  return TRUE;
-}
-
-gboolean fl_keyboard_handler_is_state_clear(FlKeyboardHandler* self) {
-  g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), FALSE);
-  return self->pending_responds->len == 0 &&
-         self->pending_redispatches->len == 0;
-}
-
-void fl_keyboard_handler_sync_modifier_if_needed(FlKeyboardHandler* self,
-                                                 guint state,
-                                                 double event_time) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER(self));
-
-  // The embedder responder is the first element in
-  // FlKeyboardHandler.responder_list.
-  FlKeyEmbedderResponder* responder =
-      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
-  fl_key_embedder_responder_sync_modifiers_if_needed(responder, state,
-                                                     event_time);
-}
-
-GHashTable* fl_keyboard_handler_get_pressed_state(FlKeyboardHandler* self) {
-  g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), nullptr);
-
-  // The embedder responder is the first element in
-  // FlKeyboardHandler.responder_list.
-  FlKeyEmbedderResponder* responder =
-      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
-  return fl_key_embedder_responder_get_pressed_state(responder);
-}
-
-void fl_keyboard_handler_notify_layout_changed(FlKeyboardHandler* self) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER(self));
-  g_clear_object(&self->derived_layout);
-  self->derived_layout = fl_keyboard_layout_new();
-}
diff --git a/shell/platform/linux/fl_keyboard_handler.h b/shell/platform/linux/fl_keyboard_handler.h
index 0d551d6..7237755 100644
--- a/shell/platform/linux/fl_keyboard_handler.h
+++ b/shell/platform/linux/fl_keyboard_handler.h
@@ -21,17 +21,7 @@
 /**
  * FlKeyboardHandler:
  *
- * Processes keyboard events and cooperate with `TextInputHandler`.
- *
- * A keyboard event goes through a few sections, each can choose to handle the
- * event, and only unhandled events can move to the next section:
- *
- * - Keyboard: Dispatch to the embedder responder and the channel responder
- *   simultaneously. After both responders have responded (asynchronously), the
- *   event is considered handled if either responder handles it.
- * - Text input: Events are sent to IM filter (usually owned by
- *   `TextInputHandler`) and are handled synchronously.
- * - Redispatching: Events are inserted back to the system for redispatching.
+ * Provides the channel to receive keyboard requests from the Dart code.
  */
 
 /**
@@ -47,60 +37,6 @@
     FlBinaryMessenger* messenger,
     FlKeyboardViewDelegate* view_delegate);
 
-/**
- * fl_keyboard_handler_handle_event:
- * @handler: the #FlKeyboardHandler self.
- * @event: the event to be dispatched. It is usually a wrap of a GdkEventKey.
- * This event will be managed and released by #FlKeyboardHandler.
- *
- * Make the handler process a system key event. This might eventually send
- * messages to the framework, trigger text input effects, or redispatch the
- * event back to the system.
- */
-gboolean fl_keyboard_handler_handle_event(FlKeyboardHandler* handler,
-                                          FlKeyEvent* event);
-
-/**
- * fl_keyboard_handler_is_state_clear:
- * @handler: the #FlKeyboardHandler self.
- *
- * A debug-only method that queries whether the handler's various states are
- * cleared, i.e. no pending events for redispatching or for responding.
- *
- * Returns: true if the handler's various states are cleared.
- */
-gboolean fl_keyboard_handler_is_state_clear(FlKeyboardHandler* handler);
-
-/**
- * fl_keyboard_handler_sync_modifier_if_needed:
- * @handler: the #FlKeyboardHandler self.
- * @state: the state of the modifiers mask.
- * @event_time: the time attribute of the incoming GDK event.
- *
- * If needed, synthesize modifier keys up and down event by comparing their
- * current pressing states with the given modifiers mask.
- */
-void fl_keyboard_handler_sync_modifier_if_needed(FlKeyboardHandler* handler,
-                                                 guint state,
-                                                 double event_time);
-
-/**
- * fl_keyboard_handler_get_pressed_state:
- * @handler: the #FlKeyboardHandler self.
- *
- * Returns the keyboard pressed state. The hash table contains one entry per
- * pressed keys, mapping from the logical key to the physical key.*
- */
-GHashTable* fl_keyboard_handler_get_pressed_state(FlKeyboardHandler* handler);
-
-/**
- * fl_keyboard_handler_notify_layout_changed:
- * @handler: the #FlKeyboardHandler self.
- *
- * Notify the handler the keyboard layout has changed.
- */
-void fl_keyboard_handler_notify_layout_changed(FlKeyboardHandler* handler);
-
 G_END_DECLS
 
 #endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_HANDLER_H_
diff --git a/shell/platform/linux/fl_keyboard_handler_test.cc b/shell/platform/linux/fl_keyboard_handler_test.cc
index f0e9815..a4d9278 100644
--- a/shell/platform/linux/fl_keyboard_handler_test.cc
+++ b/shell/platform/linux/fl_keyboard_handler_test.cc
@@ -4,143 +4,26 @@
 
 #include "flutter/shell/platform/linux/fl_keyboard_handler.h"
 
-#include <cstring>
-#include <vector>
-
-#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
-#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
 #include "flutter/shell/platform/linux/fl_method_codec_private.h"
-#include "flutter/shell/platform/linux/key_mapping.h"
-#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
-#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
-#include "flutter/shell/platform/linux/testing/fl_test.h"
 #include "flutter/shell/platform/linux/testing/mock_binary_messenger.h"
-#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h"
-#include "flutter/testing/testing.h"
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
-// Define compound `expect` in macros. If they were defined in functions, the
-// stacktrace wouldn't print where the function is called in the unit tests.
-
-#define EXPECT_KEY_EVENT(RECORD, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \
-  EXPECT_EQ((RECORD).type, CallRecord::kKeyCallEmbedder);                    \
-  EXPECT_EQ((RECORD).event->type, (TYPE));                                   \
-  EXPECT_EQ((RECORD).event->physical, (PHYSICAL));                           \
-  EXPECT_EQ((RECORD).event->logical, (LOGICAL));                             \
-  EXPECT_STREQ((RECORD).event->character, (CHAR));                           \
-  EXPECT_EQ((RECORD).event->synthesized, (SYNTHESIZED));
-
-#define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR)                          \
-  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);    \
-  EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \
-  EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL));         \
-  EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR));       \
-  EXPECT_EQ(call_records[0].event->synthesized, false);             \
-  call_records.clear()
-
-namespace {
-using ::flutter::testing::keycodes::kLogicalAltLeft;
-using ::flutter::testing::keycodes::kLogicalBracketLeft;
-using ::flutter::testing::keycodes::kLogicalComma;
-using ::flutter::testing::keycodes::kLogicalControlLeft;
-using ::flutter::testing::keycodes::kLogicalDigit1;
-using ::flutter::testing::keycodes::kLogicalKeyA;
-using ::flutter::testing::keycodes::kLogicalKeyB;
-using ::flutter::testing::keycodes::kLogicalKeyM;
-using ::flutter::testing::keycodes::kLogicalKeyQ;
-using ::flutter::testing::keycodes::kLogicalMetaLeft;
-using ::flutter::testing::keycodes::kLogicalMinus;
-using ::flutter::testing::keycodes::kLogicalParenthesisRight;
-using ::flutter::testing::keycodes::kLogicalSemicolon;
-using ::flutter::testing::keycodes::kLogicalShiftLeft;
-using ::flutter::testing::keycodes::kLogicalUnderscore;
-
-using ::flutter::testing::keycodes::kPhysicalAltLeft;
-using ::flutter::testing::keycodes::kPhysicalControlLeft;
-using ::flutter::testing::keycodes::kPhysicalKeyA;
-using ::flutter::testing::keycodes::kPhysicalKeyB;
-using ::flutter::testing::keycodes::kPhysicalMetaLeft;
-using ::flutter::testing::keycodes::kPhysicalShiftLeft;
-
-// Hardware key codes.
-typedef std::function<void(bool handled)> AsyncKeyCallback;
-typedef std::function<void(AsyncKeyCallback callback)> ChannelCallHandler;
-typedef std::function<void(const FlutterKeyEvent* event,
-                           AsyncKeyCallback callback)>
-    EmbedderCallHandler;
-typedef std::function<void(FlKeyEvent*)> RedispatchHandler;
-
-// A type that can record all kinds of effects that the keyboard handler
-// triggers.
-//
-// An instance of `CallRecord` might not have all the fields filled.
-typedef struct {
-  enum {
-    kKeyCallEmbedder,
-    kKeyCallChannel,
-  } type;
-
-  AsyncKeyCallback callback;
-  std::unique_ptr<FlutterKeyEvent> event;
-  std::unique_ptr<char[]> event_character;
-} CallRecord;
-
-// Clone a C-string.
-//
-// Must be deleted by delete[].
-char* cloneString(const char* source) {
-  if (source == nullptr) {
-    return nullptr;
-  }
-  size_t charLen = strlen(source);
-  char* target = new char[charLen + 1];
-  strncpy(target, source, charLen + 1);
-  return target;
-}
-
-constexpr guint16 kKeyCodeKeyA = 0x26u;
-constexpr guint16 kKeyCodeKeyB = 0x38u;
-constexpr guint16 kKeyCodeKeyM = 0x3au;
-constexpr guint16 kKeyCodeDigit1 = 0x0au;
-constexpr guint16 kKeyCodeMinus = 0x14u;
-constexpr guint16 kKeyCodeSemicolon = 0x2fu;
-constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u;
-
-static constexpr char kKeyEventChannelName[] = "flutter/keyevent";
 static constexpr char kKeyboardChannelName[] = "flutter/keyboard";
 static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
 static constexpr uint64_t kMockPhysicalKey = 42;
 static constexpr uint64_t kMockLogicalKey = 42;
 
-// All key clues for a keyboard layout.
-//
-// The index is (keyCode * 2 + hasShift), where each value is the character for
-// this key (GTK only supports UTF-16.) Since the maximum keycode of interest
-// is 128, it has a total of 256 entries..
-typedef std::array<uint32_t, 256> MockGroupLayoutData;
-typedef std::vector<const MockGroupLayoutData*> MockLayoutData;
-
-extern const MockLayoutData kLayoutUs;
-extern const MockLayoutData kLayoutRussian;
-extern const MockLayoutData kLayoutFrench;
-
 G_BEGIN_DECLS
 
-G_DECLARE_FINAL_TYPE(FlMockViewDelegate,
-                     fl_mock_view_delegate,
+G_DECLARE_FINAL_TYPE(FlMockKeyboardHandlerDelegate,
+                     fl_mock_keyboard_handler_delegate,
                      FL,
-                     MOCK_VIEW_DELEGATE,
+                     MOCK_KEYBOARD_HANDLER_DELEGATE,
                      GObject);
 
-G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger,
-                     fl_mock_key_binary_messenger,
-                     FL,
-                     MOCK_KEY_BINARY_MESSENGER,
-                     GObject)
-
 G_END_DECLS
 
 MATCHER_P(MethodSuccessResponse, result, "") {
@@ -156,243 +39,26 @@
   return false;
 }
 
-/***** FlMockKeyBinaryMessenger *****/
-/* Mock a binary messenger that only processes messages from the embedding on
- * the key event channel, and does so according to the callback set by
- * fl_mock_key_binary_messenger_set_callback_handler */
-
-struct _FlMockKeyBinaryMessenger {
+struct _FlMockKeyboardHandlerDelegate {
   GObject parent_instance;
 };
 
-struct FlMockKeyBinaryMessengerPrivate {
-  ChannelCallHandler callback_handler;
-};
-
-static void fl_mock_key_binary_messenger_iface_init(
-    FlBinaryMessengerInterface* iface);
-
-G_DEFINE_TYPE_WITH_CODE(
-    FlMockKeyBinaryMessenger,
-    fl_mock_key_binary_messenger,
-    G_TYPE_OBJECT,
-    G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(),
-                          fl_mock_key_binary_messenger_iface_init);
-    G_ADD_PRIVATE(FlMockKeyBinaryMessenger))
-
-#define FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(obj)    \
-  static_cast<FlMockKeyBinaryMessengerPrivate*>(         \
-      fl_mock_key_binary_messenger_get_instance_private( \
-          FL_MOCK_KEY_BINARY_MESSENGER(obj)))
-
-static void fl_mock_key_binary_messenger_init(FlMockKeyBinaryMessenger* self) {
-  FlMockKeyBinaryMessengerPrivate* priv =
-      FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self);
-  new (priv) FlMockKeyBinaryMessengerPrivate();
-}
-
-static void fl_mock_key_binary_messenger_dispose(GObject* object) {
-  FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(object)
-      ->~FlMockKeyBinaryMessengerPrivate();
-
-  G_OBJECT_CLASS(fl_mock_key_binary_messenger_parent_class)->dispose(object);
-}
-
-static void fl_mock_key_binary_messenger_class_init(
-    FlMockKeyBinaryMessengerClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_mock_key_binary_messenger_dispose;
-}
-
-static void fl_mock_key_binary_messenger_send_on_channel(
-    FlBinaryMessenger* messenger,
-    const gchar* channel,
-    GBytes* message,
-    GCancellable* cancellable,
-    GAsyncReadyCallback callback,
-    gpointer user_data) {
-  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(messenger);
-
-  if (callback != nullptr) {
-    EXPECT_STREQ(channel, kKeyEventChannelName);
-    FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self)->callback_handler(
-        [self, cancellable, callback, user_data](bool handled) {
-          g_autoptr(GTask) task =
-              g_task_new(self, cancellable, callback, user_data);
-          g_autoptr(FlValue) result = fl_value_new_map();
-          fl_value_set_string_take(result, "handled",
-                                   fl_value_new_bool(handled));
-          g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
-          g_autoptr(GError) error = nullptr;
-          GBytes* data = fl_message_codec_encode_message(
-              FL_MESSAGE_CODEC(codec), result, &error);
-
-          g_task_return_pointer(
-              task, data, reinterpret_cast<GDestroyNotify>(g_bytes_unref));
-        });
-  }
-}
-
-static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish(
-    FlBinaryMessenger* messenger,
-    GAsyncResult* result,
-    GError** error) {
-  return static_cast<GBytes*>(g_task_propagate_pointer(G_TASK(result), error));
-}
-
-static void fl_mock_binary_messenger_resize_channel(
-    FlBinaryMessenger* messenger,
-    const gchar* channel,
-    int64_t new_size) {
-  // Mock implementation. Do nothing.
-}
-
-static void fl_mock_binary_messenger_set_warns_on_channel_overflow(
-    FlBinaryMessenger* messenger,
-    const gchar* channel,
-    bool warns) {
-  // Mock implementation. Do nothing.
-}
-
-static void fl_mock_key_binary_messenger_iface_init(
-    FlBinaryMessengerInterface* iface) {
-  iface->set_message_handler_on_channel =
-      [](FlBinaryMessenger* messenger, const gchar* channel,
-         FlBinaryMessengerMessageHandler handler, gpointer user_data,
-         GDestroyNotify destroy_notify) {
-        EXPECT_STREQ(channel, kKeyEventChannelName);
-        // No need to mock. The key event channel expects no incoming messages
-        // from the framework.
-      };
-  iface->send_response = [](FlBinaryMessenger* messenger,
-                            FlBinaryMessengerResponseHandle* response_handle,
-                            GBytes* response, GError** error) -> gboolean {
-    // The key event channel expects no incoming messages from the framework,
-    // hence no responses either.
-    g_return_val_if_reached(TRUE);
-    return TRUE;
-  };
-  iface->send_on_channel = fl_mock_key_binary_messenger_send_on_channel;
-  iface->send_on_channel_finish =
-      fl_mock_key_binary_messenger_send_on_channel_finish;
-  iface->resize_channel = fl_mock_binary_messenger_resize_channel;
-  iface->set_warns_on_channel_overflow =
-      fl_mock_binary_messenger_set_warns_on_channel_overflow;
-}
-
-static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() {
-  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(
-      g_object_new(fl_mock_key_binary_messenger_get_type(), NULL));
-
-  // Added to stop compiler complaining about an unused function.
-  FL_IS_MOCK_KEY_BINARY_MESSENGER(self);
-
-  return self;
-}
-
-static void fl_mock_key_binary_messenger_set_callback_handler(
-    FlMockKeyBinaryMessenger* self,
-    ChannelCallHandler handler) {
-  FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self)->callback_handler =
-      std::move(handler);
-}
-
-/***** FlMockViewDelegate *****/
-
-struct _FlMockViewDelegate {
-  GObject parent_instance;
-};
-
-struct FlMockViewDelegatePrivate {
-  FlMockKeyBinaryMessenger* messenger;
-  EmbedderCallHandler embedder_handler;
-  bool text_filter_result;
-  RedispatchHandler redispatch_handler;
-  const MockLayoutData* layout_data;
-};
-
-static void fl_mock_view_keyboard_delegate_iface_init(
+static void fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init(
     FlKeyboardViewDelegateInterface* iface);
 
 G_DEFINE_TYPE_WITH_CODE(
-    FlMockViewDelegate,
-    fl_mock_view_delegate,
+    FlMockKeyboardHandlerDelegate,
+    fl_mock_keyboard_handler_delegate,
     G_TYPE_OBJECT,
-    G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
-                          fl_mock_view_keyboard_delegate_iface_init);
-    G_ADD_PRIVATE(FlMockViewDelegate))
+    G_IMPLEMENT_INTERFACE(
+        fl_keyboard_view_delegate_get_type(),
+        fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init))
 
-#define FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(obj) \
-  static_cast<FlMockViewDelegatePrivate*>(     \
-      fl_mock_view_delegate_get_instance_private(FL_MOCK_VIEW_DELEGATE(obj)))
+static void fl_mock_keyboard_handler_delegate_init(
+    FlMockKeyboardHandlerDelegate* self) {}
 
-static void fl_mock_view_delegate_init(FlMockViewDelegate* self) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  new (priv) FlMockViewDelegatePrivate();
-}
-
-static void fl_mock_view_delegate_dispose(GObject* object) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(object);
-
-  FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(object)->~FlMockViewDelegatePrivate();
-  g_clear_object(&priv->messenger);
-
-  G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object);
-}
-
-static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose;
-}
-
-static void fl_mock_view_keyboard_send_key_event(
-    FlKeyboardViewDelegate* view_delegate,
-    const FlutterKeyEvent* event,
-    FlutterKeyEventCallback callback,
-    void* user_data) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  priv->embedder_handler(event, [callback, user_data](bool handled) {
-    if (callback != nullptr) {
-      callback(handled, user_data);
-    }
-  });
-}
-
-static gboolean fl_mock_view_keyboard_text_filter_key_press(
-    FlKeyboardViewDelegate* view_delegate,
-    FlKeyEvent* event) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  return priv->text_filter_result;
-}
-
-static FlBinaryMessenger* fl_mock_view_keyboard_get_messenger(
-    FlKeyboardViewDelegate* view_delegate) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  return FL_BINARY_MESSENGER(priv->messenger);
-}
-
-static void fl_mock_view_keyboard_redispatch_event(
-    FlKeyboardViewDelegate* view_delegate,
-    FlKeyEvent* event) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  if (priv->redispatch_handler) {
-    priv->redispatch_handler(event);
-  }
-}
-
-static guint fl_mock_view_keyboard_lookup_key(FlKeyboardViewDelegate* delegate,
-                                              const GdkKeymapKey* key) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(delegate);
-  guint8 group = static_cast<guint8>(key->group);
-  EXPECT_LT(group, priv->layout_data->size());
-  const MockGroupLayoutData* group_layout = (*priv->layout_data)[group];
-  EXPECT_TRUE(group_layout != nullptr);
-  EXPECT_TRUE(key->level == 0 || key->level == 1);
-  bool shift = key->level == 1;
-  return (*group_layout)[key->keycode * 2 + shift];
-}
+static void fl_mock_keyboard_handler_delegate_class_init(
+    FlMockKeyboardHandlerDelegateClass* klass) {}
 
 static GHashTable* fl_mock_view_keyboard_get_keyboard_state(
     FlKeyboardViewDelegate* view_delegate) {
@@ -403,620 +69,27 @@
   return result;
 }
 
-static void fl_mock_view_keyboard_delegate_iface_init(
+static void fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init(
     FlKeyboardViewDelegateInterface* iface) {
-  iface->send_key_event = fl_mock_view_keyboard_send_key_event;
-  iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press;
-  iface->get_messenger = fl_mock_view_keyboard_get_messenger;
-  iface->redispatch_event = fl_mock_view_keyboard_redispatch_event;
-  iface->lookup_key = fl_mock_view_keyboard_lookup_key;
   iface->get_keyboard_state = fl_mock_view_keyboard_get_keyboard_state;
 }
 
-static FlMockViewDelegate* fl_mock_view_delegate_new() {
-  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(
-      g_object_new(fl_mock_view_delegate_get_type(), nullptr));
+static FlMockKeyboardHandlerDelegate* fl_mock_keyboard_handler_delegate_new() {
+  FlMockKeyboardHandlerDelegate* self = FL_MOCK_KEYBOARD_HANDLER_DELEGATE(
+      g_object_new(fl_mock_keyboard_handler_delegate_get_type(), nullptr));
 
   // Added to stop compiler complaining about an unused function.
-  FL_IS_MOCK_VIEW_DELEGATE(self);
-
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->messenger = fl_mock_key_binary_messenger_new();
+  FL_IS_MOCK_KEYBOARD_HANDLER_DELEGATE(self);
 
   return self;
 }
 
-static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self,
-                                              EmbedderCallHandler handler) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->embedder_handler = std::move(handler);
-}
-
-static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self,
-                                                bool result) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->text_filter_result = result;
-}
-
-static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self,
-                                                RedispatchHandler handler) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->redispatch_handler = std::move(handler);
-}
-
-static void fl_mock_view_set_layout(FlMockViewDelegate* self,
-                                    const MockLayoutData* layout) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->layout_data = layout;
-}
-
-/***** End FlMockViewDelegate *****/
-
-class KeyboardTester {
- public:
-  KeyboardTester() {
-    ::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
-
-    view_ = fl_mock_view_delegate_new();
-    respondToEmbedderCallsWith(false);
-    respondToChannelCallsWith(false);
-    respondToTextInputWith(false);
-    setLayout(kLayoutUs);
-
-    handler_ =
-        fl_keyboard_handler_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(view_));
-  }
-
-  ~KeyboardTester() {
-    g_clear_object(&view_);
-    g_clear_object(&handler_);
-    g_clear_pointer(&redispatched_events_, g_ptr_array_unref);
-  }
-
-  FlKeyboardHandler* handler() { return handler_; }
-
-  // Block until all GdkMainLoop messages are processed, which is basically
-  // used only for channel messages.
-  void flushChannelMessages() {
-    GMainLoop* loop = g_main_loop_new(nullptr, 0);
-    g_idle_add(_flushChannelMessagesCb, loop);
-    g_main_loop_run(loop);
-  }
-
-  // Dispatch each of the given events, expect their results to be false
-  // (unhandled), and clear the event array.
-  //
-  // Returns the number of events redispatched. If any result is unexpected
-  // (handled), return a minus number `-x` instead, where `x` is the index of
-  // the first unexpected redispatch.
-  int redispatchEventsAndClear(GPtrArray* events) {
-    guint event_count = events->len;
-    int first_error = -1;
-    during_redispatch_ = true;
-    for (guint event_id = 0; event_id < event_count; event_id += 1) {
-      FlKeyEvent* event = FL_KEY_EVENT(g_ptr_array_index(events, event_id));
-      bool handled = fl_keyboard_handler_handle_event(handler_, event);
-      EXPECT_FALSE(handled);
-      if (handled) {
-        first_error = first_error == -1 ? event_id : first_error;
-      }
-    }
-    during_redispatch_ = false;
-    g_ptr_array_set_size(events, 0);
-    return first_error < 0 ? event_count : -first_error;
-  }
-
-  void respondToEmbedderCallsWith(bool response) {
-    fl_mock_view_set_embedder_handler(
-        view_, [response, this](const FlutterKeyEvent* event,
-                                const AsyncKeyCallback& callback) {
-          EXPECT_FALSE(during_redispatch_);
-          callback(response);
-        });
-  }
-
-  void recordEmbedderCallsTo(std::vector<CallRecord>& storage) {
-    fl_mock_view_set_embedder_handler(
-        view_, [&storage, this](const FlutterKeyEvent* event,
-                                AsyncKeyCallback callback) {
-          EXPECT_FALSE(during_redispatch_);
-          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
-          char* new_event_character = cloneString(event->character);
-          new_event->character = new_event_character;
-          storage.push_back(CallRecord{
-              .type = CallRecord::kKeyCallEmbedder,
-              .callback = std::move(callback),
-              .event = std::move(new_event),
-              .event_character = std::unique_ptr<char[]>(new_event_character),
-          });
-        });
-  }
-
-  void respondToEmbedderCallsWithAndRecordsTo(
-      bool response,
-      std::vector<CallRecord>& storage) {
-    fl_mock_view_set_embedder_handler(
-        view_, [&storage, response, this](const FlutterKeyEvent* event,
-                                          const AsyncKeyCallback& callback) {
-          EXPECT_FALSE(during_redispatch_);
-          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
-          char* new_event_character = cloneString(event->character);
-          new_event->character = new_event_character;
-          storage.push_back(CallRecord{
-              .type = CallRecord::kKeyCallEmbedder,
-              .event = std::move(new_event),
-              .event_character = std::unique_ptr<char[]>(new_event_character),
-          });
-          callback(response);
-        });
-  }
-
-  void respondToChannelCallsWith(bool response) {
-    FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_);
-
-    fl_mock_key_binary_messenger_set_callback_handler(
-        priv->messenger, [response, this](const AsyncKeyCallback& callback) {
-          EXPECT_FALSE(during_redispatch_);
-          callback(response);
-        });
-  }
-
-  void recordChannelCallsTo(std::vector<CallRecord>& storage) {
-    FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_);
-
-    fl_mock_key_binary_messenger_set_callback_handler(
-        priv->messenger, [&storage, this](AsyncKeyCallback callback) {
-          EXPECT_FALSE(during_redispatch_);
-          storage.push_back(CallRecord{
-              .type = CallRecord::kKeyCallChannel,
-              .callback = std::move(callback),
-          });
-        });
-  }
-
-  void respondToTextInputWith(bool response) {
-    fl_mock_view_set_text_filter_result(view_, response);
-  }
-
-  void recordRedispatchedEventsTo(GPtrArray* storage) {
-    redispatched_events_ = g_ptr_array_ref(storage);
-    fl_mock_view_set_redispatch_handler(view_, [this](FlKeyEvent* key) {
-      g_ptr_array_add(redispatched_events_, g_object_ref(key));
-    });
-  }
-
-  void setLayout(const MockLayoutData& layout) {
-    fl_mock_view_set_layout(view_, &layout);
-    if (handler_ != nullptr) {
-      fl_keyboard_handler_notify_layout_changed(handler_);
-    }
-  }
-
- private:
-  FlMockViewDelegate* view_;
-  FlKeyboardHandler* handler_ = nullptr;
-  GPtrArray* redispatched_events_ = nullptr;
-  bool during_redispatch_ = false;
-
-  static gboolean _flushChannelMessagesCb(gpointer data) {
-    g_autoptr(GMainLoop) loop = reinterpret_cast<GMainLoop*>(data);
-    g_main_loop_quit(loop);
-    return FALSE;
-  }
-};
-
-// Make sure that the keyboard can be disposed without crashes when there are
-// unresolved pending events.
-TEST(FlKeyboardHandlerTest, DisposeWithUnresolvedPends) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-
-  // Record calls so that they aren't responded.
-  tester.recordEmbedderCallsTo(call_records);
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  fl_keyboard_handler_handle_event(tester.handler(), event1);
-
-  tester.respondToEmbedderCallsWith(true);
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  fl_keyboard_handler_handle_event(tester.handler(), event2);
-
-  tester.flushChannelMessages();
-
-  // Passes if the cleanup does not crash.
-}
-
-TEST(FlKeyboardHandlerTest, SingleDelegateWithAsyncResponds) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-
-  gboolean handler_handled = false;
-
-  /// Test 1: One event that is handled by the framework
-  tester.recordEmbedderCallsTo(call_records);
-  tester.recordRedispatchedEventsTo(redispatched);
-
-  // Dispatch a key event
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
-                   kLogicalKeyA, "a", false);
-
-  call_records[0].callback(true);
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  call_records.clear();
-
-  /// Test 2: Two events that are unhandled by the framework
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
-                   kLogicalKeyA, nullptr, false);
-
-  // Dispatch another key event
-  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyB, GDK_KEY_b, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event3);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 2u);
-  EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB,
-                   kLogicalKeyB, "b", false);
-
-  // Resolve the second event first to test out-of-order response
-  call_records[1].callback(false);
-  EXPECT_EQ(redispatched->len, 1u);
-  EXPECT_EQ(
-      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 0))),
-      0x62u);
-  call_records[0].callback(false);
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 2u);
-  EXPECT_EQ(
-      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 1))),
-      0x61u);
-
-  EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  call_records.clear();
-
-  // Resolve redispatches
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 2);
-  tester.flushChannelMessages();
-  EXPECT_EQ(call_records.size(), 0u);
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-
-  /// Test 3: Dispatch the same event again to ensure that prevention from
-  /// redispatching only works once.
-  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event4);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 1u);
-
-  call_records[0].callback(true);
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, SingleDelegateWithSyncResponds) {
-  KeyboardTester tester;
-  gboolean handler_handled = false;
-  std::vector<CallRecord> call_records;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-
-  /// Test 1: One event that is handled by the framework
-  tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records);
-  tester.recordRedispatchedEventsTo(redispatched);
-
-  // Dispatch a key event
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
-                   kLogicalKeyA, "a", false);
-  EXPECT_EQ(redispatched->len, 0u);
-  call_records.clear();
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  g_ptr_array_set_size(redispatched, 0);
-
-  /// Test 2: An event unhandled by the framework
-  tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records);
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
-                   kLogicalKeyA, nullptr, false);
-  EXPECT_EQ(redispatched->len, 1u);
-  call_records.clear();
-
-  EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler()));
-
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
-  EXPECT_EQ(call_records.size(), 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, WithTwoAsyncDelegates) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-
-  gboolean handler_handled = false;
-
-  tester.recordEmbedderCallsTo(call_records);
-  tester.recordChannelCallsTo(call_records);
-  tester.recordRedispatchedEventsTo(redispatched);
-
-  /// Test 1: One delegate responds true, the other false
-
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
-
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 2u);
-
-  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
-  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
-
-  call_records[0].callback(true);
-  call_records[1].callback(false);
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  call_records.clear();
-
-  /// Test 2: All delegates respond false
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
-
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 2u);
-
-  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
-  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
-
-  call_records[0].callback(false);
-  call_records[1].callback(false);
-
-  call_records.clear();
-
-  // Resolve redispatch
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 1u);
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
-  EXPECT_EQ(call_records.size(), 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsFalse) {
-  KeyboardTester tester;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-  gboolean handler_handled = false;
-  tester.recordRedispatchedEventsTo(redispatched);
-  tester.respondToTextInputWith(false);
-
-  // Dispatch a key event.
-  g_autoptr(FlKeyEvent) event = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  // The event was redispatched because no one handles it.
-  EXPECT_EQ(redispatched->len, 1u);
-
-  // Resolve redispatched event.
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsTrue) {
-  KeyboardTester tester;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-  gboolean handler_handled = false;
-  tester.recordRedispatchedEventsTo(redispatched);
-  tester.respondToTextInputWith(true);
-
-  // Dispatch a key event.
-  g_autoptr(FlKeyEvent) event = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  // The event was not redispatched because handler handles it.
-  EXPECT_EQ(redispatched->len, 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, CorrectLogicalKeyForLayouts) {
-  KeyboardTester tester;
-
-  std::vector<CallRecord> call_records;
-  tester.recordEmbedderCallsTo(call_records);
-
-  auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) {
-    g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-        0, TRUE, keycode, keyval, static_cast<GdkModifierType>(0), group);
-    fl_keyboard_handler_handle_event(tester.handler(), event1);
-    g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-        0, FALSE, keycode, keyval, static_cast<GdkModifierType>(0), group);
-    fl_keyboard_handler_handle_event(tester.handler(), event2);
-  };
-
-  /* US keyboard layout */
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "a");
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_A, 0);  // Shift-KeyA
-  VERIFY_DOWN(kLogicalKeyA, "A");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0);  // Shift-Digit1
-  VERIFY_DOWN(kLogicalDigit1, "!");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_minus, 0);  // Minus
-  VERIFY_DOWN(kLogicalMinus, "-");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0);  // Shift-Minus
-  VERIFY_DOWN(kLogicalUnderscore, "_");
-
-  /* French keyboard layout, group 3, which is when the input method is showing
-   * "Fr" */
-
-  tester.setLayout(kLayoutFrench);
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_q, 3);  // KeyA
-  VERIFY_DOWN(kLogicalKeyQ, "q");
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3);  // Shift-KeyA
-  VERIFY_DOWN(kLogicalKeyQ, "Q");
-
-  sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3);  // ; but prints M
-  VERIFY_DOWN(kLogicalKeyM, "m");
-
-  sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3);  // M but prints ,
-  VERIFY_DOWN(kLogicalComma, ",");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3);  // Digit1
-  VERIFY_DOWN(kLogicalDigit1, "&");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 3);  // Shift-Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3);  // Minus
-  VERIFY_DOWN(kLogicalParenthesisRight, ")");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_degree, 3);  // Shift-Minus
-  VERIFY_DOWN(static_cast<uint32_t>(L'°'), "°");
-
-  /* French keyboard layout, group 0, which is pressing the "extra key for
-   * triggering input method" key once after switching to French IME. */
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "a");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  /* Russian keyboard layout, group 2 */
-  tester.setLayout(kLayoutRussian);
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "ф");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 2);  // Shift-Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2);
-  VERIFY_DOWN(kLogicalBracketLeft, "х");
-
-  /* Russian keyboard layout, group 0 */
-  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "a");
-
-  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0);
-  VERIFY_DOWN(kLogicalBracketLeft, "[");
-}
-
-TEST(FlKeyboardHandlerTest, SynthesizeModifiersIfNeeded) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-  tester.recordEmbedderCallsTo(call_records);
-
-  auto verifyModifierIsSynthesized = [&](GdkModifierType mask,
-                                         uint64_t physical, uint64_t logical) {
-    // Modifier is pressed.
-    guint state = mask;
-    fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1000);
-    EXPECT_EQ(call_records.size(), 1u);
-    EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, physical,
-                     logical, NULL, true);
-    // Modifier is released.
-    state = state ^ mask;
-    fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1001);
-    EXPECT_EQ(call_records.size(), 2u);
-    EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeUp, physical, logical,
-                     NULL, true);
-    call_records.clear();
-  };
-
-  // No modifiers pressed.
-  guint state = 0;
-  fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1000);
-  EXPECT_EQ(call_records.size(), 0u);
-  call_records.clear();
-
-  // Press and release each modifier once.
-  verifyModifierIsSynthesized(GDK_CONTROL_MASK, kPhysicalControlLeft,
-                              kLogicalControlLeft);
-  verifyModifierIsSynthesized(GDK_META_MASK, kPhysicalMetaLeft,
-                              kLogicalMetaLeft);
-  verifyModifierIsSynthesized(GDK_MOD1_MASK, kPhysicalAltLeft, kLogicalAltLeft);
-  verifyModifierIsSynthesized(GDK_SHIFT_MASK, kPhysicalShiftLeft,
-                              kLogicalShiftLeft);
-}
-
-TEST(FlKeyboardHandlerTest, GetPressedState) {
-  KeyboardTester tester;
-  tester.respondToTextInputWith(true);
-
-  // Dispatch a key event.
-  g_autoptr(FlKeyEvent) event = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  fl_keyboard_handler_handle_event(tester.handler(), event);
-
-  GHashTable* pressedState =
-      fl_keyboard_handler_get_pressed_state(tester.handler());
-  EXPECT_EQ(g_hash_table_size(pressedState), 1u);
-
-  gpointer physical_key =
-      g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA));
-  EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA);
-}
-
 TEST(FlKeyboardHandlerTest, KeyboardChannelGetPressedState) {
   ::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
 
   g_autoptr(FlKeyboardHandler) handler = fl_keyboard_handler_new(
-      messenger, FL_KEYBOARD_VIEW_DELEGATE(fl_mock_view_delegate_new()));
+      messenger,
+      FL_KEYBOARD_VIEW_DELEGATE(fl_mock_keyboard_handler_delegate_new()));
   EXPECT_NE(handler, nullptr);
 
   g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
@@ -1034,193 +107,3 @@
 
   messenger.ReceiveMessage(kKeyboardChannelName, message);
 }
-
-// The following layout data is generated using DEBUG_PRINT_LAYOUT.
-
-const MockGroupLayoutData kLayoutUs0{{
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0xffff, 0x0031, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
-    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
-    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
-    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
-    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
-    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
-    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
-    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x003c, 0x003e, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
-}};
-
-const MockGroupLayoutData kLayoutRussian0{
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
-    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
-    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
-    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
-    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
-    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
-    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
-    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
-};
-
-const MockGroupLayoutData kLayoutRussian2{{
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0xffff, 0x0031, 0x0021, 0x0000, 0x0031, 0x0021, 0x0032, 0x0022,  // 0x08
-    0x0033, 0x06b0, 0x0034, 0x003b, 0x0035, 0x0025, 0x0036, 0x003a,  // 0x0c
-    0x0037, 0x003f, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0x0071, 0x0051, 0x0000, 0x0000,  // 0x14
-    0x06ca, 0x06ea, 0x06c3, 0x06e3, 0x06d5, 0x06f5, 0x06cb, 0x06eb,  // 0x18
-    0x06c5, 0x06e5, 0x06ce, 0x06ee, 0x06c7, 0x06e7, 0x06db, 0x06fb,  // 0x1c
-    0x06dd, 0x06fd, 0x06da, 0x06fa, 0x06c8, 0x06e8, 0x06df, 0x06ff,  // 0x20
-    0x0061, 0x0041, 0x0041, 0x0000, 0x06c6, 0x06e6, 0x06d9, 0x06f9,  // 0x24
-    0x06d7, 0x06f7, 0x06c1, 0x06e1, 0x06d0, 0x06f0, 0x06d2, 0x06f2,  // 0x28
-    0x06cf, 0x06ef, 0x06cc, 0x06ec, 0x06c4, 0x06e4, 0x06d6, 0x06f6,  // 0x2c
-    0x06dc, 0x06fc, 0x06a3, 0x06b3, 0x007c, 0x0000, 0x005c, 0x002f,  // 0x30
-    0x06d1, 0x06f1, 0x06de, 0x06fe, 0x06d3, 0x06f3, 0x06cd, 0x06ed,  // 0x34
-    0x06c9, 0x06e9, 0x06d4, 0x06f4, 0x06d8, 0x06f8, 0x06c2, 0x06e2,  // 0x38
-    0x06c0, 0x06e0, 0x002e, 0x002c, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x003c, 0x003e, 0x002f, 0x007c, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x0000,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1,  // 0x78
-    0x00b1, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
-}};
-
-const MockGroupLayoutData kLayoutFrench0 = {
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
-    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
-    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
-    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
-    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
-    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
-    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
-    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
-};
-
-const MockGroupLayoutData kLayoutFrench3 = {
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0x0000, 0xffff, 0x0000, 0x0000, 0x0026, 0x0031, 0x00e9, 0x0032,  // 0x08
-    0x0022, 0x0033, 0x0027, 0x0034, 0x0028, 0x0035, 0x002d, 0x0036,  // 0x0c
-    0x00e8, 0x0037, 0x005f, 0x0038, 0x00e7, 0x0039, 0x00e0, 0x0030,  // 0x10
-    0x0029, 0x00b0, 0x003d, 0x002b, 0x0000, 0x0000, 0x0061, 0x0041,  // 0x14
-    0x0061, 0x0041, 0x007a, 0x005a, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0xffff, 0xffff, 0x0024, 0x00a3,  // 0x20
-    0x0041, 0x0000, 0x0000, 0x0000, 0x0071, 0x0051, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x006d, 0x004d,  // 0x2c
-    0x00f9, 0x0025, 0x00b2, 0x007e, 0x0000, 0x0000, 0x002a, 0x00b5,  // 0x30
-    0x0077, 0x0057, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x002c, 0x003f, 0x003b, 0x002e,  // 0x38
-    0x003a, 0x002f, 0x0021, 0x00a7, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0x003c, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
-};
-
-const MockLayoutData kLayoutUs{&kLayoutUs0};
-const MockLayoutData kLayoutRussian{&kLayoutRussian0, nullptr,
-                                    &kLayoutRussian2};
-const MockLayoutData kLayoutFrench{&kLayoutFrench0, nullptr, nullptr,
-                                   &kLayoutFrench3};
-
-}  // namespace
diff --git a/shell/platform/linux/fl_keyboard_manager.cc b/shell/platform/linux/fl_keyboard_manager.cc
new file mode 100644
index 0000000..869d23b
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_manager.cc
@@ -0,0 +1,510 @@
+// 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 "flutter/shell/platform/linux/fl_keyboard_manager.h"
+
+#include <array>
+#include <cinttypes>
+#include <memory>
+#include <string>
+
+#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
+#include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
+#include "flutter/shell/platform/linux/fl_keyboard_layout.h"
+#include "flutter/shell/platform/linux/fl_keyboard_pending_event.h"
+#include "flutter/shell/platform/linux/key_mapping.h"
+
+// Turn on this flag to print complete layout data when switching IMEs. The data
+// is used in unit tests.
+#define DEBUG_PRINT_LAYOUT
+
+/* Declarations of private classes */
+
+G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData,
+                     fl_keyboard_manager_user_data,
+                     FL,
+                     KEYBOARD_MANAGER_USER_DATA,
+                     GObject);
+
+/* End declarations */
+
+namespace {
+
+static bool is_eascii(uint16_t character) {
+  return character < 256;
+}
+
+#ifdef DEBUG_PRINT_LAYOUT
+// Prints layout entries that will be parsed by `MockLayoutData`.
+void debug_format_layout_data(std::string& debug_layout_data,
+                              uint16_t keycode,
+                              uint16_t clue1,
+                              uint16_t clue2) {
+  if (keycode % 4 == 0) {
+    debug_layout_data.append("    ");
+  }
+
+  constexpr int kBufferSize = 30;
+  char buffer[kBufferSize];
+  buffer[0] = 0;
+  buffer[kBufferSize - 1] = 0;
+
+  snprintf(buffer, kBufferSize, "0x%04x, 0x%04x, ", clue1, clue2);
+  debug_layout_data.append(buffer);
+
+  if (keycode % 4 == 3) {
+    snprintf(buffer, kBufferSize, " // 0x%02x", keycode);
+    debug_layout_data.append(buffer);
+  }
+}
+#endif
+
+}  // namespace
+
+/* Define FlKeyboardManagerUserData */
+
+/**
+ * FlKeyboardManagerUserData:
+ * The user_data used when #FlKeyboardManager sends event to
+ * responders.
+ */
+
+struct _FlKeyboardManagerUserData {
+  GObject parent_instance;
+
+  // The owner manager.
+  GWeakRef manager;
+  uint64_t sequence_id;
+};
+
+G_DEFINE_TYPE(FlKeyboardManagerUserData,
+              fl_keyboard_manager_user_data,
+              G_TYPE_OBJECT)
+
+static void fl_keyboard_manager_user_data_dispose(GObject* object) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(object));
+  FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(object);
+
+  g_weak_ref_clear(&self->manager);
+
+  G_OBJECT_CLASS(fl_keyboard_manager_user_data_parent_class)->dispose(object);
+}
+
+static void fl_keyboard_manager_user_data_class_init(
+    FlKeyboardManagerUserDataClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_user_data_dispose;
+}
+
+static void fl_keyboard_manager_user_data_init(
+    FlKeyboardManagerUserData* self) {}
+
+// Creates a new FlKeyboardManagerUserData private class with all information.
+static FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new(
+    FlKeyboardManager* manager,
+    uint64_t sequence_id) {
+  FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(
+      g_object_new(fl_keyboard_manager_user_data_get_type(), nullptr));
+
+  g_weak_ref_init(&self->manager, manager);
+  self->sequence_id = sequence_id;
+  return self;
+}
+
+/* Define FlKeyboardManager */
+
+struct _FlKeyboardManager {
+  GObject parent_instance;
+
+  GWeakRef view_delegate;
+
+  // An array of #FlKeyResponder. Elements are added with
+  // #fl_keyboard_manager_add_responder immediately after initialization and are
+  // automatically released on dispose.
+  GPtrArray* responder_list;
+
+  // An array of #FlKeyboardPendingEvent.
+  //
+  // Its elements are *not* unreferenced when removed. When FlKeyboardManager is
+  // disposed, this array will be set with a free_func so that the elements are
+  // unreferenced when removed.
+  GPtrArray* pending_responds;
+
+  // An array of #FlKeyboardPendingEvent.
+  //
+  // Its elements are unreferenced when removed.
+  GPtrArray* pending_redispatches;
+
+  // The last sequence ID used. Increased by 1 by every use.
+  uint64_t last_sequence_id;
+
+  // Record the derived layout.
+  //
+  // It is cleared when the platform reports a layout switch. Each entry,
+  // which corresponds to a group, is only initialized on the arrival of the
+  // first event for that group that has a goal keycode.
+  FlKeyboardLayout* derived_layout;
+
+  // A static map from keycodes to all layout goals.
+  //
+  // It is set up when the manager is initialized and is not changed ever after.
+  std::unique_ptr<std::map<uint16_t, const LayoutGoal*>> keycode_to_goals;
+
+  // A static map from logical keys to all mandatory layout goals.
+  //
+  // It is set up when the manager is initialized and is not changed ever after.
+  std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
+      logical_to_mandatory_goals;
+};
+
+G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT);
+
+// This is an exact copy of g_ptr_array_find_with_equal_func.  Somehow CI
+// reports that can not find symbol g_ptr_array_find_with_equal_func, despite
+// the fact that it runs well locally.
+static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack,
+                                                  gconstpointer needle,
+                                                  GEqualFunc equal_func,
+                                                  guint* index_) {
+  guint i;
+  g_return_val_if_fail(haystack != NULL, FALSE);
+  if (equal_func == NULL) {
+    equal_func = g_direct_equal;
+  }
+  for (i = 0; i < haystack->len; i++) {
+    if (equal_func(g_ptr_array_index(haystack, i), needle)) {
+      if (index_ != NULL) {
+        *index_ = i;
+      }
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+// Compare a #FlKeyboardPendingEvent with the given sequence_id.
+static gboolean compare_pending_by_sequence_id(gconstpointer a,
+                                               gconstpointer b) {
+  FlKeyboardPendingEvent* pending =
+      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
+  uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(b);
+  return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id;
+}
+
+// Compare a #FlKeyboardPendingEvent with the given hash.
+static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) {
+  FlKeyboardPendingEvent* pending =
+      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
+  uint64_t hash = *reinterpret_cast<const uint64_t*>(b);
+  return fl_keyboard_pending_event_get_hash(pending) == hash;
+}
+
+// Try to remove a pending event from `pending_redispatches` with the target
+// hash.
+//
+// Returns true if the event is found and removed.
+static bool fl_keyboard_manager_remove_redispatched(FlKeyboardManager* self,
+                                                    uint64_t hash) {
+  guint result_index;
+  gboolean found = g_ptr_array_find_with_equal_func1(
+      self->pending_redispatches, static_cast<const uint64_t*>(&hash),
+      compare_pending_by_hash, &result_index);
+  if (found) {
+    // The removed object is freed due to `pending_redispatches`'s free_func.
+    g_ptr_array_remove_index_fast(self->pending_redispatches, result_index);
+    return TRUE;
+  } else {
+    return FALSE;
+  }
+}
+
+// The callback used by a responder after the event was dispatched.
+static void responder_handle_event_callback(bool handled,
+                                            gpointer user_data_ptr) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(user_data_ptr));
+  FlKeyboardManagerUserData* user_data =
+      FL_KEYBOARD_MANAGER_USER_DATA(user_data_ptr);
+
+  g_autoptr(FlKeyboardManager) self =
+      FL_KEYBOARD_MANAGER(g_weak_ref_get(&user_data->manager));
+  if (self == nullptr) {
+    return;
+  }
+
+  g_autoptr(FlKeyboardViewDelegate) view_delegate =
+      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
+  if (view_delegate == nullptr) {
+    return;
+  }
+
+  guint result_index = -1;
+  gboolean found = g_ptr_array_find_with_equal_func1(
+      self->pending_responds, &user_data->sequence_id,
+      compare_pending_by_sequence_id, &result_index);
+  g_return_if_fail(found);
+  FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT(
+      g_ptr_array_index(self->pending_responds, result_index));
+  g_return_if_fail(pending != nullptr);
+  fl_keyboard_pending_event_mark_replied(pending, handled);
+  // All responders have replied.
+  if (fl_keyboard_pending_event_is_complete(pending)) {
+    g_object_unref(user_data_ptr);
+    gpointer removed =
+        g_ptr_array_remove_index_fast(self->pending_responds, result_index);
+    g_return_if_fail(removed == pending);
+    bool should_redispatch =
+        !fl_keyboard_pending_event_get_any_handled(pending) &&
+        !fl_keyboard_view_delegate_text_filter_key_press(
+            view_delegate, fl_keyboard_pending_event_get_event(pending));
+    if (should_redispatch) {
+      g_ptr_array_add(self->pending_redispatches, pending);
+      fl_keyboard_view_delegate_redispatch_event(
+          view_delegate,
+          FL_KEY_EVENT(fl_keyboard_pending_event_get_event(pending)));
+    } else {
+      g_object_unref(pending);
+    }
+  }
+}
+
+static uint16_t convert_key_to_char(FlKeyboardViewDelegate* view_delegate,
+                                    guint keycode,
+                                    gint group,
+                                    gint level) {
+  GdkKeymapKey key = {keycode, group, level};
+  constexpr int kBmpMax = 0xD7FF;
+  guint origin = fl_keyboard_view_delegate_lookup_key(view_delegate, &key);
+  return origin < kBmpMax ? origin : 0xFFFF;
+}
+
+// Make sure that Flutter has derived the layout for the group of the event,
+// if the event contains a goal keycode.
+static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) {
+  g_autoptr(FlKeyboardViewDelegate) view_delegate =
+      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
+  if (view_delegate == nullptr) {
+    return;
+  }
+
+  guint8 group = fl_key_event_get_group(event);
+  if (fl_keyboard_layout_has_group(self->derived_layout, group)) {
+    return;
+  }
+  if (self->keycode_to_goals->find(fl_key_event_get_keycode(event)) ==
+      self->keycode_to_goals->end()) {
+    return;
+  }
+
+  // Clone all mandatory goals. Each goal is removed from this cloned map when
+  // fulfilled, and the remaining ones will be assigned to a default position.
+  std::map<uint64_t, const LayoutGoal*> remaining_mandatory_goals =
+      *self->logical_to_mandatory_goals;
+
+#ifdef DEBUG_PRINT_LAYOUT
+  std::string debug_layout_data;
+  for (uint16_t keycode = 0; keycode < 128; keycode += 1) {
+    std::vector<uint16_t> this_key_clues = {
+        convert_key_to_char(view_delegate, keycode, group, 0),
+        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
+    };
+    debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0],
+                             this_key_clues[1]);
+  }
+#endif
+
+  // It's important to only traverse layout goals instead of all keycodes.
+  // Some key codes outside of the standard keyboard also gives alpha-numeric
+  // letters, and will therefore take over mandatory goals from standard
+  // keyboard keys if they come first. Example: French keyboard digit 1.
+  for (const LayoutGoal& keycode_goal : layout_goals) {
+    uint16_t keycode = keycode_goal.keycode;
+    std::vector<uint16_t> this_key_clues = {
+        convert_key_to_char(view_delegate, keycode, group, 0),
+        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
+    };
+
+    // The logical key should be the first available clue from below:
+    //
+    //  - Mandatory goal, if it matches any clue. This ensures that all alnum
+    //    keys can be found somewhere.
+    //  - US layout, if neither clue of the key is EASCII. This ensures that
+    //    there are no non-latin logical keys.
+    //  - A value derived on the fly from keycode & keyval.
+    for (uint16_t clue : this_key_clues) {
+      auto matching_goal = remaining_mandatory_goals.find(clue);
+      if (matching_goal != remaining_mandatory_goals.end()) {
+        // Found a key that produces a mandatory char. Use it.
+        g_return_if_fail(fl_keyboard_layout_get_logical_key(
+                             self->derived_layout, group, keycode) == 0);
+        fl_keyboard_layout_set_logical_key(self->derived_layout, group, keycode,
+                                           clue);
+        remaining_mandatory_goals.erase(matching_goal);
+        break;
+      }
+    }
+    bool has_any_eascii =
+        is_eascii(this_key_clues[0]) || is_eascii(this_key_clues[1]);
+    // See if any produced char meets the requirement as a logical key.
+    if (fl_keyboard_layout_get_logical_key(self->derived_layout, group,
+                                           keycode) == 0 &&
+        !has_any_eascii) {
+      auto found_us_layout = self->keycode_to_goals->find(keycode);
+      if (found_us_layout != self->keycode_to_goals->end()) {
+        fl_keyboard_layout_set_logical_key(
+            self->derived_layout, group, keycode,
+            found_us_layout->second->logical_key);
+      }
+    }
+  }
+
+  // Ensure all mandatory goals are assigned.
+  for (const auto mandatory_goal_iter : remaining_mandatory_goals) {
+    const LayoutGoal* goal = mandatory_goal_iter.second;
+    fl_keyboard_layout_set_logical_key(self->derived_layout, group,
+                                       goal->keycode, goal->logical_key);
+  }
+}
+
+static void fl_keyboard_manager_dispose(GObject* object) {
+  FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object);
+
+  g_weak_ref_clear(&self->view_delegate);
+
+  self->keycode_to_goals.reset();
+  self->logical_to_mandatory_goals.reset();
+
+  g_ptr_array_free(self->responder_list, TRUE);
+  g_ptr_array_set_free_func(self->pending_responds, g_object_unref);
+  g_ptr_array_free(self->pending_responds, TRUE);
+  g_ptr_array_free(self->pending_redispatches, TRUE);
+  g_clear_object(&self->derived_layout);
+
+  G_OBJECT_CLASS(fl_keyboard_manager_parent_class)->dispose(object);
+}
+
+static void fl_keyboard_manager_class_init(FlKeyboardManagerClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_dispose;
+}
+
+static void fl_keyboard_manager_init(FlKeyboardManager* self) {
+  self->derived_layout = fl_keyboard_layout_new();
+
+  self->keycode_to_goals =
+      std::make_unique<std::map<uint16_t, const LayoutGoal*>>();
+  self->logical_to_mandatory_goals =
+      std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
+  for (const LayoutGoal& goal : layout_goals) {
+    (*self->keycode_to_goals)[goal.keycode] = &goal;
+    if (goal.mandatory) {
+      (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
+    }
+  }
+
+  self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
+
+  self->pending_responds = g_ptr_array_new();
+  self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
+
+  self->last_sequence_id = 1;
+}
+
+FlKeyboardManager* fl_keyboard_manager_new(
+    FlKeyboardViewDelegate* view_delegate) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr);
+
+  FlKeyboardManager* self = FL_KEYBOARD_MANAGER(
+      g_object_new(fl_keyboard_manager_get_type(), nullptr));
+
+  g_weak_ref_init(&self->view_delegate, view_delegate);
+
+  // The embedder responder must be added before the channel responder.
+  g_ptr_array_add(
+      self->responder_list,
+      FL_KEY_RESPONDER(fl_key_embedder_responder_new(
+          [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback,
+             void* callback_user_data, void* send_key_event_user_data) {
+            FlKeyboardManager* self =
+                FL_KEYBOARD_MANAGER(send_key_event_user_data);
+            g_autoptr(FlKeyboardViewDelegate) view_delegate =
+                FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
+            if (view_delegate == nullptr) {
+              return;
+            }
+            fl_keyboard_view_delegate_send_key_event(
+                view_delegate, event, callback, callback_user_data);
+          },
+          self)));
+  g_ptr_array_add(self->responder_list,
+                  FL_KEY_RESPONDER(fl_key_channel_responder_new(
+                      fl_keyboard_view_delegate_get_messenger(view_delegate))));
+
+  return self;
+}
+
+gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self,
+                                          FlKeyEvent* event) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE);
+  g_return_val_if_fail(event != nullptr, FALSE);
+
+  guarantee_layout(self, event);
+
+  uint64_t incoming_hash = fl_key_event_hash(event);
+  if (fl_keyboard_manager_remove_redispatched(self, incoming_hash)) {
+    return FALSE;
+  }
+
+  FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new(
+      event, ++self->last_sequence_id, self->responder_list->len);
+
+  g_ptr_array_add(self->pending_responds, pending);
+  FlKeyboardManagerUserData* user_data = fl_keyboard_manager_user_data_new(
+      self, fl_keyboard_pending_event_get_sequence_id(pending));
+  uint64_t specified_logical_key = fl_keyboard_layout_get_logical_key(
+      self->derived_layout, fl_key_event_get_group(event),
+      fl_key_event_get_keycode(event));
+  for (guint i = 0; i < self->responder_list->len; i++) {
+    FlKeyResponder* responder =
+        FL_KEY_RESPONDER(g_ptr_array_index(self->responder_list, i));
+    fl_key_responder_handle_event(responder, event,
+                                  responder_handle_event_callback, user_data,
+                                  specified_logical_key);
+  }
+
+  return TRUE;
+}
+
+gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE);
+  return self->pending_responds->len == 0 &&
+         self->pending_redispatches->len == 0;
+}
+
+void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self,
+                                                 guint state,
+                                                 double event_time) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self));
+
+  // The embedder responder is the first element in
+  // FlKeyboardManager.responder_list.
+  FlKeyEmbedderResponder* responder =
+      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
+  fl_key_embedder_responder_sync_modifiers_if_needed(responder, state,
+                                                     event_time);
+}
+
+GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), nullptr);
+
+  // The embedder responder is the first element in
+  // FlKeyboardManager.responder_list.
+  FlKeyEmbedderResponder* responder =
+      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
+  return fl_key_embedder_responder_get_pressed_state(responder);
+}
+
+void fl_keyboard_manager_notify_layout_changed(FlKeyboardManager* self) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self));
+  g_clear_object(&self->derived_layout);
+  self->derived_layout = fl_keyboard_layout_new();
+}
diff --git a/shell/platform/linux/fl_keyboard_manager.h b/shell/platform/linux/fl_keyboard_manager.h
new file mode 100644
index 0000000..be1be5b
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_manager.h
@@ -0,0 +1,105 @@
+// 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_LINUX_FL_KEYBOARD_MANAGER_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
+
+#include <gdk/gdk.h>
+
+#include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h"
+
+G_BEGIN_DECLS
+
+#define FL_TYPE_KEYBOARD_MANAGER fl_keyboard_manager_get_type()
+G_DECLARE_FINAL_TYPE(FlKeyboardManager,
+                     fl_keyboard_manager,
+                     FL,
+                     KEYBOARD_MANAGER,
+                     GObject);
+
+/**
+ * FlKeyboardManager:
+ *
+ * Processes keyboard events and cooperate with `TextInputManager`.
+ *
+ * A keyboard event goes through a few sections, each can choose to handle the
+ * event, and only unhandled events can move to the next section:
+ *
+ * - Keyboard: Dispatch to the embedder responder and the channel responder
+ *   simultaneously. After both responders have responded (asynchronously), the
+ *   event is considered handled if either responder handles it.
+ * - Text input: Events are sent to IM filter (usually owned by
+ *   `TextInputManager`) and are handled synchronously.
+ * - Redispatching: Events are inserted back to the system for redispatching.
+ */
+
+/**
+ * fl_keyboard_manager_new:
+ * @view_delegate: An interface that the manager requires to communicate with
+ * the platform. Usually implemented by FlView.
+ *
+ * Create a new #FlKeyboardManager.
+ *
+ * Returns: a new #FlKeyboardManager.
+ */
+FlKeyboardManager* fl_keyboard_manager_new(
+    FlKeyboardViewDelegate* view_delegate);
+
+/**
+ * fl_keyboard_manager_handle_event:
+ * @manager: the #FlKeyboardManager self.
+ * @event: the event to be dispatched. It is usually a wrap of a GdkEventKey.
+ * This event will be managed and released by #FlKeyboardManager.
+ *
+ * Make the manager process a system key event. This might eventually send
+ * messages to the framework, trigger text input effects, or redispatch the
+ * event back to the system.
+ */
+gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* manager,
+                                          FlKeyEvent* event);
+
+/**
+ * fl_keyboard_manager_is_state_clear:
+ * @manager: the #FlKeyboardManager self.
+ *
+ * A debug-only method that queries whether the manager's various states are
+ * cleared, i.e. no pending events for redispatching or for responding.
+ *
+ * Returns: true if the manager's various states are cleared.
+ */
+gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* manager);
+
+/**
+ * fl_keyboard_manager_sync_modifier_if_needed:
+ * @manager: the #FlKeyboardManager self.
+ * @state: the state of the modifiers mask.
+ * @event_time: the time attribute of the incoming GDK event.
+ *
+ * If needed, synthesize modifier keys up and down event by comparing their
+ * current pressing states with the given modifiers mask.
+ */
+void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager,
+                                                 guint state,
+                                                 double event_time);
+
+/**
+ * fl_keyboard_manager_get_pressed_state:
+ * @manager: the #FlKeyboardManager self.
+ *
+ * Returns the keyboard pressed state. The hash table contains one entry per
+ * pressed keys, mapping from the logical key to the physical key.*
+ */
+GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* manager);
+
+/**
+ * fl_keyboard_manager_notify_layout_changed:
+ * @manager: the #FlKeyboardManager self.
+ *
+ * Notify the manager the keyboard layout has changed.
+ */
+void fl_keyboard_manager_notify_layout_changed(FlKeyboardManager* manager);
+
+G_END_DECLS
+
+#endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
diff --git a/shell/platform/linux/fl_keyboard_manager_test.cc b/shell/platform/linux/fl_keyboard_manager_test.cc
new file mode 100644
index 0000000..9011662
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_manager_test.cc
@@ -0,0 +1,1145 @@
+// 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 "flutter/shell/platform/linux/fl_keyboard_manager.h"
+
+#include <cstring>
+#include <vector>
+
+#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
+#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
+#include "flutter/shell/platform/linux/fl_method_codec_private.h"
+#include "flutter/shell/platform/linux/key_mapping.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
+#include "flutter/shell/platform/linux/testing/fl_test.h"
+#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h"
+#include "flutter/testing/testing.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+// Define compound `expect` in macros. If they were defined in functions, the
+// stacktrace wouldn't print where the function is called in the unit tests.
+
+#define EXPECT_KEY_EVENT(RECORD, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \
+  EXPECT_EQ((RECORD).type, CallRecord::kKeyCallEmbedder);                    \
+  EXPECT_EQ((RECORD).event->type, (TYPE));                                   \
+  EXPECT_EQ((RECORD).event->physical, (PHYSICAL));                           \
+  EXPECT_EQ((RECORD).event->logical, (LOGICAL));                             \
+  EXPECT_STREQ((RECORD).event->character, (CHAR));                           \
+  EXPECT_EQ((RECORD).event->synthesized, (SYNTHESIZED));
+
+#define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR)                          \
+  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);    \
+  EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \
+  EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL));         \
+  EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR));       \
+  EXPECT_EQ(call_records[0].event->synthesized, false);             \
+  call_records.clear()
+
+namespace {
+using ::flutter::testing::keycodes::kLogicalAltLeft;
+using ::flutter::testing::keycodes::kLogicalBracketLeft;
+using ::flutter::testing::keycodes::kLogicalComma;
+using ::flutter::testing::keycodes::kLogicalControlLeft;
+using ::flutter::testing::keycodes::kLogicalDigit1;
+using ::flutter::testing::keycodes::kLogicalKeyA;
+using ::flutter::testing::keycodes::kLogicalKeyB;
+using ::flutter::testing::keycodes::kLogicalKeyM;
+using ::flutter::testing::keycodes::kLogicalKeyQ;
+using ::flutter::testing::keycodes::kLogicalMetaLeft;
+using ::flutter::testing::keycodes::kLogicalMinus;
+using ::flutter::testing::keycodes::kLogicalParenthesisRight;
+using ::flutter::testing::keycodes::kLogicalSemicolon;
+using ::flutter::testing::keycodes::kLogicalShiftLeft;
+using ::flutter::testing::keycodes::kLogicalUnderscore;
+
+using ::flutter::testing::keycodes::kPhysicalAltLeft;
+using ::flutter::testing::keycodes::kPhysicalControlLeft;
+using ::flutter::testing::keycodes::kPhysicalKeyA;
+using ::flutter::testing::keycodes::kPhysicalKeyB;
+using ::flutter::testing::keycodes::kPhysicalMetaLeft;
+using ::flutter::testing::keycodes::kPhysicalShiftLeft;
+
+// Hardware key codes.
+typedef std::function<void(bool handled)> AsyncKeyCallback;
+typedef std::function<void(AsyncKeyCallback callback)> ChannelCallHandler;
+typedef std::function<void(const FlutterKeyEvent* event,
+                           AsyncKeyCallback callback)>
+    EmbedderCallHandler;
+typedef std::function<void(FlKeyEvent*)> RedispatchHandler;
+
+// A type that can record all kinds of effects that the keyboard handler
+// triggers.
+//
+// An instance of `CallRecord` might not have all the fields filled.
+typedef struct {
+  enum {
+    kKeyCallEmbedder,
+    kKeyCallChannel,
+  } type;
+
+  AsyncKeyCallback callback;
+  std::unique_ptr<FlutterKeyEvent> event;
+  std::unique_ptr<char[]> event_character;
+} CallRecord;
+
+// Clone a C-string.
+//
+// Must be deleted by delete[].
+char* cloneString(const char* source) {
+  if (source == nullptr) {
+    return nullptr;
+  }
+  size_t charLen = strlen(source);
+  char* target = new char[charLen + 1];
+  strncpy(target, source, charLen + 1);
+  return target;
+}
+
+constexpr guint16 kKeyCodeKeyA = 0x26u;
+constexpr guint16 kKeyCodeKeyB = 0x38u;
+constexpr guint16 kKeyCodeKeyM = 0x3au;
+constexpr guint16 kKeyCodeDigit1 = 0x0au;
+constexpr guint16 kKeyCodeMinus = 0x14u;
+constexpr guint16 kKeyCodeSemicolon = 0x2fu;
+constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u;
+
+static constexpr char kKeyEventChannelName[] = "flutter/keyevent";
+
+// All key clues for a keyboard layout.
+//
+// The index is (keyCode * 2 + hasShift), where each value is the character for
+// this key (GTK only supports UTF-16.) Since the maximum keycode of interest
+// is 128, it has a total of 256 entries..
+typedef std::array<uint32_t, 256> MockGroupLayoutData;
+typedef std::vector<const MockGroupLayoutData*> MockLayoutData;
+
+extern const MockLayoutData kLayoutUs;
+extern const MockLayoutData kLayoutRussian;
+extern const MockLayoutData kLayoutFrench;
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(FlMockViewDelegate,
+                     fl_mock_view_delegate,
+                     FL,
+                     MOCK_VIEW_DELEGATE,
+                     GObject);
+
+G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger,
+                     fl_mock_key_binary_messenger,
+                     FL,
+                     MOCK_KEY_BINARY_MESSENGER,
+                     GObject)
+
+G_END_DECLS
+
+MATCHER_P(MethodSuccessResponse, result, "") {
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodResponse) response =
+      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr);
+  fl_method_response_get_result(response, nullptr);
+  if (fl_value_equal(fl_method_response_get_result(response, nullptr),
+                     result)) {
+    return true;
+  }
+  *result_listener << ::testing::PrintToString(response);
+  return false;
+}
+
+/***** FlMockKeyBinaryMessenger *****/
+/* Mock a binary messenger that only processes messages from the embedding on
+ * the key event channel, and does so according to the callback set by
+ * fl_mock_key_binary_messenger_set_callback_handler */
+
+struct _FlMockKeyBinaryMessenger {
+  GObject parent_instance;
+
+  ChannelCallHandler callback_handler;
+};
+
+static void fl_mock_key_binary_messenger_iface_init(
+    FlBinaryMessengerInterface* iface);
+
+G_DEFINE_TYPE_WITH_CODE(
+    FlMockKeyBinaryMessenger,
+    fl_mock_key_binary_messenger,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(),
+                          fl_mock_key_binary_messenger_iface_init))
+
+static void fl_mock_key_binary_messenger_init(FlMockKeyBinaryMessenger* self) {}
+
+static void fl_mock_key_binary_messenger_dispose(GObject* object) {
+  G_OBJECT_CLASS(fl_mock_key_binary_messenger_parent_class)->dispose(object);
+}
+
+static void fl_mock_key_binary_messenger_class_init(
+    FlMockKeyBinaryMessengerClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_mock_key_binary_messenger_dispose;
+}
+
+static void fl_mock_key_binary_messenger_send_on_channel(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    GBytes* message,
+    GCancellable* cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data) {
+  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(messenger);
+
+  if (callback != nullptr) {
+    EXPECT_STREQ(channel, kKeyEventChannelName);
+    self->callback_handler([self, cancellable, callback,
+                            user_data](bool handled) {
+      g_autoptr(GTask) task =
+          g_task_new(self, cancellable, callback, user_data);
+      g_autoptr(FlValue) result = fl_value_new_map();
+      fl_value_set_string_take(result, "handled", fl_value_new_bool(handled));
+      g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
+      g_autoptr(GError) error = nullptr;
+      GBytes* data = fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec),
+                                                     result, &error);
+
+      g_task_return_pointer(task, data,
+                            reinterpret_cast<GDestroyNotify>(g_bytes_unref));
+    });
+  }
+}
+
+static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish(
+    FlBinaryMessenger* messenger,
+    GAsyncResult* result,
+    GError** error) {
+  return static_cast<GBytes*>(g_task_propagate_pointer(G_TASK(result), error));
+}
+
+static void fl_mock_binary_messenger_resize_channel(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    int64_t new_size) {
+  // Mock implementation. Do nothing.
+}
+
+static void fl_mock_binary_messenger_set_warns_on_channel_overflow(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    bool warns) {
+  // Mock implementation. Do nothing.
+}
+
+static void fl_mock_key_binary_messenger_iface_init(
+    FlBinaryMessengerInterface* iface) {
+  iface->set_message_handler_on_channel =
+      [](FlBinaryMessenger* messenger, const gchar* channel,
+         FlBinaryMessengerMessageHandler handler, gpointer user_data,
+         GDestroyNotify destroy_notify) {
+        EXPECT_STREQ(channel, kKeyEventChannelName);
+        // No need to mock. The key event channel expects no incoming messages
+        // from the framework.
+      };
+  iface->send_response = [](FlBinaryMessenger* messenger,
+                            FlBinaryMessengerResponseHandle* response_handle,
+                            GBytes* response, GError** error) -> gboolean {
+    // The key event channel expects no incoming messages from the framework,
+    // hence no responses either.
+    g_return_val_if_reached(TRUE);
+    return TRUE;
+  };
+  iface->send_on_channel = fl_mock_key_binary_messenger_send_on_channel;
+  iface->send_on_channel_finish =
+      fl_mock_key_binary_messenger_send_on_channel_finish;
+  iface->resize_channel = fl_mock_binary_messenger_resize_channel;
+  iface->set_warns_on_channel_overflow =
+      fl_mock_binary_messenger_set_warns_on_channel_overflow;
+}
+
+static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() {
+  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(
+      g_object_new(fl_mock_key_binary_messenger_get_type(), NULL));
+
+  // Added to stop compiler complaining about an unused function.
+  FL_IS_MOCK_KEY_BINARY_MESSENGER(self);
+
+  return self;
+}
+
+static void fl_mock_key_binary_messenger_set_callback_handler(
+    FlMockKeyBinaryMessenger* self,
+    ChannelCallHandler handler) {
+  self->callback_handler = std::move(handler);
+}
+
+/***** FlMockViewDelegate *****/
+
+struct _FlMockViewDelegate {
+  GObject parent_instance;
+
+  FlMockKeyBinaryMessenger* messenger;
+  EmbedderCallHandler embedder_handler;
+  bool text_filter_result;
+  RedispatchHandler redispatch_handler;
+  const MockLayoutData* layout_data;
+};
+
+static void fl_mock_view_keyboard_delegate_iface_init(
+    FlKeyboardViewDelegateInterface* iface);
+
+G_DEFINE_TYPE_WITH_CODE(
+    FlMockViewDelegate,
+    fl_mock_view_delegate,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
+                          fl_mock_view_keyboard_delegate_iface_init))
+
+static void fl_mock_view_delegate_init(FlMockViewDelegate* self) {}
+
+static void fl_mock_view_delegate_dispose(GObject* object) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(object);
+
+  g_clear_object(&self->messenger);
+
+  G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object);
+}
+
+static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose;
+}
+
+static void fl_mock_view_keyboard_send_key_event(
+    FlKeyboardViewDelegate* view_delegate,
+    const FlutterKeyEvent* event,
+    FlutterKeyEventCallback callback,
+    void* user_data) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  self->embedder_handler(event, [callback, user_data](bool handled) {
+    if (callback != nullptr) {
+      callback(handled, user_data);
+    }
+  });
+}
+
+static gboolean fl_mock_view_keyboard_text_filter_key_press(
+    FlKeyboardViewDelegate* view_delegate,
+    FlKeyEvent* event) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  return self->text_filter_result;
+}
+
+static FlBinaryMessenger* fl_mock_view_keyboard_get_messenger(
+    FlKeyboardViewDelegate* view_delegate) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  return FL_BINARY_MESSENGER(self->messenger);
+}
+
+static void fl_mock_view_keyboard_redispatch_event(
+    FlKeyboardViewDelegate* view_delegate,
+    FlKeyEvent* event) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  if (self->redispatch_handler) {
+    self->redispatch_handler(event);
+  }
+}
+
+static guint fl_mock_view_keyboard_lookup_key(
+    FlKeyboardViewDelegate* view_delegate,
+    const GdkKeymapKey* key) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  guint8 group = static_cast<guint8>(key->group);
+  EXPECT_LT(group, self->layout_data->size());
+  const MockGroupLayoutData* group_layout = (*self->layout_data)[group];
+  EXPECT_TRUE(group_layout != nullptr);
+  EXPECT_TRUE(key->level == 0 || key->level == 1);
+  bool shift = key->level == 1;
+  return (*group_layout)[key->keycode * 2 + shift];
+}
+
+static void fl_mock_view_keyboard_delegate_iface_init(
+    FlKeyboardViewDelegateInterface* iface) {
+  iface->send_key_event = fl_mock_view_keyboard_send_key_event;
+  iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press;
+  iface->get_messenger = fl_mock_view_keyboard_get_messenger;
+  iface->redispatch_event = fl_mock_view_keyboard_redispatch_event;
+  iface->lookup_key = fl_mock_view_keyboard_lookup_key;
+}
+
+static FlMockViewDelegate* fl_mock_view_delegate_new() {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(
+      g_object_new(fl_mock_view_delegate_get_type(), nullptr));
+
+  // Added to stop compiler complaining about an unused function.
+  FL_IS_MOCK_VIEW_DELEGATE(self);
+
+  self->messenger = fl_mock_key_binary_messenger_new();
+
+  return self;
+}
+
+static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self,
+                                              EmbedderCallHandler handler) {
+  self->embedder_handler = std::move(handler);
+}
+
+static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self,
+                                                bool result) {
+  self->text_filter_result = result;
+}
+
+static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self,
+                                                RedispatchHandler handler) {
+  self->redispatch_handler = std::move(handler);
+}
+
+static void fl_mock_view_set_layout(FlMockViewDelegate* self,
+                                    const MockLayoutData* layout) {
+  self->layout_data = layout;
+}
+
+/***** End FlMockViewDelegate *****/
+
+class KeyboardTester {
+ public:
+  KeyboardTester() {
+    view_ = fl_mock_view_delegate_new();
+    respondToEmbedderCallsWith(false);
+    respondToChannelCallsWith(false);
+    respondToTextInputWith(false);
+    setLayout(kLayoutUs);
+
+    handler_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_));
+  }
+
+  ~KeyboardTester() {
+    g_clear_object(&view_);
+    g_clear_object(&handler_);
+    g_clear_pointer(&redispatched_events_, g_ptr_array_unref);
+  }
+
+  FlKeyboardManager* handler() { return handler_; }
+
+  // Block until all GdkMainLoop messages are processed, which is basically
+  // used only for channel messages.
+  void flushChannelMessages() {
+    GMainLoop* loop = g_main_loop_new(nullptr, 0);
+    g_idle_add(_flushChannelMessagesCb, loop);
+    g_main_loop_run(loop);
+  }
+
+  // Dispatch each of the given events, expect their results to be false
+  // (unhandled), and clear the event array.
+  //
+  // Returns the number of events redispatched. If any result is unexpected
+  // (handled), return a minus number `-x` instead, where `x` is the index of
+  // the first unexpected redispatch.
+  int redispatchEventsAndClear(GPtrArray* events) {
+    guint event_count = events->len;
+    int first_error = -1;
+    during_redispatch_ = true;
+    for (guint event_id = 0; event_id < event_count; event_id += 1) {
+      FlKeyEvent* event = FL_KEY_EVENT(g_ptr_array_index(events, event_id));
+      bool handled = fl_keyboard_manager_handle_event(handler_, event);
+      EXPECT_FALSE(handled);
+      if (handled) {
+        first_error = first_error == -1 ? event_id : first_error;
+      }
+    }
+    during_redispatch_ = false;
+    g_ptr_array_set_size(events, 0);
+    return first_error < 0 ? event_count : -first_error;
+  }
+
+  void respondToEmbedderCallsWith(bool response) {
+    fl_mock_view_set_embedder_handler(
+        view_, [response, this](const FlutterKeyEvent* event,
+                                const AsyncKeyCallback& callback) {
+          EXPECT_FALSE(during_redispatch_);
+          callback(response);
+        });
+  }
+
+  void recordEmbedderCallsTo(std::vector<CallRecord>& storage) {
+    fl_mock_view_set_embedder_handler(
+        view_, [&storage, this](const FlutterKeyEvent* event,
+                                AsyncKeyCallback callback) {
+          EXPECT_FALSE(during_redispatch_);
+          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
+          char* new_event_character = cloneString(event->character);
+          new_event->character = new_event_character;
+          storage.push_back(CallRecord{
+              .type = CallRecord::kKeyCallEmbedder,
+              .callback = std::move(callback),
+              .event = std::move(new_event),
+              .event_character = std::unique_ptr<char[]>(new_event_character),
+          });
+        });
+  }
+
+  void respondToEmbedderCallsWithAndRecordsTo(
+      bool response,
+      std::vector<CallRecord>& storage) {
+    fl_mock_view_set_embedder_handler(
+        view_, [&storage, response, this](const FlutterKeyEvent* event,
+                                          const AsyncKeyCallback& callback) {
+          EXPECT_FALSE(during_redispatch_);
+          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
+          char* new_event_character = cloneString(event->character);
+          new_event->character = new_event_character;
+          storage.push_back(CallRecord{
+              .type = CallRecord::kKeyCallEmbedder,
+              .event = std::move(new_event),
+              .event_character = std::unique_ptr<char[]>(new_event_character),
+          });
+          callback(response);
+        });
+  }
+
+  void respondToChannelCallsWith(bool response) {
+    fl_mock_key_binary_messenger_set_callback_handler(
+        view_->messenger, [response, this](const AsyncKeyCallback& callback) {
+          EXPECT_FALSE(during_redispatch_);
+          callback(response);
+        });
+  }
+
+  void recordChannelCallsTo(std::vector<CallRecord>& storage) {
+    fl_mock_key_binary_messenger_set_callback_handler(
+        view_->messenger, [&storage, this](AsyncKeyCallback callback) {
+          EXPECT_FALSE(during_redispatch_);
+          storage.push_back(CallRecord{
+              .type = CallRecord::kKeyCallChannel,
+              .callback = std::move(callback),
+          });
+        });
+  }
+
+  void respondToTextInputWith(bool response) {
+    fl_mock_view_set_text_filter_result(view_, response);
+  }
+
+  void recordRedispatchedEventsTo(GPtrArray* storage) {
+    redispatched_events_ = g_ptr_array_ref(storage);
+    fl_mock_view_set_redispatch_handler(view_, [this](FlKeyEvent* key) {
+      g_ptr_array_add(redispatched_events_, g_object_ref(key));
+    });
+  }
+
+  void setLayout(const MockLayoutData& layout) {
+    fl_mock_view_set_layout(view_, &layout);
+    if (handler_ != nullptr) {
+      fl_keyboard_manager_notify_layout_changed(handler_);
+    }
+  }
+
+ private:
+  FlMockViewDelegate* view_;
+  FlKeyboardManager* handler_ = nullptr;
+  GPtrArray* redispatched_events_ = nullptr;
+  bool during_redispatch_ = false;
+
+  static gboolean _flushChannelMessagesCb(gpointer data) {
+    g_autoptr(GMainLoop) loop = reinterpret_cast<GMainLoop*>(data);
+    g_main_loop_quit(loop);
+    return FALSE;
+  }
+};
+
+// Make sure that the keyboard can be disposed without crashes when there are
+// unresolved pending events.
+TEST(FlKeyboardManagerTest, DisposeWithUnresolvedPends) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+
+  // Record calls so that they aren't responded.
+  tester.recordEmbedderCallsTo(call_records);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_manager_handle_event(tester.handler(), event1);
+
+  tester.respondToEmbedderCallsWith(true);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_manager_handle_event(tester.handler(), event2);
+
+  tester.flushChannelMessages();
+
+  // Passes if the cleanup does not crash.
+}
+
+TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+
+  gboolean handler_handled = false;
+
+  /// Test 1: One event that is handled by the framework
+  tester.recordEmbedderCallsTo(call_records);
+  tester.recordRedispatchedEventsTo(redispatched);
+
+  // Dispatch a key event
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
+                   kLogicalKeyA, "a", false);
+
+  call_records[0].callback(true);
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  call_records.clear();
+
+  /// Test 2: Two events that are unhandled by the framework
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
+                   kLogicalKeyA, nullptr, false);
+
+  // Dispatch another key event
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyB, GDK_KEY_b, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event3);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 2u);
+  EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB,
+                   kLogicalKeyB, "b", false);
+
+  // Resolve the second event first to test out-of-order response
+  call_records[1].callback(false);
+  EXPECT_EQ(redispatched->len, 1u);
+  EXPECT_EQ(
+      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 0))),
+      0x62u);
+  call_records[0].callback(false);
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 2u);
+  EXPECT_EQ(
+      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 1))),
+      0x61u);
+
+  EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  call_records.clear();
+
+  // Resolve redispatches
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 2);
+  tester.flushChannelMessages();
+  EXPECT_EQ(call_records.size(), 0u);
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+
+  /// Test 3: Dispatch the same event again to ensure that prevention from
+  /// redispatching only works once.
+  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event4);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 1u);
+
+  call_records[0].callback(true);
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) {
+  KeyboardTester tester;
+  gboolean handler_handled = false;
+  std::vector<CallRecord> call_records;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+
+  /// Test 1: One event that is handled by the framework
+  tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records);
+  tester.recordRedispatchedEventsTo(redispatched);
+
+  // Dispatch a key event
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
+                   kLogicalKeyA, "a", false);
+  EXPECT_EQ(redispatched->len, 0u);
+  call_records.clear();
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  g_ptr_array_set_size(redispatched, 0);
+
+  /// Test 2: An event unhandled by the framework
+  tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
+                   kLogicalKeyA, nullptr, false);
+  EXPECT_EQ(redispatched->len, 1u);
+  call_records.clear();
+
+  EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.handler()));
+
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
+  EXPECT_EQ(call_records.size(), 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, WithTwoAsyncDelegates) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+
+  gboolean handler_handled = false;
+
+  tester.recordEmbedderCallsTo(call_records);
+  tester.recordChannelCallsTo(call_records);
+  tester.recordRedispatchedEventsTo(redispatched);
+
+  /// Test 1: One delegate responds true, the other false
+
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1);
+
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 2u);
+
+  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
+  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
+
+  call_records[0].callback(true);
+  call_records[1].callback(false);
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  call_records.clear();
+
+  /// Test 2: All delegates respond false
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2);
+
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 2u);
+
+  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
+  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
+
+  call_records[0].callback(false);
+  call_records[1].callback(false);
+
+  call_records.clear();
+
+  // Resolve redispatch
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 1u);
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
+  EXPECT_EQ(call_records.size(), 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, TextInputHandlerReturnsFalse) {
+  KeyboardTester tester;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+  gboolean handler_handled = false;
+  tester.recordRedispatchedEventsTo(redispatched);
+  tester.respondToTextInputWith(false);
+
+  // Dispatch a key event.
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  // The event was redispatched because no one handles it.
+  EXPECT_EQ(redispatched->len, 1u);
+
+  // Resolve redispatched event.
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, TextInputHandlerReturnsTrue) {
+  KeyboardTester tester;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+  gboolean handler_handled = false;
+  tester.recordRedispatchedEventsTo(redispatched);
+  tester.respondToTextInputWith(true);
+
+  // Dispatch a key event.
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  // The event was not redispatched because handler handles it.
+  EXPECT_EQ(redispatched->len, 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) {
+  KeyboardTester tester;
+
+  std::vector<CallRecord> call_records;
+  tester.recordEmbedderCallsTo(call_records);
+
+  auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) {
+    g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+        0, TRUE, keycode, keyval, static_cast<GdkModifierType>(0), group);
+    fl_keyboard_manager_handle_event(tester.handler(), event1);
+    g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+        0, FALSE, keycode, keyval, static_cast<GdkModifierType>(0), group);
+    fl_keyboard_manager_handle_event(tester.handler(), event2);
+  };
+
+  /* US keyboard layout */
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "a");
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_A, 0);  // Shift-KeyA
+  VERIFY_DOWN(kLogicalKeyA, "A");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0);  // Shift-Digit1
+  VERIFY_DOWN(kLogicalDigit1, "!");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_minus, 0);  // Minus
+  VERIFY_DOWN(kLogicalMinus, "-");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0);  // Shift-Minus
+  VERIFY_DOWN(kLogicalUnderscore, "_");
+
+  /* French keyboard layout, group 3, which is when the input method is showing
+   * "Fr" */
+
+  tester.setLayout(kLayoutFrench);
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_q, 3);  // KeyA
+  VERIFY_DOWN(kLogicalKeyQ, "q");
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3);  // Shift-KeyA
+  VERIFY_DOWN(kLogicalKeyQ, "Q");
+
+  sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3);  // ; but prints M
+  VERIFY_DOWN(kLogicalKeyM, "m");
+
+  sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3);  // M but prints ,
+  VERIFY_DOWN(kLogicalComma, ",");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3);  // Digit1
+  VERIFY_DOWN(kLogicalDigit1, "&");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 3);  // Shift-Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3);  // Minus
+  VERIFY_DOWN(kLogicalParenthesisRight, ")");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_degree, 3);  // Shift-Minus
+  VERIFY_DOWN(static_cast<uint32_t>(L'°'), "°");
+
+  /* French keyboard layout, group 0, which is pressing the "extra key for
+   * triggering input method" key once after switching to French IME. */
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "a");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  /* Russian keyboard layout, group 2 */
+  tester.setLayout(kLayoutRussian);
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "ф");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 2);  // Shift-Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2);
+  VERIFY_DOWN(kLogicalBracketLeft, "х");
+
+  /* Russian keyboard layout, group 0 */
+  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "a");
+
+  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0);
+  VERIFY_DOWN(kLogicalBracketLeft, "[");
+}
+
+TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+  tester.recordEmbedderCallsTo(call_records);
+
+  auto verifyModifierIsSynthesized = [&](GdkModifierType mask,
+                                         uint64_t physical, uint64_t logical) {
+    // Modifier is pressed.
+    guint state = mask;
+    fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1000);
+    EXPECT_EQ(call_records.size(), 1u);
+    EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, physical,
+                     logical, NULL, true);
+    // Modifier is released.
+    state = state ^ mask;
+    fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1001);
+    EXPECT_EQ(call_records.size(), 2u);
+    EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeUp, physical, logical,
+                     NULL, true);
+    call_records.clear();
+  };
+
+  // No modifiers pressed.
+  guint state = 0;
+  fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1000);
+  EXPECT_EQ(call_records.size(), 0u);
+  call_records.clear();
+
+  // Press and release each modifier once.
+  verifyModifierIsSynthesized(GDK_CONTROL_MASK, kPhysicalControlLeft,
+                              kLogicalControlLeft);
+  verifyModifierIsSynthesized(GDK_META_MASK, kPhysicalMetaLeft,
+                              kLogicalMetaLeft);
+  verifyModifierIsSynthesized(GDK_MOD1_MASK, kPhysicalAltLeft, kLogicalAltLeft);
+  verifyModifierIsSynthesized(GDK_SHIFT_MASK, kPhysicalShiftLeft,
+                              kLogicalShiftLeft);
+}
+
+TEST(FlKeyboardManagerTest, GetPressedState) {
+  KeyboardTester tester;
+  tester.respondToTextInputWith(true);
+
+  // Dispatch a key event.
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_manager_handle_event(tester.handler(), event);
+
+  GHashTable* pressedState =
+      fl_keyboard_manager_get_pressed_state(tester.handler());
+  EXPECT_EQ(g_hash_table_size(pressedState), 1u);
+
+  gpointer physical_key =
+      g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA));
+  EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA);
+}
+
+// The following layout data is generated using DEBUG_PRINT_LAYOUT.
+
+const MockGroupLayoutData kLayoutUs0{{
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0xffff, 0x0031, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
+    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
+    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
+    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
+    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
+    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
+    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
+    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x003c, 0x003e, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
+}};
+
+const MockGroupLayoutData kLayoutRussian0{
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
+    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
+    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
+    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
+    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
+    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
+    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
+    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
+};
+
+const MockGroupLayoutData kLayoutRussian2{{
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0xffff, 0x0031, 0x0021, 0x0000, 0x0031, 0x0021, 0x0032, 0x0022,  // 0x08
+    0x0033, 0x06b0, 0x0034, 0x003b, 0x0035, 0x0025, 0x0036, 0x003a,  // 0x0c
+    0x0037, 0x003f, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0x0071, 0x0051, 0x0000, 0x0000,  // 0x14
+    0x06ca, 0x06ea, 0x06c3, 0x06e3, 0x06d5, 0x06f5, 0x06cb, 0x06eb,  // 0x18
+    0x06c5, 0x06e5, 0x06ce, 0x06ee, 0x06c7, 0x06e7, 0x06db, 0x06fb,  // 0x1c
+    0x06dd, 0x06fd, 0x06da, 0x06fa, 0x06c8, 0x06e8, 0x06df, 0x06ff,  // 0x20
+    0x0061, 0x0041, 0x0041, 0x0000, 0x06c6, 0x06e6, 0x06d9, 0x06f9,  // 0x24
+    0x06d7, 0x06f7, 0x06c1, 0x06e1, 0x06d0, 0x06f0, 0x06d2, 0x06f2,  // 0x28
+    0x06cf, 0x06ef, 0x06cc, 0x06ec, 0x06c4, 0x06e4, 0x06d6, 0x06f6,  // 0x2c
+    0x06dc, 0x06fc, 0x06a3, 0x06b3, 0x007c, 0x0000, 0x005c, 0x002f,  // 0x30
+    0x06d1, 0x06f1, 0x06de, 0x06fe, 0x06d3, 0x06f3, 0x06cd, 0x06ed,  // 0x34
+    0x06c9, 0x06e9, 0x06d4, 0x06f4, 0x06d8, 0x06f8, 0x06c2, 0x06e2,  // 0x38
+    0x06c0, 0x06e0, 0x002e, 0x002c, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x003c, 0x003e, 0x002f, 0x007c, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x0000,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1,  // 0x78
+    0x00b1, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
+}};
+
+const MockGroupLayoutData kLayoutFrench0 = {
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
+    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
+    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
+    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
+    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
+    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
+    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
+    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
+};
+
+const MockGroupLayoutData kLayoutFrench3 = {
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0x0000, 0xffff, 0x0000, 0x0000, 0x0026, 0x0031, 0x00e9, 0x0032,  // 0x08
+    0x0022, 0x0033, 0x0027, 0x0034, 0x0028, 0x0035, 0x002d, 0x0036,  // 0x0c
+    0x00e8, 0x0037, 0x005f, 0x0038, 0x00e7, 0x0039, 0x00e0, 0x0030,  // 0x10
+    0x0029, 0x00b0, 0x003d, 0x002b, 0x0000, 0x0000, 0x0061, 0x0041,  // 0x14
+    0x0061, 0x0041, 0x007a, 0x005a, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0xffff, 0xffff, 0x0024, 0x00a3,  // 0x20
+    0x0041, 0x0000, 0x0000, 0x0000, 0x0071, 0x0051, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x006d, 0x004d,  // 0x2c
+    0x00f9, 0x0025, 0x00b2, 0x007e, 0x0000, 0x0000, 0x002a, 0x00b5,  // 0x30
+    0x0077, 0x0057, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x002c, 0x003f, 0x003b, 0x002e,  // 0x38
+    0x003a, 0x002f, 0x0021, 0x00a7, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0x003c, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
+};
+
+const MockLayoutData kLayoutUs{&kLayoutUs0};
+const MockLayoutData kLayoutRussian{&kLayoutRussian0, nullptr,
+                                    &kLayoutRussian2};
+const MockLayoutData kLayoutFrench{&kLayoutFrench0, nullptr, nullptr,
+                                   &kLayoutFrench3};
+
+}  // namespace
diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc
index 6e44ecb..1a342f9 100644
--- a/shell/platform/linux/fl_view.cc
+++ b/shell/platform/linux/fl_view.cc
@@ -17,6 +17,7 @@
 #include "flutter/shell/platform/linux/fl_framebuffer.h"
 #include "flutter/shell/platform/linux/fl_key_event.h"
 #include "flutter/shell/platform/linux/fl_keyboard_handler.h"
+#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
 #include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h"
 #include "flutter/shell/platform/linux/fl_mouse_cursor_handler.h"
 #include "flutter/shell/platform/linux/fl_platform_handler.h"
@@ -63,6 +64,8 @@
 
   FlScrollingManager* scrolling_manager;
 
+  FlKeyboardManager* keyboard_manager;
+
   // Flutter system channel handlers.
   FlKeyboardHandler* keyboard_handler;
   FlTextInputHandler* text_input_handler;
@@ -151,6 +154,9 @@
   g_clear_object(&self->keyboard_handler);
   self->keyboard_handler =
       fl_keyboard_handler_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self));
+  g_clear_object(&self->keyboard_manager);
+  self->keyboard_manager =
+      fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(self));
 }
 
 static void init_scrolling(FlView* self) {
@@ -230,7 +236,7 @@
   gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
   fl_scrolling_manager_set_last_mouse_position(
       self->scrolling_manager, event_x * scale_factor, event_y * scale_factor);
-  fl_keyboard_handler_sync_modifier_if_needed(self->keyboard_handler,
+  fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager,
                                               event_state, event_time);
   fl_engine_send_mouse_pointer_event(
       self->engine, self->view_id, phase,
@@ -500,7 +506,7 @@
 
   gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
 
-  fl_keyboard_handler_sync_modifier_if_needed(self->keyboard_handler,
+  fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager,
                                               event_state, event_time);
   fl_engine_send_mouse_pointer_event(
       self->engine, self->view_id, self->button_state != 0 ? kMove : kHover,
@@ -559,7 +565,7 @@
 }
 
 static void keymap_keys_changed_cb(FlView* self) {
-  fl_keyboard_handler_notify_layout_changed(self->keyboard_handler);
+  fl_keyboard_manager_notify_layout_changed(self->keyboard_manager);
 }
 
 static void gesture_rotation_begin_cb(FlView* self) {
@@ -728,11 +734,12 @@
   g_clear_pointer(&self->background_color, gdk_rgba_free);
   g_clear_object(&self->window_state_monitor);
   g_clear_object(&self->scrolling_manager);
-  g_clear_object(&self->keyboard_handler);
+  g_clear_object(&self->keyboard_manager);
   if (self->keymap_keys_changed_cb_id != 0) {
     g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id);
     self->keymap_keys_changed_cb_id = 0;
   }
+  g_clear_object(&self->keyboard_handler);
   g_clear_object(&self->mouse_cursor_handler);
   g_clear_object(&self->platform_handler);
   g_clear_object(&self->view_accessible);
@@ -755,8 +762,8 @@
 static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) {
   FlView* self = FL_VIEW(widget);
 
-  return fl_keyboard_handler_handle_event(
-      self->keyboard_handler, fl_key_event_new_from_gdk_event(gdk_event_copy(
+  return fl_keyboard_manager_handle_event(
+      self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy(
                                   reinterpret_cast<GdkEvent*>(event))));
 }
 
@@ -764,8 +771,8 @@
 static gboolean fl_view_key_release_event(GtkWidget* widget,
                                           GdkEventKey* event) {
   FlView* self = FL_VIEW(widget);
-  return fl_keyboard_handler_handle_event(
-      self->keyboard_handler, fl_key_event_new_from_gdk_event(gdk_event_copy(
+  return fl_keyboard_manager_handle_event(
+      self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy(
                                   reinterpret_cast<GdkEvent*>(event))));
 }
 
@@ -911,5 +918,5 @@
 
 GHashTable* fl_view_get_keyboard_state(FlView* self) {
   g_return_val_if_fail(FL_IS_VIEW(self), nullptr);
-  return fl_keyboard_handler_get_pressed_state(self->keyboard_handler);
+  return fl_keyboard_manager_get_pressed_state(self->keyboard_manager);
 }