blob: ef80b02a83fdafe308d3f2bf1652045a78647ac3 [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/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 <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";
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 {
focus_chain_updates_.push_back(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 (focus_chain_updates_.empty()) {
return false;
}
const auto& last_focus_chain = focus_chain_updates_.back();
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());
}
const std::vector<fuf::FocusChain>& focus_chain_updates() const { return focus_chain_updates_; }
private:
fidl::Binding<fuf::FocusChainListener> binding_;
std::vector<fuf::FocusChain> focus_chain_updates_;
};
using FocusConformanceTest = ui_conformance_test_base::ConformanceTest;
// This test exercises the focus contract with the scene owner: the view offered to the
// scene owner will have focus transferred to it.
TEST_F(FocusConformanceTest, ReceivesFocusTransfer) {
// Setup focus listener.
FocusListener focus_listener;
{
auto focus_registry = ConnectSyncIntoRealm<fuf::FocusChainListenerRegistry>();
ASSERT_EQ(focus_registry->Register(focus_listener.NewBinding()), ZX_OK);
}
RunLoopUntil([&focus_listener]() { return focus_listener.focus_chain_updates().size() > 0; });
// No focus chain when no scene.
EXPECT_FALSE(focus_listener.focus_chain_updates().back().has_focus_chain());
// Present view.
fuv::ViewCreationToken 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);
}
// Add puppet view.
fuv::ViewRef view_ref;
{
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::PuppetSyncPtr puppet_ptr;
futc::PuppetCreationArgs creation_args;
creation_args.set_server_end(puppet_ptr.NewRequest());
creation_args.set_view_token(std::move(root_view_token));
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);
resp.view_ref().Clone(&view_ref);
}
FX_LOGS(INFO) << "wait for focus";
RunLoopUntil([&focus_listener, &view_ref]() { return focus_listener.IsViewFocused(view_ref); });
}
// This test ensures that multiple clients can connect to the FocusChainListenerRegistry.
TEST_F(FocusConformanceTest, MultiListener) {
// Setup focus listener a.
FocusListener focus_listener_a;
{
auto focus_registry = ConnectSyncIntoRealm<fuf::FocusChainListenerRegistry>();
ASSERT_EQ(focus_registry->Register(focus_listener_a.NewBinding()), ZX_OK);
}
RunLoopUntil([&focus_listener_a]() { return focus_listener_a.focus_chain_updates().size() > 0; });
// No focus chain when no scene.
EXPECT_FALSE(focus_listener_a.focus_chain_updates().back().has_focus_chain());
// Present view.
fuv::ViewCreationToken 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 b.
FocusListener focus_listener_b;
{
auto focus_registry = ConnectSyncIntoRealm<fuf::FocusChainListenerRegistry>();
ASSERT_EQ(focus_registry->Register(focus_listener_b.NewBinding()), ZX_OK);
}
// Add puppet view.
fuv::ViewRef view_ref;
{
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::PuppetSyncPtr puppet_ptr;
futc::PuppetCreationArgs creation_args;
creation_args.set_server_end(puppet_ptr.NewRequest());
creation_args.set_view_token(std::move(root_view_token));
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);
resp.view_ref().Clone(&view_ref);
}
FX_LOGS(INFO) << "focus listener a receives focus transfer";
RunLoopUntil(
[&focus_listener_a, &view_ref]() { return focus_listener_a.IsViewFocused(view_ref); });
FX_LOGS(INFO) << "focus listener b receives focus transfer";
RunLoopUntil(
[&focus_listener_b, &view_ref]() { return focus_listener_b.IsViewFocused(view_ref); });
}
} // namespace ui_conformance_testing