blob: 8c0acbb792b5a13ecc51e8db5d207aeccf26fcf4 [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 <fuchsia/ui/display/singleton/cpp/fidl.h>
#include <fuchsia/ui/focus/cpp/fidl.h>
#include <fuchsia/ui/input3/cpp/fidl.h>
#include <fuchsia/ui/test/conformance/cpp/fidl.h>
#include <fuchsia/ui/test/input/cpp/fidl.h>
#include <fuchsia/ui/test/scene/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/view_creation_tokens.h>
#include <zircon/errors.h>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "src/lib/fsl/handles/object_info.h"
#include "src/ui/tests/conformance_input_tests/conformance-test-base.h"
namespace ui_conformance_testing {
const std::string PUPPET_UNDER_TEST_FACTORY_SERVICE = "puppet-under-test-factory-service";
const std::string AUXILIARY_PUPPET_FACTORY_SERVICE = "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 fuf::FocusChainListener {
public:
FocusListener() : binding_(this) {}
// |fuchsia::ui::focus::FocusChainListener|
void OnFocusChange(fuf::FocusChain focus_chain, OnFocusChangeCallback callback) override {
last_focus_chain_ = std::move(focus_chain);
callback();
}
// Returns a client end bound to this object.
fidl::InterfaceHandle<fuf::FocusChainListener> NewBinding() { return binding_.NewBinding(); }
bool IsViewFocused(const fuv::ViewRef& view_ref) const {
if (!last_focus_chain_) {
return false;
}
if (!last_focus_chain_->has_focus_chain()) {
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());
}
private:
fidl::Binding<fuf::FocusChainListener> binding_;
// Holds the most recent focus chain update received.
std::optional<fuchsia::ui::focus::FocusChain> last_focus_chain_;
};
class KeyListener : public futi::KeyboardInputListener {
public:
KeyListener() : binding_(this) {}
// |fuchsia::ui::test::input::KeyboardInputListener|
void ReportTextInput(futi::KeyboardInputListenerReportTextInputRequest request) override {
events_received_.push_back(std::move(request));
}
// |fuchsia::ui::test::input::KeyboardInputListener|
void ReportReady(futi::KeyboardInputListener::ReportReadyCallback callback) override {
// Puppet factory create view already wait for keyboard ready.
}
// Returns a client end bound to this object.
fidl::InterfaceHandle<futi::KeyboardInputListener> NewBinding() { return binding_.NewBinding(); }
const std::vector<futi::KeyboardInputListenerReportTextInputRequest>& events_received() {
return events_received_;
}
void clear_events() { events_received_.clear(); }
private:
fidl::Binding<futi::KeyboardInputListener> binding_;
std::vector<futi::KeyboardInputListenerReportTextInputRequest> events_received_;
};
// Holds resources associated with a single puppet instance.
struct KeyPuppet {
futc::PuppetSyncPtr puppet_ptr;
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";
futi::RegistryRegisterKeyboardRequest request;
request.set_device(fake_keyboard_.NewRequest());
ASSERT_EQ(input_registry->RegisterKeyboard(std::move(request)), ZX_OK);
}
// Get display dimensions.
{
FX_LOGS(INFO) << "Reading display dimensions";
auto display_info = ConnectSyncIntoRealm<fuchsia::ui::display::singleton::Info>();
fuchsia::ui::display::singleton::Metrics metrics;
ASSERT_EQ(display_info->GetMetrics(&metrics), ZX_OK);
display_width_ = metrics.extent_in_px().width;
display_height_ = metrics.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::ViewCreationTokenPair::New();
req.set_viewport_creation_token(std::move(viewport_token));
ASSERT_EQ(controller->PresentClientView(std::move(req)), ZX_OK);
root_view_token_ = std::move(view_token);
}
// Setup focus listener.
{
auto focus_registry = ConnectSyncIntoRealm<fuf::FocusChainListenerRegistry>();
ASSERT_EQ(focus_registry->Register(focus_listener_.NewBinding()), ZX_OK);
}
}
void SimulateKeyEvent(std::string str) {
FX_LOGS(INFO) << "Requesting key event";
futi::KeyboardSimulateUsAsciiTextEntryRequest request;
request.set_text(std::move(str));
fake_keyboard_->SimulateUsAsciiTextEntry(std::move(request));
FX_LOGS(INFO) << "Key event injected";
}
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_); }
futi::KeyboardSyncPtr fake_keyboard_;
fuv::ViewCreationToken root_view_token_;
FocusListener focus_listener_;
uint32_t display_width_ = 0;
uint32_t display_height_ = 0;
};
class SingleViewKeyConformanceTest : public KeyConformanceTest {
public:
~SingleViewKeyConformanceTest() override = default;
void SetUp() override {
KeyConformanceTest::SetUp();
{
FX_LOGS(INFO) << "Create puppet under test";
futc::PuppetFactorySyncPtr puppet_factory;
ASSERT_EQ(LocalServiceDirectory()->Connect(puppet_factory.NewRequest(),
PUPPET_UNDER_TEST_FACTORY_SERVICE),
ZX_OK);
futc::PuppetFactoryCreateResponse resp;
auto flatland = ConnectSyncIntoRealm<fuchsia::ui::composition::Flatland>();
auto keyboard = ConnectSyncIntoRealm<fui::Keyboard>();
futc::PuppetCreationArgs creation_args;
creation_args.set_server_end(puppet_.puppet_ptr.NewRequest());
creation_args.set_view_token(std::move(root_view_token_));
creation_args.set_keyboard_listener(puppet_.key_listener.NewBinding());
creation_args.set_flatland_client(std::move(flatland));
creation_args.set_keyboard_client(std::move(keyboard));
creation_args.set_device_pixel_ratio(1.0);
ASSERT_EQ(puppet_factory->Create(std::move(creation_args), &resp), ZX_OK);
ASSERT_EQ(resp.result(), futc::Result::SUCCESS);
FX_LOGS(INFO) << "wait for focus";
RunLoopUntil(
[this, &resp]() { return this->focus_listener_.IsViewFocused(resp.view_ref()); });
}
}
protected:
KeyPuppet puppet_;
};
TEST_F(SingleViewKeyConformanceTest, SimpleKeyPress) {
SimulateKeyEvent("Hello\nWorld!");
FX_LOGS(INFO) << "Wait for puppet to report key events";
RunLoopUntil([this]() { return this->puppet_.key_listener.events_received().size() >= 15; });
ASSERT_EQ(puppet_.key_listener.events_received().size(), 15u);
const auto& events = puppet_.key_listener.events_received();
EXPECT_EQ(events[0].non_printable(), fui::NonPrintableKey::SHIFT);
// view reports value from key meaning so it is Shifted h (H).
EXPECT_EQ(events[1].text(), std::string("H"));
EXPECT_EQ(events[2].text(), std::string("e"));
EXPECT_EQ(events[3].text(), std::string("l"));
EXPECT_EQ(events[4].text(), std::string("l"));
EXPECT_EQ(events[5].text(), std::string("o"));
EXPECT_EQ(events[6].non_printable(), fui::NonPrintableKey::ENTER);
EXPECT_EQ(events[7].non_printable(), fui::NonPrintableKey::SHIFT);
EXPECT_EQ(events[8].text(), std::string("W"));
EXPECT_EQ(events[9].text(), std::string("o"));
EXPECT_EQ(events[10].text(), std::string("r"));
EXPECT_EQ(events[11].text(), std::string("l"));
EXPECT_EQ(events[12].text(), std::string("d"));
EXPECT_EQ(events[13].non_printable(), fui::NonPrintableKey::SHIFT);
EXPECT_EQ(events[14].text(), std::string("!"));
}
class EmbeddedViewKeyConformanceTest : public KeyConformanceTest {
public:
~EmbeddedViewKeyConformanceTest() override = default;
void SetUp() override {
KeyConformanceTest::SetUp();
{
FX_LOGS(INFO) << "Create parent puppet";
futc::PuppetFactorySyncPtr puppet_factory;
ASSERT_EQ(LocalServiceDirectory()->Connect(puppet_factory.NewRequest(),
AUXILIARY_PUPPET_FACTORY_SERVICE),
ZX_OK);
futc::PuppetFactoryCreateResponse resp;
auto flatland = ConnectSyncIntoRealm<fuchsia::ui::composition::Flatland>();
auto keyboard = ConnectSyncIntoRealm<fui::Keyboard>();
futc::PuppetCreationArgs creation_args;
creation_args.set_server_end(parent_puppet_.puppet_ptr.NewRequest());
creation_args.set_view_token(std::move(root_view_token_));
creation_args.set_keyboard_listener(parent_puppet_.key_listener.NewBinding());
creation_args.set_flatland_client(std::move(flatland));
creation_args.set_keyboard_client(std::move(keyboard));
creation_args.set_device_pixel_ratio(1.0);
creation_args.set_focuser(parent_focuser_.NewRequest());
ASSERT_EQ(puppet_factory->Create(std::move(creation_args), &resp), ZX_OK);
ASSERT_EQ(resp.result(), futc::Result::SUCCESS);
ASSERT_TRUE(resp.has_view_ref());
resp.view_ref().Clone(&parent_view_ref_);
}
// Create child viewport.
futc::PuppetEmbedRemoteViewResponse embed_remote_view_response;
{
FX_LOGS(INFO) << "Creating child viewport";
const uint64_t kChildViewportId = 1u;
futc::PuppetEmbedRemoteViewRequest embed_remote_view_request;
embed_remote_view_request.set_id(kChildViewportId);
embed_remote_view_request.mutable_properties()->mutable_bounds()->set_size(
{.width = display_width_ / 2, .height = display_height_ / 2});
embed_remote_view_request.mutable_properties()->mutable_bounds()->set_origin(
{.x = display_width_as_int() / 2, .y = display_height_as_int() / 2});
parent_puppet_.puppet_ptr->EmbedRemoteView(std::move(embed_remote_view_request),
&embed_remote_view_response);
}
// Create child view.
{
FX_LOGS(INFO) << "Creating child puppet";
futc::PuppetFactorySyncPtr puppet_factory;
ASSERT_EQ(LocalServiceDirectory()->Connect(puppet_factory.NewRequest(),
PUPPET_UNDER_TEST_FACTORY_SERVICE),
ZX_OK);
futc::PuppetFactoryCreateResponse resp;
auto flatland = ConnectSyncIntoRealm<fuchsia::ui::composition::Flatland>();
auto keyboard = ConnectSyncIntoRealm<fui::Keyboard>();
futc::PuppetCreationArgs creation_args;
creation_args.set_server_end(child_puppet_.puppet_ptr.NewRequest());
creation_args.set_view_token(
std::move(*embed_remote_view_response.mutable_view_creation_token()));
creation_args.set_keyboard_listener(child_puppet_.key_listener.NewBinding());
creation_args.set_flatland_client(std::move(flatland));
creation_args.set_keyboard_client(std::move(keyboard));
creation_args.set_device_pixel_ratio(1.0);
ASSERT_EQ(puppet_factory->Create(std::move(creation_args), &resp), ZX_OK);
ASSERT_EQ(resp.result(), futc::Result::SUCCESS);
ASSERT_TRUE(resp.has_view_ref());
resp.view_ref().Clone(&child_view_ref_);
}
}
protected:
KeyPuppet parent_puppet_;
fuv::FocuserSyncPtr parent_focuser_;
fuv::ViewRef parent_view_ref_;
KeyPuppet child_puppet_;
fuv::ViewRef child_view_ref_;
};
TEST_F(EmbeddedViewKeyConformanceTest, KeyToFocusedView) {
// Default is focus on parent view.
{
FX_LOGS(INFO) << "wait for focus to parent";
RunLoopUntil([this]() { return this->focus_listener_.IsViewFocused(this->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(
[this]() { return this->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();
EXPECT_EQ(events[0].text(), "a");
EXPECT_TRUE(child_puppet_.key_listener.events_received().empty());
parent_puppet_.key_listener.clear_events();
}
// Focus on child view.
{
fuv::ViewRef child_view_ref;
child_view_ref_.Clone(&child_view_ref);
fuv::Focuser_RequestFocus_Result res;
ASSERT_EQ(parent_focuser_->RequestFocus(std::move(child_view_ref), &res), ZX_OK);
FX_LOGS(INFO) << "wait for focus to child";
RunLoopUntil([this]() { return this->focus_listener_.IsViewFocused(this->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(
[this]() { return this->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();
EXPECT_EQ(events[0].text(), "b");
EXPECT_TRUE(parent_puppet_.key_listener.events_received().empty());
child_puppet_.key_listener.clear_events();
}
// Focus back to parent view.
{
fuv::ViewRef parent_view_ref;
parent_view_ref_.Clone(&parent_view_ref);
fuv::Focuser_RequestFocus_Result res;
ASSERT_EQ(parent_focuser_->RequestFocus(std::move(parent_view_ref), &res), ZX_OK);
FX_LOGS(INFO) << "wait for focus to parent";
RunLoopUntil([this]() { return this->focus_listener_.IsViewFocused(this->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(
[this]() { return this->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();
EXPECT_EQ(events[0].text(), "c");
EXPECT_TRUE(child_puppet_.key_listener.events_received().empty());
parent_puppet_.key_listener.clear_events();
}
}
} // namespace ui_conformance_testing