[fuchsia][input] Migrate Flutter to "input3" (flutter/engine#23262)

This commit is contained in:
Filip Filmar 2021-01-07 15:39:03 -08:00 committed by GitHub
parent 0c179e5d66
commit 943ae09365
11 changed files with 928 additions and 224 deletions

View File

@ -1230,6 +1230,9 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/extract_far.dart
FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/framework_shim.dart
FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/libraries.json
FILE: ../../../flutter/shell/platform/fuchsia/flutter/keyboard.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/keyboard.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/keyboard_unittest.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/logging.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/loop.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/loop.h

View File

@ -63,6 +63,8 @@ template("runner_sources") {
"fuchsia_intl.h",
"isolate_configurator.cc",
"isolate_configurator.h",
"keyboard.cc",
"keyboard.h",
"logging.h",
"loop.cc",
"loop.h",
@ -446,6 +448,7 @@ executable("flutter_runner_unittests") {
"component_unittest.cc",
"flutter_runner_fakes.h",
"fuchsia_intl_unittest.cc",
"keyboard_unittest.cc",
"platform_view_unittest.cc",
"runner_unittest.cc",
"tests/engine_unittests.cc",

View File

@ -101,10 +101,16 @@ Engine::Engine(Delegate& delegate,
scenic->CreateSession2(session.NewRequest(), session_listener.Bind(),
focuser.NewRequest());
// Make clones of the `ViewRef` before sending it down to Scenic.
fuchsia::ui::views::ViewRef platform_view_ref, isolate_view_ref;
// Make clones of the `ViewRef` before sending it down to Scenic, since the
// refs are not copyable, and multiple consumers need view refs.
fuchsia::ui::views::ViewRef platform_view_ref;
view_ref_pair.view_ref.Clone(&platform_view_ref);
fuchsia::ui::views::ViewRef isolate_view_ref;
view_ref_pair.view_ref.Clone(&isolate_view_ref);
// Input3 keyboard listener registration requires a ViewRef as an event
// filter. So we clone it here, as ViewRefs can not be reused, only cloned.
fuchsia::ui::views::ViewRef keyboard_view_ref;
view_ref_pair.view_ref.Clone(&keyboard_view_ref);
// Session is terminated on the raster thread, but we must terminate ourselves
// on the platform thread.
@ -203,6 +209,26 @@ Engine::Engine(Delegate& delegate,
auto run_configuration = flutter::RunConfiguration::InferFromSettings(
settings, task_runners.GetIOTaskRunner());
// Connect to fuchsia.ui.input3.Keyboard to hand out a listener.
using fuchsia::ui::input3::Keyboard;
using fuchsia::ui::input3::KeyboardListener;
// Keyboard client-side stub.
keyboard_svc_ = svc->Connect<Keyboard>();
ZX_ASSERT(keyboard_svc_.is_bound());
// KeyboardListener handle pair is not initialized until NewRequest() is
// called.
fidl::InterfaceHandle<KeyboardListener> keyboard_listener;
// Server side of KeyboardListener. Initializes the keyboard_listener
// handle.
fidl::InterfaceRequest<KeyboardListener> keyboard_listener_request =
keyboard_listener.NewRequest();
ZX_ASSERT(keyboard_listener_request.is_valid());
keyboard_svc_->AddListener(std::move(keyboard_view_ref),
keyboard_listener.Bind(), [] {});
// Setup the callback that will instantiate the platform view.
flutter::Shell::CreateCallback<flutter::PlatformView>
on_create_platform_view = fml::MakeCopyable(
@ -222,7 +248,9 @@ Engine::Engine(Delegate& delegate,
on_create_surface_callback = std::move(on_create_surface_callback),
external_view_embedder = GetExternalViewEmbedder(),
vsync_offset = product_config.get_vsync_offset(),
vsync_handle = vsync_event_.get()](flutter::Shell& shell) mutable {
vsync_handle = vsync_event_.get(),
keyboard_listener_request = std::move(keyboard_listener_request)](
flutter::Shell& shell) mutable {
return std::make_unique<flutter_runner::PlatformView>(
shell, // delegate
debug_label, // debug label
@ -232,6 +260,9 @@ Engine::Engine(Delegate& delegate,
std::move(parent_environment_service_provider), // services
std::move(session_listener_request), // session listener
std::move(focuser),
// Server-side part of the fuchsia.ui.input3.KeyboardListener
// connection.
std::move(keyboard_listener_request),
std::move(on_session_listener_error_callback),
std::move(on_enable_wireframe_callback),
std::move(on_create_view_callback),

View File

@ -114,6 +114,8 @@ class Engine final {
friend class testing::EngineTest;
fidl::InterfacePtr<fuchsia::ui::input3::Keyboard> keyboard_svc_;
FML_DISALLOW_COPY_AND_ASSIGN(Engine);
};

View File

@ -0,0 +1,318 @@
// 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/fuchsia/flutter/keyboard.h"
#include <fuchsia/input/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/input3/cpp/fidl.h>
#include <iostream>
namespace flutter_runner {
using fuchsia::input::Key;
using fuchsia::ui::input::kModifierCapsLock;
using fuchsia::ui::input::kModifierLeftAlt;
using fuchsia::ui::input::kModifierLeftControl;
using fuchsia::ui::input::kModifierLeftShift;
using fuchsia::ui::input::kModifierNone;
using fuchsia::ui::input::kModifierRightAlt;
using fuchsia::ui::input::kModifierRightControl;
using fuchsia::ui::input::kModifierRightShift;
using fuchsia::ui::input3::KeyEvent;
using fuchsia::ui::input3::KeyEventType;
namespace {
// A simple keymap from a QWERTY keyboard to code points. A value 0 means no
// code point has been assigned for the respective keypress. Column 0 is the
// code point without a level modifier active, and Column 1 is the code point
// with a level modifier (e.g. Shift key) active.
static const uint32_t QWERTY_TO_CODE_POINTS[][2] = {
// 0x00
{},
{},
{},
{},
// 0x04,
{'a', 'A'},
{'b', 'B'},
{'c', 'C'},
{'d', 'D'},
// 0x08
{'e', 'E'},
{'f', 'F'},
{'g', 'G'},
{'h', 'H'},
// 0x0c
{'i', 'I'},
{'j', 'J'},
{'k', 'K'},
{'l', 'L'},
// 0x10
{'m', 'M'},
{'n', 'N'},
{'o', 'O'},
{'p', 'P'},
// 0x14
{'q', 'Q'},
{'r', 'R'},
{'s', 'S'},
{'t', 'T'},
// 0x18
{'u', 'U'},
{'v', 'V'},
{'w', 'W'},
{'x', 'X'},
// 0x1c
{'y', 'Y'},
{'z', 'Z'},
{'1', '!'},
{'2', '@'},
// 0x20
{'3', '#'},
{'4', '$'},
{'5', '%'},
{'6', '^'},
// 0x24
{'7', '&'},
{'8', '*'},
{'9', '('},
{'0', ')'},
// 0x28
{},
{},
{},
{},
// 0x2c
{' ', ' '},
{'-', '_'},
{'=', '+'},
{'[', '{'},
// 0x30
{']', '}'},
{'\\', '|'},
{},
{';', ':'},
// 0x34
{'\'', '"'},
{'`', '~'},
{',', '<'},
{'.', '>'},
// 0x38
{'/', '?'},
{},
{},
{},
// 0x3c
{},
{},
{},
{},
// 0x40
{},
{},
{},
{},
// 0x44
{},
{},
{},
{},
// 0x48
{},
{},
{},
{},
// 0x4c
{},
{},
{},
{},
// 0x50
{},
{},
{},
{},
// 0x54
{'/', 0},
{'*', 0},
{'-', 0},
{'+', 0},
// 0x58
{},
{'1', 0},
{'2', 0},
{'3', 0},
// 0x5c
{'4', 0},
{'5', 0},
{'6', 0},
{'7', 0},
// 0x60
{'8', 0},
{'9', 0},
{'0', 0},
{'.', 0},
};
} // namespace
Keyboard::Keyboard()
: any_events_received_(false),
stateful_caps_lock_(false),
left_shift_(false),
right_shift_(false),
left_alt_(false),
right_alt_(false),
left_ctrl_(false),
right_ctrl_(false),
last_event_() {}
bool Keyboard::ConsumeEvent(KeyEvent event) {
if (!event.has_type()) {
return false;
}
if (!event.has_key()) {
return false;
}
// Check if the time sequence of the events is correct.
last_event_ = std::move(event);
any_events_received_ = true;
const Key& key = last_event_.key();
const KeyEventType& event_type = last_event_.type();
switch (event_type) {
// For modifier keys, a SYNC is the same as a press.
case KeyEventType::SYNC:
switch (key) {
case Key::CAPS_LOCK:
stateful_caps_lock_ = true;
break;
case Key::LEFT_ALT:
left_alt_ = true;
break;
case Key::LEFT_CTRL:
left_ctrl_ = true;
break;
case Key::LEFT_SHIFT:
left_shift_ = true;
break;
case Key::RIGHT_ALT:
right_alt_ = true;
break;
case Key::RIGHT_CTRL:
right_ctrl_ = true;
break;
case Key::RIGHT_SHIFT:
right_shift_ = true;
break;
default:
// no-op
break;
}
break;
case KeyEventType::PRESSED:
switch (key) {
case Key::CAPS_LOCK:
stateful_caps_lock_ = !stateful_caps_lock_;
break;
case Key::LEFT_ALT:
left_alt_ = true;
break;
case Key::LEFT_CTRL:
left_ctrl_ = true;
break;
case Key::LEFT_SHIFT:
left_shift_ = true;
break;
case Key::RIGHT_ALT:
right_alt_ = true;
break;
case Key::RIGHT_CTRL:
right_ctrl_ = true;
break;
case Key::RIGHT_SHIFT:
right_shift_ = true;
break;
default:
// No-op
break;
}
break;
case KeyEventType::RELEASED:
switch (key) {
case Key::CAPS_LOCK:
// No-op.
break;
case Key::LEFT_ALT:
left_alt_ = false;
break;
case Key::LEFT_CTRL:
left_ctrl_ = false;
break;
case Key::LEFT_SHIFT:
left_shift_ = false;
break;
case Key::RIGHT_ALT:
right_alt_ = false;
break;
case Key::RIGHT_CTRL:
right_ctrl_ = false;
break;
case Key::RIGHT_SHIFT:
right_shift_ = false;
break;
default:
// No-op
break;
}
break;
case KeyEventType::CANCEL:
// No-op?
break;
default:
// No-op
break;
}
return false;
}
bool Keyboard::IsShift() {
return left_shift_ | right_shift_ | stateful_caps_lock_;
}
bool Keyboard::IsKeys() {
return (static_cast<uint64_t>(last_event_.key()) & 0xFFFF0000) == 0x00070000;
}
uint32_t Keyboard::Modifiers() {
return kModifierNone + (kModifierLeftShift * left_shift_) +
(kModifierLeftAlt * left_alt_) + (kModifierLeftControl * left_ctrl_) +
(kModifierRightShift * right_shift_) +
(kModifierRightAlt * right_alt_) +
(kModifierRightControl * right_ctrl_) +
(kModifierCapsLock * stateful_caps_lock_);
}
uint32_t Keyboard::LastCodePoint() {
static const int qwerty_map_size =
sizeof(QWERTY_TO_CODE_POINTS) / sizeof(QWERTY_TO_CODE_POINTS[0]);
if (!IsKeys()) {
return 0;
}
const auto usage = LastHIDUsage();
if (usage < qwerty_map_size) {
return QWERTY_TO_CODE_POINTS[usage][IsShift() & 1];
}
// Any other keys don't have a code point.
return 0;
}
uint32_t Keyboard::LastHIDUsage() {
return static_cast<uint64_t>(last_event_.key()) & 0xFFFF;
}
} // namespace flutter_runner

View File

@ -0,0 +1,63 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_KEYBOARD_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_KEYBOARD_H_
#include <fuchsia/ui/input3/cpp/fidl.h>
namespace flutter_runner {
// Keyboard handles the keyboard signals from fuchsia.ui.input3. Specifically,
// input3 has no notion of a code point, and does not track stateful versions
// of the modifier keys.
class Keyboard final {
public:
explicit Keyboard();
// Consumes the given keyboard event. Keyboard will adjust the modifier
// state based on the info given in the event. Returns true if the event has
// been integrated into the internal state successfully, or false otherwise.
bool ConsumeEvent(fuchsia::ui::input3::KeyEvent event);
// Gets the currently active modifier keys.
uint32_t Modifiers();
// Gets the last encountered code point. The reported code point depends on
// the state of the modifier keys.
uint32_t LastCodePoint();
// Gets the last encountered HID usage.
uint32_t LastHIDUsage();
private:
// Return true if any level shift is active.
bool IsShift();
// Returns true if the last key event was about a key that may have a code
// point associated.
bool IsKeys();
// Set to false until any event is received.
bool any_events_received_ : 1;
// The flags below show the state of the keyboard modifiers after the last
// event has been processed. Stateful keys remain in the same state after
// a release and require an additional press to toggle.
bool stateful_caps_lock_ : 1;
bool left_shift_ : 1;
bool right_shift_ : 1;
bool left_alt_ : 1;
bool right_alt_ : 1;
bool left_ctrl_ : 1;
bool right_ctrl_ : 1;
// The last received key event. If any_events_received_ is not set, this is
// not valid.
fuchsia::ui::input3::KeyEvent last_event_;
};
} // namespace flutter_runner
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_KEYBOARD_H_

View File

@ -0,0 +1,172 @@
// 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.
// Testing the stateful Fuchsia Input3 keyboard interactions. This test case
// is not intended to be exhaustive: it is intended to capture the tests that
// demonstrate how we think Input3 interaction should work, and possibly
// regression tests if we catch some behavior that needs to be guarded long
// term. Pragmatically, this should be enough to ensure no specific bug
// happens twice.
#include "flutter/shell/platform/fuchsia/flutter/keyboard.h"
#include <fuchsia/input/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/input3/cpp/fidl.h>
#include <gtest/gtest.h>
#include <zircon/time.h>
#include <vector>
namespace flutter_runner {
namespace {
using fuchsia::input::Key;
using fuchsia::ui::input::kModifierCapsLock;
using fuchsia::ui::input::kModifierLeftAlt;
using fuchsia::ui::input::kModifierLeftControl;
using fuchsia::ui::input::kModifierLeftShift;
using fuchsia::ui::input::kModifierNone;
using fuchsia::ui::input::kModifierRightAlt;
using fuchsia::ui::input::kModifierRightControl;
using fuchsia::ui::input::kModifierRightShift;
using fuchsia::ui::input3::KeyEvent;
using fuchsia::ui::input3::KeyEventType;
class KeyboardTest : public testing::Test {
protected:
static void SetUpTestCase() { testing::Test::SetUpTestCase(); }
// Creates a new key event for testing.
KeyEvent NewKeyEvent(KeyEventType event_type, Key key) {
KeyEvent event;
// Assume events are delivered with correct timing.
event.set_timestamp(++timestamp_);
event.set_type(event_type);
event.set_key(key);
return event;
}
// Makes the keyboard consume all the provided `events`. The end state of
// the keyboard is as if all of the specified events happened between the
// start state of the keyboard and its end state.
void ConsumeEvents(Keyboard* keyboard, const std::vector<KeyEvent>& events) {
for (const auto& event : events) {
KeyEvent e;
event.Clone(&e);
keyboard->ConsumeEvent(std::move(e));
}
}
// Converts a pressed key to usage value.
uint32_t ToUsage(Key key) { return static_cast<uint64_t>(key) & 0xFFFF; }
private:
zx_time_t timestamp_ = 0;
};
// This test checks that if a caps lock has been pressed when we didn't have
// focus, the effect of caps lock remains. Only this first test case is
// commented to explain how the test case works.
TEST_F(KeyboardTest, CapsLockSync) {
// Place the key events since the beginning of time into `keys`.
std::vector<KeyEvent> keys;
keys.emplace_back(NewKeyEvent(KeyEventType::SYNC, Key::CAPS_LOCK));
// Replay them on the keyboard.
Keyboard keyboard;
ConsumeEvents(&keyboard, keys);
// Verify the state of the keyboard's public API:
// - check that the key sync had no code point (it was a caps lock press).
// - check that the registered usage was that of caps lock.
// - check that the net effect is that the caps lock modifier is locked
// active.
EXPECT_EQ(0u, keyboard.LastCodePoint());
EXPECT_EQ(ToUsage(Key::CAPS_LOCK), keyboard.LastHIDUsage());
EXPECT_EQ(kModifierCapsLock, keyboard.Modifiers());
}
TEST_F(KeyboardTest, CapsLockPress) {
std::vector<KeyEvent> keys;
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::CAPS_LOCK));
Keyboard keyboard;
ConsumeEvents(&keyboard, keys);
EXPECT_EQ(0u, keyboard.LastCodePoint());
EXPECT_EQ(ToUsage(Key::CAPS_LOCK), keyboard.LastHIDUsage());
EXPECT_EQ(kModifierCapsLock, keyboard.Modifiers());
}
TEST_F(KeyboardTest, CapsLockPressRelease) {
std::vector<KeyEvent> keys;
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::CAPS_LOCK));
keys.emplace_back(NewKeyEvent(KeyEventType::RELEASED, Key::CAPS_LOCK));
Keyboard keyboard;
ConsumeEvents(&keyboard, keys);
EXPECT_EQ(0u, keyboard.LastCodePoint());
EXPECT_EQ(ToUsage(Key::CAPS_LOCK), keyboard.LastHIDUsage());
EXPECT_EQ(kModifierCapsLock, keyboard.Modifiers());
}
TEST_F(KeyboardTest, ShiftA) {
std::vector<KeyEvent> keys;
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::LEFT_SHIFT));
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::A));
Keyboard keyboard;
ConsumeEvents(&keyboard, keys);
EXPECT_EQ(static_cast<uint32_t>('A'), keyboard.LastCodePoint());
EXPECT_EQ(ToUsage(Key::A), keyboard.LastHIDUsage());
EXPECT_EQ(kModifierLeftShift, keyboard.Modifiers());
}
TEST_F(KeyboardTest, ShiftAWithRelease) {
std::vector<KeyEvent> keys;
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::LEFT_SHIFT));
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::A));
keys.emplace_back(NewKeyEvent(KeyEventType::RELEASED, Key::A));
Keyboard keyboard;
ConsumeEvents(&keyboard, keys);
EXPECT_EQ(static_cast<uint32_t>('A'), keyboard.LastCodePoint());
EXPECT_EQ(ToUsage(Key::A), keyboard.LastHIDUsage());
EXPECT_EQ(kModifierLeftShift, keyboard.Modifiers());
}
TEST_F(KeyboardTest, ShiftAWithReleaseShift) {
std::vector<KeyEvent> keys;
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::LEFT_SHIFT));
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::A));
keys.emplace_back(NewKeyEvent(KeyEventType::RELEASED, Key::LEFT_SHIFT));
keys.emplace_back(NewKeyEvent(KeyEventType::RELEASED, Key::A));
Keyboard keyboard;
ConsumeEvents(&keyboard, keys);
EXPECT_EQ(static_cast<uint32_t>('a'), keyboard.LastCodePoint());
EXPECT_EQ(ToUsage(Key::A), keyboard.LastHIDUsage());
EXPECT_EQ(kModifierNone, keyboard.Modifiers());
}
TEST_F(KeyboardTest, LowcaseA) {
std::vector<KeyEvent> keys;
keys.emplace_back(NewKeyEvent(KeyEventType::PRESSED, Key::A));
keys.emplace_back(NewKeyEvent(KeyEventType::RELEASED, Key::A));
Keyboard keyboard;
ConsumeEvents(&keyboard, keys);
EXPECT_EQ(static_cast<uint32_t>('a'), keyboard.LastCodePoint());
EXPECT_EQ(ToUsage(Key::A), keyboard.LastHIDUsage());
EXPECT_EQ(kModifierNone, keyboard.Modifiers());
}
} // namespace
} // namespace flutter_runner

