blob: ae5b39b8b52e4fcece915d0af1b9ddc4c45504d5 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fidl/fuchsia.ui.display.singleton/cpp/fidl.h>
#include <fidl/fuchsia.ui.focus/cpp/fidl.h>
#include <fidl/fuchsia.ui.input3/cpp/fidl.h>
#include <fidl/fuchsia.ui.test.conformance/cpp/fidl.h>
#include <fidl/fuchsia.ui.test.input/cpp/fidl.h>
#include <fidl/fuchsia.ui.test.scene/cpp/fidl.h>
#include <fidl/fuchsia.ui.views/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fidl/cpp/channel.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/view_creation_tokens.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <src/lib/fsl/handles/object_info.h>
#include <src/ui/testing/util/fidl_cpp_helpers.h>
#include <src/ui/tests/conformance_input_tests/conformance-test-base.h>
#include <zxtest/zxtest.h>
namespace ui_conformance_testing {
const std::string PUPPET_UNDER_TEST_FACTORY_SERVICE = "/svc/puppet-under-test-factory-service";
const std::string AUXILIARY_PUPPET_FACTORY_SERVICE = "/svc/auxiliary-puppet-factory-service";
namespace futi = fuchsia_ui_test_input;
namespace futc = fuchsia_ui_test_conformance;
namespace fui = fuchsia_ui_input3;
namespace fuv = fuchsia_ui_views;
namespace fuf = fuchsia_ui_focus;
class FocusListener : public fidl::Server<fuf::FocusChainListener> {
public:
// |fuchsia_ui_focus::FocusChainListener|
void OnFocusChange(OnFocusChangeRequest& request,
OnFocusChangeCompleter::Sync& completer) override {
focus_chain_updates_.push_back(std::move(request.focus_chain()));
completer.Reply();
}
fidl::ClientEnd<fuf::FocusChainListener> ServeAndGetClientEnd(async_dispatcher_t* dispatcher) {
auto [client_end, server_end] = fidl::Endpoints<fuf::FocusChainListener>::Create();
binding_.AddBinding(dispatcher, std::move(server_end), this, fidl::kIgnoreBindingClosure);
return std::move(client_end);
}
bool IsViewFocused(const fuv::ViewRef& view_ref) const {
if (focus_chain_updates_.empty()) {
return false;
}
const auto& last_focus_chain = focus_chain_updates_.back();
if (!last_focus_chain.focus_chain().has_value()) {
return false;
}
if (last_focus_chain.focus_chain()->empty()) {
return false;
}
// the new focus view store at the last slot.
return fsl::GetKoid(last_focus_chain.focus_chain()->back().reference().get()) ==
fsl::GetKoid(view_ref.reference().get());
}
const std::vector<fuf::FocusChain>& focus_chain_updates() const { return focus_chain_updates_; }
private:
fidl::ServerBindingGroup<fuf::FocusChainListener> binding_;
std::vector<fuf::FocusChain> focus_chain_updates_;
};
class KeyListener : public fidl::Server<futi::KeyboardInputListener> {
public:
// |fuchsia_ui_test_input::KeyboardInputListener|
void ReportTextInput(ReportTextInputRequest& request,
ReportTextInputCompleter::Sync& completer) override {
events_received_.push_back(std::move(request));
}
// |fuchsia_ui_test_input::KeyboardInputListener|
void ReportReady(ReportReadyCompleter::Sync& completer) override {
// Puppet factory create view already wait for keyboard ready.
}
fidl::ClientEnd<futi::KeyboardInputListener> ServeAndGetClientEnd(
async_dispatcher_t* dispatcher) {
auto [client_end, server_end] = fidl::Endpoints<futi::KeyboardInputListener>::Create();
binding_.AddBinding(dispatcher, std::move(server_end), this, fidl::kIgnoreBindingClosure);
return std::move(client_end);
}
const std::vector<futi::KeyboardInputListenerReportTextInputRequest>& events_received() {
return events_received_;
}
void clear_events() { events_received_.clear(); }
private:
fidl::ServerBindingGroup<futi::KeyboardInputListener> binding_;
std::vector<futi::KeyboardInputListenerReportTextInputRequest> events_received_;
};
// Holds resources associated with a single puppet instance.
struct KeyPuppet {
fidl::SyncClient<futc::Puppet> client;
KeyListener key_listener;
};
class KeyConformanceTest : public ui_conformance_test_base::ConformanceTest {
public:
~KeyConformanceTest() override = default;
void SetUp() override {
ui_conformance_test_base::ConformanceTest::SetUp();
// Register fake keyboard.
{
FX_LOGS(INFO) << "Connecting to input registry";
auto input_registry = ConnectSyncIntoRealm<futi::Registry>();
FX_LOGS(INFO) << "Registering fake keyboard";
auto [client_end, server_end] = fidl::Endpoints<futi::Keyboard>::Create();
futi::RegistryRegisterKeyboardAndGetDeviceInfoRequest request;
request.device(std::move(server_end));
auto res = input_registry->RegisterKeyboardAndGetDeviceInfo(std::move(request));
ZX_ASSERT_OK(res);
fake_keyboard_device_id_ = res->device_id().value();
fake_keyboard_ = fidl::SyncClient(std::move(client_end));
}
// Get display dimensions.
{
FX_LOGS(INFO) << "Reading display dimensions";
auto display_info = ConnectSyncIntoRealm<fuchsia_ui_display_singleton::Info>();
auto res = display_info->GetMetrics();
ZX_ASSERT_OK(res);
display_width_ = res.value().info().extent_in_px()->width();
display_height_ = res.value().info().extent_in_px()->height();
FX_LOGS(INFO) << "Received display dimensions (" << display_width_ << ", " << display_height_
<< ")";
}
// Get root view token.
{
FX_LOGS(INFO) << "Creating root view token";
auto controller = ConnectSyncIntoRealm<fuchsia_ui_test_scene::Controller>();
fuchsia_ui_test_scene::ControllerPresentClientViewRequest req;
auto [view_token, viewport_token] = scenic::cpp::ViewCreationTokenPair::New();
req.viewport_creation_token(std::move(viewport_token));
ZX_ASSERT_OK(controller->PresentClientView(std::move(req)));
root_view_token_ = std::move(view_token);
}
// Setup focus listener.
{
auto focus_registry = ConnectSyncIntoRealm<fuf::FocusChainListenerRegistry>();
auto focus_listener_client_end = focus_listener_.ServeAndGetClientEnd(dispatcher());
ZX_ASSERT_OK(focus_registry->Register({std::move(focus_listener_client_end)}));
}
}
void SimulateKeyEvent(std::string str) {
FX_LOGS(INFO) << "Requesting key event";
futi::KeyboardSimulateUsAsciiTextEntryRequest request;
request.text(std::move(str));
ZX_ASSERT_OK(fake_keyboard_->SimulateUsAsciiTextEntry(request));
FX_LOGS(INFO) << "Key event injected";
}
void ExpectEvent(const futi::KeyboardInputListenerReportTextInputRequest& event,
std::optional<std::string> text,
std::optional<fui::NonPrintableKey> non_printable) const {
ASSERT_EQ(text, event.text());
ASSERT_EQ(non_printable, event.non_printable());
ASSERT_EQ(fake_keyboard_device_id_, event.device_id());
}
protected:
int32_t display_width_as_int() const { return static_cast<int32_t>(display_width_); }
int32_t display_height_as_int() const { return static_cast<int32_t>(display_height_); }
fidl::SyncClient<futi::Keyboard> fake_keyboard_;
fuv::ViewCreationToken root_view_token_;
FocusListener focus_listener_;
uint32_t display_width_ = 0;
uint32_t display_height_ = 0;
uint32_t fake_keyboard_device_id_ = 0;
};
class SingleViewKeyConformanceTest : public KeyConformanceTest {
public:
~SingleViewKeyConformanceTest() override = default;
void SetUp() override {
KeyConformanceTest::SetUp();
{
FX_LOGS(INFO) << "Create puppet under test";
auto puppet_factory_connect =
component::Connect<futc::PuppetFactory>(PUPPET_UNDER_TEST_FACTORY_SERVICE);
ZX_ASSERT_OK(puppet_factory_connect);
puppet_factory_ = fidl::SyncClient(std::move(puppet_factory_connect.value()));
auto flatland = ConnectIntoRealm<fuchsia_ui_composition::Flatland>();
auto keyboard = ConnectIntoRealm<fui::Keyboard>();
auto [puppet_client_end, puppet_server_end] = fidl::Endpoints<futc::Puppet>::Create();
auto key_listener_client_end = puppet_.key_listener.ServeAndGetClientEnd(dispatcher());
puppet_.client = fidl::SyncClient(std::move(puppet_client_end));
futc::PuppetCreationArgs creation_args;
creation_args.server_end(std::move(puppet_server_end));
creation_args.view_token(std::move(root_view_token_));
creation_args.flatland_client(std::move(flatland));
creation_args.keyboard_client(std::move(keyboard));
creation_args.keyboard_listener(std::move(key_listener_client_end));
creation_args.device_pixel_ratio(DevicePixelRatio());
auto res = puppet_factory_->Create(std::move(creation_args));
ZX_ASSERT_OK(res);
ASSERT_EQ(res.value().result(), futc::Result::kSuccess);
auto puppet_view_ref = std::move(res.value().view_ref().value());
FX_LOGS(INFO) << "wait for focus";
RunLoopUntil([&]() { return focus_listener_.IsViewFocused(puppet_view_ref); });
}
}
protected:
KeyPuppet puppet_;
private:
fidl::SyncClient<futc::PuppetFactory> puppet_factory_;
};
TEST_F(SingleViewKeyConformanceTest, SimpleKeyPress) {
SimulateKeyEvent("Hello\nWorld!");
FX_LOGS(INFO) << "Wait for puppet to report key events";
RunLoopUntil([&]() { return puppet_.key_listener.events_received().size() >= 15; });
ASSERT_EQ(puppet_.key_listener.events_received().size(), 15u);
const auto& events = puppet_.key_listener.events_received();
ExpectEvent(events[0], std::nullopt, fui::NonPrintableKey::kShift);
// view reports value from key meaning so it is Shifted h (H).
ExpectEvent(events[1], "H", std::nullopt);
ExpectEvent(events[2], "e", std::nullopt);
ExpectEvent(events[3], "l", std::nullopt);
ExpectEvent(events[4], "l", std::nullopt);
ExpectEvent(events[5], "o", std::nullopt);
ExpectEvent(events[6], std::nullopt, fui::NonPrintableKey::kEnter);
ExpectEvent(events[7], std::nullopt, fui::NonPrintableKey::kShift);
ExpectEvent(events[8], "W", std::nullopt);
ExpectEvent(events[9], "o", std::nullopt);
ExpectEvent(events[10], "r", std::nullopt);
ExpectEvent(events[11], "l", std::nullopt);
ExpectEvent(events[12], "d", std::nullopt);
ExpectEvent(events[13], std::nullopt, fui::NonPrintableKey::kShift);
ExpectEvent(events[14], "!", std::nullopt);
}
class EmbeddedViewKeyConformanceTest : public KeyConformanceTest {
public:
~EmbeddedViewKeyConformanceTest() override = default;
void SetUp() override {
KeyConformanceTest::SetUp();
{
FX_LOGS(INFO) << "Create parent puppet";
auto puppet_factory_connect =
component::Connect<futc::PuppetFactory>(AUXILIARY_PUPPET_FACTORY_SERVICE);
ZX_ASSERT_OK(puppet_factory_connect);
parent_puppet_factory_ = fidl::SyncClient(std::move(puppet_factory_connect.value()));
auto flatland = ConnectIntoRealm<fuchsia_ui_composition::Flatland>();
auto keyboard = ConnectIntoRealm<fui::Keyboard>();
auto [puppet_client_end, puppet_server_end] = fidl::Endpoints<futc::Puppet>::Create();
auto key_listener_client_end = parent_puppet_.key_listener.ServeAndGetClientEnd(dispatcher());
auto [focuser_client_end, focuser_server_end] = fidl::Endpoints<fuv::Focuser>::Create();
parent_focuser_ = fidl::SyncClient(std::move(focuser_client_end));
parent_puppet_.client = fidl::SyncClient(std::move(puppet_client_end));
futc::PuppetCreationArgs creation_args;
creation_args.server_end(std::move(puppet_server_end));
creation_args.view_token(std::move(root_view_token_));
creation_args.flatland_client(std::move(flatland));
creation_args.keyboard_client(std::move(keyboard));
creation_args.keyboard_listener(std::move(key_listener_client_end));
creation_args.device_pixel_ratio(DevicePixelRatio());
creation_args.focuser(std::move(focuser_server_end));
auto res = parent_puppet_factory_->Create(std::move(creation_args));
ZX_ASSERT_OK(res);
ASSERT_EQ(res.value().result(), futc::Result::kSuccess);
parent_view_ref_ = std::move(res.value().view_ref().value());
}
// Create child viewport.
fuv::ViewCreationToken view_creation_token;
{
FX_LOGS(INFO) << "Creating child viewport";
const uint64_t kChildViewportId = 1u;
futc::ContentBounds bounds;
bounds.size() = {display_width_ / 2, display_height_ / 2};
bounds.origin() = {display_width_as_int() / 2, display_height_as_int() / 2};
futc::EmbeddedViewProperties properties({
.bounds = std::move(bounds),
});
futc::PuppetEmbedRemoteViewRequest embed_remote_view_request({
.id = kChildViewportId,
.properties = std::move(properties),
});
auto res = parent_puppet_.client->EmbedRemoteView(embed_remote_view_request);
ZX_ASSERT_OK(res);
view_creation_token = std::move(res->view_creation_token()->value());
}
// Create child view.
{
FX_LOGS(INFO) << "Creating child puppet";
auto puppet_factory_connect =
component::Connect<futc::PuppetFactory>(PUPPET_UNDER_TEST_FACTORY_SERVICE);
ZX_ASSERT_OK(puppet_factory_connect);
child_puppet_factory_ = fidl::SyncClient(std::move(puppet_factory_connect.value()));
auto flatland = ConnectIntoRealm<fuchsia_ui_composition::Flatland>();
auto keyboard = ConnectIntoRealm<fui::Keyboard>();
auto [puppet_client_end, puppet_server_end] = fidl::Endpoints<futc::Puppet>::Create();
auto key_listener_client_end = child_puppet_.key_listener.ServeAndGetClientEnd(dispatcher());
child_puppet_.client = fidl::SyncClient(std::move(puppet_client_end));
futc::PuppetCreationArgs creation_args;
creation_args.server_end(std::move(puppet_server_end));
creation_args.view_token(std::move(view_creation_token));
creation_args.flatland_client(std::move(flatland));
creation_args.keyboard_client(std::move(keyboard));
creation_args.keyboard_listener(std::move(key_listener_client_end));
creation_args.device_pixel_ratio(DevicePixelRatio());
auto res = child_puppet_factory_->Create(std::move(creation_args));
ZX_ASSERT_OK(res);
ASSERT_EQ(res.value().result(), futc::Result::kSuccess);
child_view_ref_ = std::move(res.value().view_ref().value());
}
}
protected:
KeyPuppet parent_puppet_;
fidl::SyncClient<fuv::Focuser> parent_focuser_;
fuv::ViewRef parent_view_ref_;
KeyPuppet child_puppet_;
fuv::ViewRef child_view_ref_;
private:
fidl::SyncClient<futc::PuppetFactory> parent_puppet_factory_;
fidl::SyncClient<futc::PuppetFactory> child_puppet_factory_;
};
TEST_F(EmbeddedViewKeyConformanceTest, KeyToFocusedView) {
// Default is focus on parent view.
{
FX_LOGS(INFO) << "wait for focus to parent";
RunLoopUntil([&]() { return focus_listener_.IsViewFocused(parent_view_ref_); });
// Inject key events, and expect parent view receives events.
SimulateKeyEvent("a");
FX_LOGS(INFO) << "Wait for parent puppet to report key events";
RunLoopUntil([&]() { return parent_puppet_.key_listener.events_received().size() >= 1; });
ASSERT_EQ(parent_puppet_.key_listener.events_received().size(), 1u);
const auto& events = parent_puppet_.key_listener.events_received();
ExpectEvent(events[0], "a", std::nullopt);
EXPECT_TRUE(child_puppet_.key_listener.events_received().empty());
parent_puppet_.key_listener.clear_events();
}
// Focus on child view.
{
auto child_view_ref = scenic::cpp::CloneViewRef(child_view_ref_);
ZX_ASSERT_OK(parent_focuser_->RequestFocus({std::move(child_view_ref)}));
FX_LOGS(INFO) << "wait for focus to child";
RunLoopUntil([&]() { return focus_listener_.IsViewFocused(child_view_ref_); });
// Inject key events, and expect child view receives events.
SimulateKeyEvent("b");
FX_LOGS(INFO) << "Wait for child puppet to report key events";
RunLoopUntil([&]() { return child_puppet_.key_listener.events_received().size() >= 1; });
ASSERT_EQ(child_puppet_.key_listener.events_received().size(), 1u);
const auto& events = child_puppet_.key_listener.events_received();
ExpectEvent(events[0], "b", std::nullopt);
EXPECT_TRUE(parent_puppet_.key_listener.events_received().empty());
child_puppet_.key_listener.clear_events();
}
// Focus back to parent view.
{
auto parent_view_ref = scenic::cpp::CloneViewRef(parent_view_ref_);
ZX_ASSERT_OK(parent_focuser_->RequestFocus({std::move(parent_view_ref)}));
FX_LOGS(INFO) << "wait for focus to parent";
RunLoopUntil([&]() { return focus_listener_.IsViewFocused(parent_view_ref_); });
// Inject key events, and expect parent view receives events.
SimulateKeyEvent("c");
FX_LOGS(INFO) << "Wait for parent puppet to report key events";
RunLoopUntil([&]() { return parent_puppet_.key_listener.events_received().size() >= 1; });
ASSERT_EQ(parent_puppet_.key_listener.events_received().size(), 1u);
const auto& events = parent_puppet_.key_listener.events_received();
ExpectEvent(events[0], "c", std::nullopt);
EXPECT_TRUE(child_puppet_.key_listener.events_received().empty());
parent_puppet_.key_listener.clear_events();
}
}
} // namespace ui_conformance_testing