View File

@ -40,13 +40,13 @@ template <class T>
void SetInterfaceErrorHandler(fidl::InterfacePtr<T>& interface,
std::string name) {
interface.set_error_handler([name](zx_status_t status) {
FML_LOG(ERROR) << "Interface error on: " << name;
FML_LOG(ERROR) << "Interface error on: " << name << "status: " << status;
});
}
template <class T>
void SetInterfaceErrorHandler(fidl::Binding<T>& binding, std::string name) {
binding.set_error_handler([name](zx_status_t status) {
FML_LOG(ERROR) << "Interface error on: " << name;
FML_LOG(ERROR) << "Interface error on: " << name << ", status: " << status;
});
}
@ -61,6 +61,8 @@ PlatformView::PlatformView(
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
session_listener_request,
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser,
fidl::InterfaceRequest<fuchsia::ui::input3::KeyboardListener>
keyboard_listener_request,
fit::closure session_listener_error_callback,
OnEnableWireframe wireframe_enabled_callback,
OnCreateView on_create_view_callback,
@ -85,13 +87,16 @@ PlatformView::PlatformView(
external_view_embedder_(external_view_embedder),
ime_client_(this),
vsync_offset_(std::move(vsync_offset)),
vsync_event_handle_(vsync_event_handle) {
vsync_event_handle_(vsync_event_handle),
keyboard_listener_binding_(this, std::move(keyboard_listener_request)) {
// Register all error handlers.
SetInterfaceErrorHandler(session_listener_binding_, "SessionListener");
SetInterfaceErrorHandler(ime_, "Input Method Editor");
SetInterfaceErrorHandler(text_sync_service_, "Text Sync Service");
SetInterfaceErrorHandler(parent_environment_service_provider_,
"Parent Environment Service Provider");
SetInterfaceErrorHandler(keyboard_listener_binding_,
"KeyboardListener Service");
// Access the IME service.
parent_environment_service_provider_ =
parent_environment_service_provider_handle.Bind();
@ -174,12 +179,6 @@ void PlatformView::DidUpdateState(
);
last_text_state_ =
std::make_unique<fuchsia::ui::input::TextInputState>(state);
// Handle keyboard input events for HID keys only.
// TODO(SCN-1189): Are we done here?
if (input_event && input_event->keyboard().hid_usage != 0) {
OnHandleKeyboardEvent(input_event->keyboard());
}
}
// |fuchsia::ui::input::InputMethodEditorClient|
@ -308,7 +307,9 @@ void PlatformView::OnScenicEvent(
break;
}
case fuchsia::ui::input::InputEvent::Tag::kKeyboard: {
OnHandleKeyboardEvent(event.input().keyboard());
// All devices should receive key events via input3.KeyboardListener
// instead.
FML_LOG(WARNING) << "Keyboard event from Scenic: ignored";
break;
}
case fuchsia::ui::input::InputEvent::Tag::Invalid: {
@ -501,31 +502,40 @@ bool PlatformView::OnHandlePointerEvent(
return true;
}
bool PlatformView::OnHandleKeyboardEvent(
const fuchsia::ui::input::KeyboardEvent& keyboard) {
// |fuchsia::ui:input3::KeyboardListener|
void PlatformView::OnKeyEvent(
fuchsia::ui::input3::KeyEvent key_event,
fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback callback) {
const char* type = nullptr;
if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::PRESSED) {
type = "keydown";
} else if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::REPEAT) {
type = "keydown"; // TODO change this to keyrepeat
} else if (keyboard.phase ==
fuchsia::ui::input::KeyboardEventPhase::RELEASED) {
type = "keyup";
switch (key_event.type()) {
case fuchsia::ui::input3::KeyEventType::PRESSED:
type = "keydown";
break;
case fuchsia::ui::input3::KeyEventType::RELEASED:
type = "keyup";
break;
case fuchsia::ui::input3::KeyEventType::SYNC:
// What, if anything, should happen here?
case fuchsia::ui::input3::KeyEventType::CANCEL:
// What, if anything, should happen here?
default:
break;
}
if (type == nullptr) {
FML_DLOG(ERROR) << "Unknown key event phase.";
return false;
callback(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED);
return;
}
keyboard_.ConsumeEvent(std::move(key_event));
rapidjson::Document document;
auto& allocator = document.GetAllocator();
document.SetObject();
document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator);
document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator);
document.AddMember("hidUsage", keyboard.hid_usage, allocator);
document.AddMember("codePoint", keyboard.code_point, allocator);
document.AddMember("modifiers", keyboard.modifiers, allocator);
document.AddMember("hidUsage", keyboard_.LastHIDUsage(), allocator);
document.AddMember("codePoint", keyboard_.LastCodePoint(), allocator);
document.AddMember("modifiers", keyboard_.Modifiers(), allocator);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
@ -536,8 +546,7 @@ bool PlatformView::OnHandleKeyboardEvent(
std::vector<uint8_t>(data, data + buffer.GetSize()), // data
nullptr) // response
);
return true;
callback(fuchsia::ui::input3::KeyEventStatus::HANDLED);
}
bool PlatformView::OnHandleFocusEvent(

View File

@ -6,6 +6,7 @@
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_PLATFORM_VIEW_H_
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/input3/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fit/function.h>
@ -19,6 +20,7 @@
#include "flutter/fml/time/time_delta.h"
#include "flutter/shell/common/platform_view.h"
#include "flutter/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h"
#include "flutter/shell/platform/fuchsia/flutter/keyboard.h"
#include "accessibility_bridge.h"
@ -45,6 +47,7 @@ using OnCreateSurface = fit::function<std::unique_ptr<flutter::Surface>()>;
class PlatformView final : public flutter::PlatformView,
public AccessibilityBridge::Delegate,
private fuchsia::ui::scenic::SessionListener,
private fuchsia::ui::input3::KeyboardListener,
private fuchsia::ui::input::InputMethodEditorClient {
public:
PlatformView(flutter::PlatformView::Delegate& delegate,
@ -57,6 +60,8 @@ class PlatformView final : public flutter::PlatformView,
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
session_listener_request,
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser,
fidl::InterfaceRequest<fuchsia::ui::input3::KeyboardListener>
keyboard_listener,
fit::closure on_session_listener_error_callback,
OnEnableWireframe wireframe_enabled_callback,
OnCreateView on_create_view_callback,
@ -87,6 +92,12 @@ class PlatformView final : public flutter::PlatformView,
private:
void RegisterPlatformMessageHandlers();
// |fuchsia.ui.input3.KeyboardListener|
// Called by the embedder every time there is a key event to process.
void OnKeyEvent(fuchsia::ui::input3::KeyEvent key_event,
fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback
callback) override;
// |fuchsia::ui::input::InputMethodEditorClient|
void DidUpdateState(
fuchsia::ui::input::TextInputState state,
@ -105,8 +116,6 @@ class PlatformView final : public flutter::PlatformView,
bool OnHandlePointerEvent(const fuchsia::ui::input::PointerEvent& pointer);
bool OnHandleKeyboardEvent(const fuchsia::ui::input::KeyboardEvent& keyboard);
bool OnHandleFocusEvent(const fuchsia::ui::input::FocusEvent& focus);
// Gets a new input method editor from the input connection. Run when both
@ -204,6 +213,13 @@ class PlatformView final : public flutter::PlatformView,
fml::TimeDelta vsync_offset_;
zx_handle_t vsync_event_handle_ = 0;
// The registered binding for serving the keyboard listener server endpoint.
fidl::Binding<fuchsia::ui::input3::KeyboardListener>
keyboard_listener_binding_;
// The keyboard translation for fuchsia.ui.input3.KeyEvent.
Keyboard keyboard_;
FML_DISALLOW_COPY_AND_ASSIGN(PlatformView);
};

View File

@ -162,6 +162,129 @@ class MockResponse : public flutter::PlatformMessageResponse {
MOCK_METHOD0(CompleteEmpty, void());
};
// Used to construct partial instances of PlatformView for testing. The
// PlatformView constructor has many parameters, not all of which need to
// be filled out for each test. The builder allows you to initialize only
// those that matter to your specific test. Not all builder methods are
// provided: if you find some that are missing, feel free to add them.
class PlatformViewBuilder {
public:
PlatformViewBuilder(flutter::PlatformView::Delegate& delegate,
flutter::TaskRunners task_runners,
std::shared_ptr<sys::ServiceDirectory> runner_services)
: delegate_(delegate),
debug_label_("test_platform_view"),
view_ref_(fuchsia::ui::views::ViewRef()),
task_runners_(task_runners),
runner_services_(runner_services) {}
// Add builder methods as required.
PlatformViewBuilder& SetServiceProvider(
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> service_provider) {
parent_environment_service_provider_ = std::move(service_provider);
return *this;
}
PlatformViewBuilder& SetFocuser(
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser) {
focuser_ = std::move(focuser);
return *this;
}
PlatformViewBuilder& SetDestroyViewCallback(OnDestroyView callback) {
on_destroy_view_callback_ = std::move(callback);
return *this;
}
PlatformViewBuilder& SetUpdateViewCallback(OnUpdateView callback) {
on_update_view_callback_ = std::move(callback);
return *this;
}
PlatformViewBuilder& SetEnableWireframeCallback(OnEnableWireframe callback) {
wireframe_enabled_callback_ = std::move(callback);
return *this;
}
PlatformViewBuilder& SetCreateViewCallback(OnCreateView callback) {
on_create_view_callback_ = std::move(callback);
return *this;
}
PlatformViewBuilder& SetSessionListenerRequest(
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> request) {
session_listener_request_ = std::move(request);
return *this;
}
PlatformViewBuilder& SetCreateSurfaceCallback(OnCreateSurface callback) {
on_create_surface_callback_ = std::move(callback);
return *this;
}
PlatformViewBuilder& SetViewEmbedder(
std::shared_ptr<flutter::ExternalViewEmbedder> embedder) {
view_embedder_ = embedder;
return *this;
}
PlatformViewBuilder& SetKeyboardListener(
fidl::InterfaceRequest<fuchsia::ui::input3::KeyboardListener> listener) {
keyboard_listener_ = std::move(listener);
return *this;
}
// Once Build is called, the instance is no longer usable.
PlatformView Build() {
EXPECT_EQ(false, built_)
<< "Build() was already called, this buider is good for one use only.";
built_ = true;
return PlatformView(delegate_, debug_label_, std::move(view_ref_),
task_runners_, runner_services_,
std::move(parent_environment_service_provider_),
std::move(session_listener_request_),
std::move(focuser_), std::move(keyboard_listener_),
std::move(on_session_listener_error_callback_),
std::move(wireframe_enabled_callback_),
std::move(on_create_view_callback_),
std::move(on_update_view_callback_),
std::move(on_destroy_view_callback_),
std::move(on_create_surface_callback_), view_embedder_,
std::move(vsync_offset_), vsync_event_handle_);
}
private:
PlatformViewBuilder() = delete;
bool built_{false};
// Required elements. Make sure to initialize them.
flutter::PlatformView::Delegate& delegate_;
std::string debug_label_;
fuchsia::ui::views::ViewRef view_ref_;
flutter::TaskRunners task_runners_;
std::shared_ptr<sys::ServiceDirectory> runner_services_{nullptr};
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider>
parent_environment_service_provider_{nullptr};
// Optional elements.
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
session_listener_request_{nullptr};
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser_{nullptr};
fidl::InterfaceRequest<fuchsia::ui::input3::KeyboardListener>
keyboard_listener_{nullptr};
fit::closure on_session_listener_error_callback_{nullptr};
OnEnableWireframe wireframe_enabled_callback_{nullptr};
OnCreateView on_create_view_callback_{nullptr};
OnUpdateView on_update_view_callback_{nullptr};
OnDestroyView on_destroy_view_callback_{nullptr};
OnCreateSurface on_create_surface_callback_{nullptr};
std::shared_ptr<flutter::ExternalViewEmbedder> view_embedder_{nullptr};
fml::TimeDelta vsync_offset_{fml::TimeDelta::Zero()};
zx_handle_t vsync_event_handle_{ZX_HANDLE_INVALID};
};
} // namespace
class PlatformViewTests : public ::testing::Test {
@ -175,9 +298,25 @@ class PlatformViewTests : public ::testing::Test {
loop_.ResetQuit();
}
fuchsia::ui::input3::KeyEvent MakeEvent(
fuchsia::ui::input3::KeyEventType event_type,
std::optional<fuchsia::ui::input3::Modifiers> modifiers,
fuchsia::input::Key key) {
fuchsia::ui::input3::KeyEvent event;
event.set_timestamp(++event_timestamp_);
event.set_type(event_type);
if (modifiers.has_value()) {
event.set_modifiers(modifiers.value());
}
event.set_key(key);
return event;
}
private:
async::Loop loop_;
uint64_t event_timestamp_{42};
FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewTests);
};
@ -206,25 +345,12 @@ TEST_F(PlatformViewTests, CreateSurfaceTest) {
"PlatformViewTest", view_embedder, gr_context.get());
};
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
CreateSurfaceCallback, // on_create_surface_callback,
view_embedder, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetCreateSurfaceCallback(CreateSurfaceCallback)
.SetViewEmbedder(view_embedder)
.Build();
platform_view.NotifyCreated();
RunLoopUntilIdle();
@ -251,25 +377,11 @@ TEST_F(PlatformViewTests, SetViewportMetrics) {
sys::testing::ServiceDirectoryProvider services_provider(dispatcher());
flutter::TaskRunners task_runners("test_runners", nullptr, nullptr, nullptr,
nullptr);
flutter_runner::PlatformView platform_view(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
session_listener.NewRequest(), // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetSessionListenerRequest(session_listener.NewRequest())
.Build();
RunLoopUntilIdle();
EXPECT_EQ(delegate.metrics(), flutter::ViewportMetrics());
@ -384,25 +496,13 @@ TEST_F(PlatformViewTests, ChangesAccessibilitySettings) {
EXPECT_FALSE(delegate.semantics_enabled());
EXPECT_EQ(delegate.semantics_features(), 0);
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(
delegate, // delegate
std::move(task_runners), // task_runners
services_provider.service_directory() // runner_services
)
.Build();
RunLoopUntilIdle();
@ -435,25 +535,11 @@ TEST_F(PlatformViewTests, EnableWireframeTest) {
wireframe_enabled = should_enable;
};
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
EnableWireframeCallback, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetEnableWireframeCallback(EnableWireframeCallback)
.Build();
// Cast platform_view to its base view so we can have access to the public
// "HandlePlatformMessage" function.
@ -497,25 +583,11 @@ TEST_F(PlatformViewTests, CreateViewTest) {
int64_t view_id, bool hit_testable,
bool focusable) { create_view_called = true; };
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
CreateViewCallback, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetCreateViewCallback(CreateViewCallback)
.Build();
// Cast platform_view to its base view so we can have access to the public
// "HandlePlatformMessage" function.
@ -561,25 +633,11 @@ TEST_F(PlatformViewTests, UpdateViewTest) {
int64_t view_id, bool hit_testable,
bool focusable) { update_view_called = true; };
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
UpdateViewCallback, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetUpdateViewCallback(UpdateViewCallback)
.Build();
// Cast platform_view to its base view so we can have access to the public
// "HandlePlatformMessage" function.
@ -625,25 +683,11 @@ TEST_F(PlatformViewTests, DestroyViewTest) {
destroy_view_called = true;
};
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
DestroyViewCallback, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetDestroyViewCallback(DestroyViewCallback)
.Build();
// Cast platform_view to its base view so we can have access to the public
// "HandlePlatformMessage" function.
@ -686,25 +730,12 @@ TEST_F(PlatformViewTests, ViewEventsTest) {
flutter_runner::CreateFMLTaskRunner(async_get_default_dispatcher()),
nullptr);
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
session_listener.NewRequest(), // session_listener_request
nullptr, // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetSessionListenerRequest(session_listener.NewRequest())
.Build();
RunLoopUntilIdle();
// ViewConnected event.
@ -769,25 +800,11 @@ TEST_F(PlatformViewTests, RequestFocusTest) {
fidl::BindingSet<fuchsia::ui::views::Focuser> focuser_bindings;
auto focuser_handle = focuser_bindings.AddBinding(&mock_focuser);
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
std::move(focuser_handle), // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetFocuser(std::move(focuser_handle))
.Build();
// Cast platform_view to its base view so we can have access to the public
// "HandlePlatformMessage" function.
@ -846,25 +863,11 @@ TEST_F(PlatformViewTests, RequestFocusFailTest) {
fidl::BindingSet<fuchsia::ui::views::Focuser> focuser_bindings;
auto focuser_handle = focuser_bindings.AddBinding(&mock_focuser);
auto platform_view = flutter_runner::PlatformView(
delegate, // delegate
"test_platform_view", // label
fuchsia::ui::views::ViewRef(), // view_ref
std::move(task_runners), // task_runners
services_provider.service_directory(), // runner_services
nullptr, // parent_environment_service_provider_handle
nullptr, // session_listener_request
std::move(focuser_handle), // focuser,
nullptr, // on_session_listener_error_callback
nullptr, // on_enable_wireframe_callback,
nullptr, // on_create_view_callback,
nullptr, // on_update_view_callback,
nullptr, // on_destroy_view_callback,
nullptr, // on_create_surface_callback,
nullptr, // external_view_embedder,
fml::TimeDelta::Zero(), // vsync_offset
ZX_HANDLE_INVALID // vsync_event_handle
);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetFocuser(std::move(focuser_handle))
.Build();
// Cast platform_view to its base view so we can have access to the public
// "HandlePlatformMessage" function.
@ -916,4 +919,88 @@ TEST_F(PlatformViewTests, RequestFocusFailTest) {
EXPECT_EQ(out.str(), result);
}
struct EventFlow {
fuchsia::ui::input3::KeyEvent event;
fuchsia::ui::input3::KeyEventStatus expected_key_event_status;
std::string expected_platform_message;
};
// Makes sure that OnKeyEvent is dispatched as a platform message.
TEST_F(PlatformViewTests, OnKeyEvent) {
sys::testing::ServiceDirectoryProvider services_provider(dispatcher());
MockPlatformViewDelegate delegate;
flutter::TaskRunners task_runners =
flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr);
fidl::InterfacePtr<fuchsia::ui::input3::KeyboardListener> keyboard_listener;
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetKeyboardListener(keyboard_listener.NewRequest(dispatcher()))
.Build();
using fuchsia::input::Key;
using fuchsia::ui::input3::KeyEvent;
using fuchsia::ui::input3::KeyEventStatus;
using fuchsia::ui::input3::KeyEventType;
using fuchsia::ui::input3::Modifiers;
std::vector<EventFlow> events;
// Press A. Get 'a'.
events.emplace_back(EventFlow{
MakeEvent(KeyEventType::PRESSED, std::nullopt, Key::A),
KeyEventStatus::HANDLED,
R"({"type":"keydown","keymap":"fuchsia","hidUsage":4,"codePoint":97,"modifiers":0})",
});
// Release A. Get 'a' release.
events.emplace_back(EventFlow{
MakeEvent(KeyEventType::RELEASED, std::nullopt, Key::A),
KeyEventStatus::HANDLED,
R"({"type":"keyup","keymap":"fuchsia","hidUsage":4,"codePoint":97,"modifiers":0})",
});
// Press CAPS_LOCK. Modifier now active.
events.emplace_back(EventFlow{
MakeEvent(KeyEventType::PRESSED, Modifiers::CAPS_LOCK, Key::CAPS_LOCK),
KeyEventStatus::HANDLED,
R"({"type":"keydown","keymap":"fuchsia","hidUsage":57,"codePoint":0,"modifiers":1})",
});
// Pres A. Get 'A'.
events.emplace_back(EventFlow{
MakeEvent(KeyEventType::PRESSED, std::nullopt, Key::A),
KeyEventStatus::HANDLED,
R"({"type":"keydown","keymap":"fuchsia","hidUsage":4,"codePoint":65,"modifiers":1})",
});
// Release CAPS_LOCK.
events.emplace_back(EventFlow{
MakeEvent(KeyEventType::RELEASED, Modifiers::CAPS_LOCK, Key::CAPS_LOCK),
KeyEventStatus::HANDLED,
R"({"type":"keyup","keymap":"fuchsia","hidUsage":57,"codePoint":0,"modifiers":1})",
});
// Press A again. This time get 'A'.
// CAPS_LOCK is latched active even if it was just released.
events.emplace_back(EventFlow{
MakeEvent(KeyEventType::PRESSED, std::nullopt, Key::A),
KeyEventStatus::HANDLED,
R"({"type":"keydown","keymap":"fuchsia","hidUsage":4,"codePoint":65,"modifiers":1})",
});
for (const auto& event : events) {
KeyEvent e;
event.event.Clone(&e);
fuchsia::ui::input3::KeyEventStatus key_event_status;
keyboard_listener->OnKeyEvent(
std::move(e),
[&key_event_status](fuchsia::ui::input3::KeyEventStatus status) {
key_event_status = status;
});
RunLoopUntilIdle();
const std::vector<uint8_t> data = delegate.message()->data();
const std::string message = std::string(data.begin(), data.end());
EXPECT_EQ(event.expected_platform_message, message);
EXPECT_EQ(key_event_status, event.expected_key_event_status);
}
}
} // namespace flutter_runner::testing

View File

@ -12,15 +12,15 @@
"services": [
"fuchsia.accessibility.semantics.SemanticsManager",
"fuchsia.deprecatedtimezone.Timezone",
"fuchsia.fonts.Provider",
"fuchsia.intl.PropertyProvider",
"fuchsia.logger.LogSink",
"fuchsia.process.Launcher",
"fuchsia.settings.Intl",
"fuchsia.sysmem.Allocator",
"fuchsia.tracing.provider.Registry",
"fuchsia.vulkan.loader.Loader",
"fuchsia.intl.PropertyProvider",
"fuchsia.fonts.Provider"
"fuchsia.ui.input3.Keyboard",
"fuchsia.vulkan.loader.Loader"
]
}
}