blob: 5169618997e37e4c90d244e2dad4286b783ac4dc [file] [log] [blame]
// Copyright 2020 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/input/cpp/fidl.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <zircon/status.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/ui/scenic/lib/input/helper.h"
#include "src/ui/scenic/lib/input/input_system.h"
#include "src/ui/scenic/lib/input/tests/util.h"
using fuchsia::ui::views::ViewRef;
using Phase = fuchsia::ui::pointerinjector::EventPhase;
using DeviceType = fuchsia::ui::pointerinjector::DeviceType;
using StreamId = scenic_impl::input::StreamId;
namespace lib_ui_input_tests {
namespace {
// clang-format off
static constexpr std::array<float, 9> kIdentityMatrix = {
1, 0, 0, // first column
0, 1, 0, // second column
0, 0, 1, // third column
};
// clang-format on
class InputInjectionTest : public InputSystemTest {
public:
InputInjectionTest() {}
void TearDown() override {
root_resources_.reset();
root_session_.reset();
parent_.reset();
child_.reset();
InputSystemTest::TearDown();
}
// Create a view tree of depth 3: scene, parent view, child view.
// Return view refs of parent view and child view.
std::pair<ViewRef, ViewRef> SetupSceneWithParentAndChildViews() {
auto [v1, vh1] = scenic::ViewTokenPair::New();
auto [v2, vh2] = scenic::ViewTokenPair::New();
auto [root_session, root_resources] = CreateScene();
scenic::Session* const session = root_session.session();
scenic::Scene* const scene = &root_resources.scene;
scenic::ViewHolder parent_view_holder(session, std::move(vh1), "1");
scene->AddChild(parent_view_holder);
RequestToPresent(session);
SessionWrapper parent = CreateClient("parent_view", std::move(v1));
scenic::ViewHolder child_view_holder(parent.session(), std::move(vh2), "2");
parent.view()->AddChild(child_view_holder);
RequestToPresent(parent.session());
SessionWrapper child = CreateClient("child_view", std::move(v2));
RequestToPresent(child.session());
ViewRef parent_view_ref = parent.view_ref();
ViewRef child_view_ref = child.view_ref();
root_session_ = std::make_unique<SessionWrapper>(std::move(root_session));
parent_ = std::make_unique<SessionWrapper>(std::move(parent));
child_ = std::make_unique<SessionWrapper>(std::move(child));
root_resources_ = std::make_unique<ResourceGraph>(std::move(root_resources));
return {std::move(parent_view_ref), std::move(child_view_ref)};
}
protected:
fuchsia::ui::pointerinjector::Config ConfigTemplate(
const fuchsia::ui::views::ViewRef& context_view_ref,
const fuchsia::ui::views::ViewRef& target_view_ref) {
fuchsia::ui::pointerinjector::Config config;
config.set_device_id(1);
config.set_device_type(DeviceType::TOUCH);
config.set_dispatch_policy(fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET);
{
fuchsia::ui::pointerinjector::Viewport viewport;
viewport.set_extents(FullScreenExtents());
viewport.set_viewport_to_context_transform(kIdentityMatrix);
config.set_viewport(std::move(viewport));
}
{
fuchsia::ui::pointerinjector::Context context;
ViewRef context_clone;
fidl::Clone(context_view_ref, &context_clone);
context.set_view(std::move(context_clone));
config.set_context(std::move(context));
}
{
fuchsia::ui::pointerinjector::Target target;
ViewRef target_clone;
fidl::Clone(target_view_ref, &target_clone);
target.set_view(std::move(target_clone));
config.set_target(std::move(target));
}
return config;
}
uint32_t test_display_width_px() const override { return 5; }
uint32_t test_display_height_px() const override { return 5; }
std::array<std::array<float, 2>, 2> FullScreenExtents() const {
return {{{0, 0},
{static_cast<float>(test_display_width_px()),
static_cast<float>(test_display_height_px())}}};
}
std::unique_ptr<ResourceGraph> root_resources_;
std::unique_ptr<SessionWrapper> root_session_;
std::unique_ptr<SessionWrapper> parent_;
std::unique_ptr<SessionWrapper> child_;
};
scenic_impl::input::InjectorSettings InjectorSettingsTemplate() {
return {.dispatch_policy = fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET,
.device_id = 1,
.device_type = DeviceType::TOUCH,
.context_koid = 1,
.target_koid = 2};
}
scenic_impl::input::Viewport ViewportTemplate() {
return {
.extents = std::array<std::array<float, 2>, 2>{{{0, 0}, {1000, 1000}}},
.context_from_viewport_transform =
scenic_impl::input::ColumnMajorMat3VectorToMat4(kIdentityMatrix),
};
}
fuchsia::ui::pointerinjector::Event InjectionEventTemplate() {
fuchsia::ui::pointerinjector::Event event;
event.set_timestamp(1111);
{
fuchsia::ui::pointerinjector::PointerSample pointer_sample;
pointer_sample.set_pointer_id(2222);
pointer_sample.set_phase(Phase::CHANGE);
pointer_sample.set_position_in_viewport({333, 444});
fuchsia::ui::pointerinjector::Data data;
data.set_pointer_sample(std::move(pointer_sample));
event.set_data(std::move(data));
}
return event;
}
TEST_F(InputInjectionTest, RegisterAttemptWithCorrectArguments_ShouldSucceed) {
const auto [parent_view_ref, child_view_ref] = SetupSceneWithParentAndChildViews();
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t status) {
error_callback_fired = true;
FX_LOGS(INFO) << "Error: " << status;
});
{
fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
}
RunLoopUntilIdle();
EXPECT_TRUE(register_callback_fired);
EXPECT_FALSE(error_callback_fired);
}
TEST_F(InputInjectionTest, RegisterAttemptWithBadDeviceConfig_ShouldFail) {
const auto [parent_view_ref, child_view_ref] = SetupSceneWithParentAndChildViews();
const fuchsia::ui::pointerinjector::Config base_config =
ConfigTemplate(std::move(parent_view_ref), std::move(child_view_ref));
{ // No device id.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
config.clear_device_id();
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // No device type.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
config.clear_device_type();
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // Wrong device type.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
// Set to not TOUCH.
config.set_device_type(static_cast<DeviceType>(static_cast<uint32_t>(12421)));
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
}
TEST_F(InputInjectionTest, RegisterAttemptWithBadContextOrTarget_ShouldFail) {
const auto [parent_view_ref, child_view_ref] = SetupSceneWithParentAndChildViews();
const fuchsia::ui::pointerinjector::Config base_config =
ConfigTemplate(parent_view_ref, child_view_ref);
{ // No context.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
config.clear_context();
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // No target.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
config.clear_target();
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // Context equals target.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
ViewRef parent_clone1, parent_clone2;
fidl::Clone(parent_view_ref, &parent_clone1);
fidl::Clone(parent_view_ref, &parent_clone2);
{
fuchsia::ui::pointerinjector::Context context;
context.set_view(std::move(parent_clone1));
config.set_context(std::move(context));
}
{
fuchsia::ui::pointerinjector::Target target;
target.set_view(std::move(parent_clone2));
config.set_target(std::move(target));
}
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // Context is descendant of target.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
ViewRef parent_clone, child_clone;
fidl::Clone(parent_view_ref, &parent_clone);
fidl::Clone(child_view_ref, &child_clone);
// Swap context and target.
{
fuchsia::ui::pointerinjector::Context context;
context.set_view(std::move(child_clone));
config.set_context(std::move(context));
}
{
fuchsia::ui::pointerinjector::Target target;
target.set_view(std::move(parent_clone));
config.set_target(std::move(target));
}
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // Context is unregistered.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
ViewRef child_clone;
fidl::Clone(child_view_ref, &child_clone);
auto [control_ref, unregistered_view_ref] = scenic::ViewRefPair::New();
{
fuchsia::ui::pointerinjector::Context context;
context.set_view(std::move(unregistered_view_ref));
config.set_context(std::move(context));
}
{
fuchsia::ui::pointerinjector::Target target;
target.set_view(std::move(child_clone));
config.set_target(std::move(target));
}
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // Target is unregistered.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
ViewRef parent_clone;
fidl::Clone(parent_view_ref, &parent_clone);
auto [control_ref, unregistered_view_ref] = scenic::ViewRefPair::New();
{
fuchsia::ui::pointerinjector::Context context;
context.set_view(std::move(parent_clone));
config.set_context(std::move(context));
}
{
fuchsia::ui::pointerinjector::Target target;
target.set_view(std::move(unregistered_view_ref));
config.set_target(std::move(target));
}
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // Context is detached from scene.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
ViewRef parent_clone;
fidl::Clone(parent_view_ref, &parent_clone);
ViewRef child_clone;
fidl::Clone(child_view_ref, &child_clone);
{
fuchsia::ui::pointerinjector::Context context;
context.set_view(std::move(parent_clone));
config.set_context(std::move(context));
}
{
fuchsia::ui::pointerinjector::Target target;
target.set_view(std::move(child_clone));
config.set_target(std::move(target));
}
// Detach from scene.
root_resources_->scene.DetachChildren();
RequestToPresent(root_session_->session());
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
}
TEST_F(InputInjectionTest, RegisterAttemptWithBadDispatchPolicy_ShouldFail) {
const auto [parent_view_ref, child_view_ref] = SetupSceneWithParentAndChildViews();
const fuchsia::ui::pointerinjector::Config base_config =
ConfigTemplate(parent_view_ref, child_view_ref);
{ // No dispatch policy.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
config.clear_dispatch_policy();
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
{ // Unsupported dispatch policy.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config;
fidl::Clone(base_config, &config);
config.set_dispatch_policy(static_cast<fuchsia::ui::pointerinjector::DispatchPolicy>(6323));
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
}
TEST_F(InputInjectionTest, ChannelDying_ShouldNotCrash) {
const auto [parent_view_ref, child_view_ref] = SetupSceneWithParentAndChildViews();
{
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_TRUE(register_callback_fired);
EXPECT_FALSE(error_callback_fired);
} // |injector| goes out of scope.
RunLoopUntilIdle();
}
TEST_F(InputInjectionTest, MultipleRegistrations_ShouldSucceed) {
const auto [parent_view_ref, child_view_ref] = SetupSceneWithParentAndChildViews();
fuchsia::ui::pointerinjector::DevicePtr injector;
{
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_TRUE(register_callback_fired);
EXPECT_FALSE(error_callback_fired);
}
fuchsia::ui::pointerinjector::DevicePtr injector2;
{
bool register_callback_fired = false;
bool error_callback_fired = false;
injector2.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config = ConfigTemplate(parent_view_ref, child_view_ref);
input_system()->Register(std::move(config), injector2.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_TRUE(register_callback_fired);
EXPECT_FALSE(error_callback_fired);
}
}
TEST(InjectorTest, InjectedEvents_ShouldTriggerTheInjectLambda) {
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
bool connectivity_is_good = true;
uint32_t num_injections = 0;
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[&connectivity_is_good](zx_koid_t, zx_koid_t) { return connectivity_is_good; },
/*inject=*/[&num_injections](auto...) { ++num_injections; });
{ // Inject one event.
bool injection_callback_fired = false;
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_TRUE(injection_callback_fired);
}
// 2 injections, since an injected ADD becomes "ADD; DOWN"
// in fuchsia.ui.input.PointerEvent's state machine.
EXPECT_EQ(num_injections, 2u);
{ // Inject CHANGE event.
bool injection_callback_fired = false;
std::vector<fuchsia::ui::pointerinjector::Event> events;
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_phase(Phase::CHANGE);
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_TRUE(injection_callback_fired);
EXPECT_EQ(num_injections, 3u);
}
{ // Inject remove event.
bool injection_callback_fired = false;
std::vector<fuchsia::ui::pointerinjector::Event> events;
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_phase(Phase::REMOVE);
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_TRUE(injection_callback_fired);
}
// 5 injections, since an injected REMOVE becomes "UP; REMOVE"
// in fuchsia.ui.input.PointerEvent's state machine.
EXPECT_EQ(num_injections, 5u);
EXPECT_FALSE(error_callback_fired);
}
TEST(InjectorTest, InjectionWithNoEvent_ShouldCloseChannel) {
// Test loop to be able to control dispatch without having to create an entire test class
// subclassing TestLoopFixture.
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[](auto...) { return true; },
/*inject=*/
[](auto...) {});
bool injection_callback_fired = false;
// Inject nothing.
injector->Inject({}, [&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_FALSE(injection_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
TEST(InjectorTest, ClientClosingChannel_ShouldTriggerCancelEvents_ForEachOngoingStream) {
// Test loop to be able to control dispatch without having to create an entire test class
// subclassing TestLoopFixture.
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
std::vector<uint32_t> cancelled_streams;
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[](auto...) { return true; },
/*inject=*/
[&cancelled_streams](const scenic_impl::input::InternalPointerEvent& event, StreamId) {
if (event.phase == scenic_impl::input::Phase::CANCEL)
cancelled_streams.push_back(event.pointer_id);
});
// Start three streams and end one.
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(1);
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)}, [] {});
}
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(2);
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)}, [] {});
}
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(3);
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)}, [] {});
}
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(1);
event.mutable_data()->pointer_sample().set_phase(Phase::REMOVE);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)}, [] {});
}
// Close the client side channel.
injector = {};
test_loop.RunUntilIdle();
// Should receive two CANCEL events, since there should be two ongoing streams.
EXPECT_FALSE(error_callback_fired);
EXPECT_THAT(cancelled_streams, testing::UnorderedElementsAre(2, 3));
}
TEST(InjectorTest, ServerClosingChannel_ShouldTriggerCancelEvents_ForEachOngoingStream) {
// Test loop to be able to control dispatch without having to create an entire test class
// subclassing TestLoopFixture.
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
std::vector<uint32_t> cancelled_streams;
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[](auto...) { return true; },
/*inject=*/
[&cancelled_streams](const scenic_impl::input::InternalPointerEvent& event, StreamId) {
if (event.phase == scenic_impl::input::Phase::CANCEL)
cancelled_streams.push_back(event.pointer_id);
});
// Start three streams and end one.
{
std::vector<fuchsia::ui::pointerinjector::Event> events;
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(1);
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
events.emplace_back(std::move(event));
}
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(2);
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
events.emplace_back(std::move(event));
}
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(3);
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
events.emplace_back(std::move(event));
}
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(1);
event.mutable_data()->pointer_sample().set_phase(Phase::REMOVE);
events.emplace_back(std::move(event));
}
injector->Inject({std::move(events)}, [] {});
}
// Inject an event with missing fields to cause the channel to close.
{
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back();
injector->Inject(std::move(events), [] {});
}
test_loop.RunUntilIdle();
EXPECT_TRUE(error_callback_fired);
// Should receive CANCEL events for the two ongoing streams; 2 and 3.
EXPECT_THAT(cancelled_streams, testing::UnorderedElementsAre(2, 3));
}
TEST(InjectorTest, InjectionOfEmptyEvent_ShouldCloseChannel) {
// Test loop to be able to control dispatch without having to create an entire test class
// subclassing TestLoopFixture.
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](auto) { error_callback_fired = true; });
bool injection_lambda_fired = false;
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[](zx_koid_t, zx_koid_t) { return true; },
/*inject=*/
[&injection_lambda_fired](auto...) { injection_lambda_fired = true; });
bool injection_callback_fired = false;
fuchsia::ui::pointerinjector::Event event;
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_FALSE(injection_lambda_fired);
EXPECT_FALSE(injection_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
// Test for lazy connectivity detection.
// TODO(fxbug.dev/50348): Remove when instant connectivity breakage detection is added.
TEST(InjectorTest, InjectionWithBadConnectivity_ShouldCloseChannel) {
// Test loop to be able to control dispatch without having to create an entire test class
// subclassing TestLoopFixture.
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
zx_status_t error = ZX_OK;
injector.set_error_handler([&error_callback_fired, &error](zx_status_t status) {
error_callback_fired = true;
error = status;
});
bool connectivity_is_good = true;
uint32_t num_cancel_events = 0;
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[&connectivity_is_good](zx_koid_t, zx_koid_t) { return connectivity_is_good; },
/*inject=*/
[&num_cancel_events](const scenic_impl::input::InternalPointerEvent& event, StreamId) {
num_cancel_events += event.phase == scenic_impl::input::Phase::CANCEL ? 1 : 0;
});
// Start event stream while connectivity is good.
{
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_phase(Phase::ADD);
event.mutable_data()->pointer_sample().set_pointer_id(1);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)}, [] {});
test_loop.RunUntilIdle();
}
// Connectivity was good. No problems.
EXPECT_FALSE(error_callback_fired);
// Inject with bad connectivity.
connectivity_is_good = false;
{
bool injection_callback_fired = false;
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_phase(Phase::CHANGE);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_FALSE(injection_callback_fired);
}
// Connectivity was bad, so channel should be closed and an extra CANCEL event should have been
// injected for each ongoing stream.
EXPECT_EQ(num_cancel_events, 1u);
EXPECT_TRUE(error_callback_fired);
EXPECT_EQ(error, ZX_ERR_BAD_STATE);
}
// Class for testing parameterized injection of invalid events.
// Takes an int that determines which field gets deleted (parameter must be copyable).
class InjectorInvalidEventsTest : public gtest::TestLoopFixture,
public testing::WithParamInterface<int> {};
INSTANTIATE_TEST_SUITE_P(InjectEventWithMissingField_ShouldCloseChannel, InjectorInvalidEventsTest,
testing::Range(0, 3));
TEST_P(InjectorInvalidEventsTest, InjectEventWithMissingField_ShouldCloseChannel) {
// Create event with a missing field based on GetParam().
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
switch (GetParam()) {
case 0:
event.mutable_data()->pointer_sample().clear_pointer_id();
break;
case 1:
event.mutable_data()->pointer_sample().clear_phase();
break;
case 2:
event.mutable_data()->pointer_sample().clear_position_in_viewport();
break;
}
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
zx_status_t error = ZX_OK;
injector.set_error_handler([&error_callback_fired, &error](zx_status_t status) {
error_callback_fired = true;
error = status;
});
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[](auto...) { return true; },
/*inject=*/
[](auto...) {});
bool injection_callback_fired = false;
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(injection_callback_fired);
EXPECT_TRUE(error_callback_fired);
EXPECT_EQ(error, ZX_ERR_INVALID_ARGS);
}
// Class for testing different event streams.
// Each invocation gets a vector of pairs of pointer ids and Phases, representing pointer streams.
class InjectorGoodEventStreamTest
: public gtest::TestLoopFixture,
public testing::WithParamInterface<std::vector<std::pair</*pointer_id*/ uint32_t, Phase>>> {};
static std::vector<std::vector<std::pair<uint32_t, Phase>>> GoodStreamTestData() {
// clang-format off
return {
{{1, Phase::ADD}, {1, Phase::REMOVE}}, // 0: one pointer trivial
{{1, Phase::ADD}, {1, Phase::CHANGE}, {1, Phase::REMOVE}}, // 1: one pointer minimal all phases
{{1, Phase::ADD}, {1, Phase::CANCEL}}, // 2: one pointer trivial cancelled
{{1, Phase::ADD}, {1, Phase::CHANGE}, {1, Phase::CANCEL}}, // 3: one pointer minimal all phases cancelled
{{1, Phase::ADD}, {1, Phase::CHANGE}, {1, Phase::CANCEL},
{2, Phase::ADD}, {2, Phase::CHANGE}, {2, Phase::CANCEL}}, // 4: two pointers successive streams
{{2, Phase::ADD}, {1, Phase::ADD}, {2, Phase::CHANGE},
{1, Phase::CHANGE}, {1, Phase::CANCEL}, {2, Phase::CANCEL}}, // 5: two pointer interleaved
};
// clang-format on
}
INSTANTIATE_TEST_SUITE_P(InjectionWithGoodEventStream_ShouldHaveNoProblems_CombinedEvents,
InjectorGoodEventStreamTest, testing::ValuesIn(GoodStreamTestData()));
INSTANTIATE_TEST_SUITE_P(InjectionWithGoodEventStream_ShouldHaveNoProblems_SeparateEvents,
InjectorGoodEventStreamTest, testing::ValuesIn(GoodStreamTestData()));
// Inject a valid event stream in a single Inject() call.
TEST_P(InjectorGoodEventStreamTest,
InjectionWithGoodEventStream_ShouldHaveNoProblems_CombinedEvents) {
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[](auto...) { return true; }, // Always true.
/*inject=*/
[](auto...) {});
std::vector<fuchsia::ui::pointerinjector::Event> events;
for (auto [pointer_id, phase] : GetParam()) {
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(pointer_id);
event.mutable_data()->pointer_sample().set_phase(phase);
events.emplace_back(std::move(event));
}
bool injection_callback_fired = false;
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_TRUE(injection_callback_fired);
EXPECT_FALSE(error_callback_fired);
}
// Inject a valid event stream in multiple Inject() calls.
TEST_P(InjectorGoodEventStreamTest,
InjectionWithGoodEventStream_ShouldHaveNoProblems_SeparateEvents) {
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/
[](auto...) { return true; }, // Always true.
/*inject=*/
[](auto...) {});
for (auto [pointer_id, phase] : GetParam()) {
bool injection_callback_fired = false;
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(pointer_id);
event.mutable_data()->pointer_sample().set_phase(phase);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_TRUE(injection_callback_fired);
ASSERT_FALSE(error_callback_fired);
}
}
// Bad event streams.
// Each invocation gets a vector of pairs of pointer ids and Phases, representing pointer streams.
class InjectorBadEventStreamTest
: public gtest::TestLoopFixture,
public testing::WithParamInterface<std::vector<std::pair</*pointer_id*/ uint32_t, Phase>>> {};
static std::vector<std::vector<std::pair<uint32_t, Phase>>> BadStreamTestData() {
// clang-format off
return {
{{1, Phase::CHANGE}}, // 0: one pointer non-add initial event
{{1, Phase::REMOVE}}, // 1: one pointer non-add initial event
{{1, Phase::ADD}, {1, Phase::ADD}}, // 2: one pointer double add
{{1, Phase::ADD}, {1, Phase::CHANGE}, {1, Phase::ADD}}, // 3: one pointer double add mid-stream
{{1, Phase::ADD}, {1, Phase::REMOVE}, {1, Phase::REMOVE}}, // 4: one pointer double remove
{{1, Phase::ADD}, {1, Phase::REMOVE}, {1, Phase::CHANGE}}, // 5: one pointer event after remove
{{1, Phase::ADD}, {1, Phase::CHANGE},
{1, Phase::REMOVE}, {2, Phase::ADD}, {2, Phase::ADD}}, // 6: two pointer faulty stream after correct stream
{{1, Phase::ADD}, {2, Phase::ADD},
{2, Phase::CHANGE}, {2, Phase::REMOVE}, {1, Phase::ADD}}, // 7 two pointer faulty stream interleaved with correct stream
};
// clang-format on
}
INSTANTIATE_TEST_SUITE_P(InjectionWithBadEventStream_ShouldCloseChannel_CombinedEvents,
InjectorBadEventStreamTest, testing::ValuesIn(BadStreamTestData()));
INSTANTIATE_TEST_SUITE_P(InjectionWithBadEventStream_ShouldCloseChannel_SeparateEvents,
InjectorBadEventStreamTest, testing::ValuesIn(BadStreamTestData()));
// Inject an invalid event stream in a single Inject() call.
TEST_P(InjectorBadEventStreamTest, InjectionWithBadEventStream_ShouldCloseChannel_CombinedEvents) {
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
zx_status_t error = ZX_OK;
injector.set_error_handler([&error_callback_fired, &error](zx_status_t status) {
error_callback_fired = true;
error = status;
});
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/[](auto...) { return true; },
/*inject=*/[](auto...) {});
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
// Run event stream.
std::vector<fuchsia::ui::pointerinjector::Event> events;
for (auto [pointer_id, phase] : GetParam()) {
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(pointer_id);
event.mutable_data()->pointer_sample().set_phase(phase);
events.emplace_back(std::move(event));
}
injector->Inject({std::move(events)}, [] {});
RunLoopUntilIdle();
EXPECT_TRUE(error_callback_fired);
EXPECT_EQ(error, ZX_ERR_BAD_STATE);
}
// Inject an invalid event stream in multiple Inject() calls.
TEST_P(InjectorBadEventStreamTest, InjectionWithBadEventStream_ShouldCloseChannel_SeparateEvents) {
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
zx_status_t error = ZX_OK;
injector.set_error_handler([&error_callback_fired, &error](zx_status_t status) {
error_callback_fired = true;
error = status;
});
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/[](auto...) { return true; },
/*inject=*/[](auto...) {});
// Run event stream.
for (auto [pointer_id, phase] : GetParam()) {
fuchsia::ui::pointerinjector::Event event = InjectionEventTemplate();
event.mutable_data()->pointer_sample().set_pointer_id(pointer_id);
event.mutable_data()->pointer_sample().set_phase(phase);
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)}, [] {});
RunLoopUntilIdle();
}
EXPECT_TRUE(error_callback_fired);
EXPECT_EQ(error, ZX_ERR_BAD_STATE);
}
TEST(InjectorTest, InjectedViewport_ShouldNotTriggerInjectLambda) {
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
// Set up an isolated Injector.
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
bool inject_lambda_fired = false;
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/[](zx_koid_t, zx_koid_t) { return true; },
/*inject=*/[&inject_lambda_fired](auto...) { inject_lambda_fired = true; });
{
bool injection_callback_fired = false;
fuchsia::ui::pointerinjector::Event event;
event.set_timestamp(1);
{
fuchsia::ui::pointerinjector::Viewport viewport;
viewport.set_extents({{{-242, -383}, {124, 252}}});
viewport.set_viewport_to_context_transform(kIdentityMatrix);
fuchsia::ui::pointerinjector::Data data;
data.set_viewport(std::move(viewport));
event.set_data(std::move(data));
}
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_TRUE(injection_callback_fired);
}
test_loop.RunUntilIdle();
EXPECT_FALSE(inject_lambda_fired);
EXPECT_FALSE(error_callback_fired);
}
// Parameterized tests for malformed viewport arguments.
// Use pairs of optional extents and matrices. Because test parameters must be copyable.
using ViewportPair = std::pair<std::optional<std::array<std::array<float, 2>, 2>>,
std::optional<std::array<float, 9>>>;
class InjectorBadViewportTest : public gtest::TestLoopFixture,
public testing::WithParamInterface<ViewportPair> {};
static std::vector<ViewportPair> BadViewportTestData() {
std::vector<ViewportPair> bad_viewports;
{ // 0: No extents.
ViewportPair pair;
pair.second.emplace(kIdentityMatrix);
bad_viewports.emplace_back(pair);
}
{ // 1: No viewport_to_context_transform.
ViewportPair pair;
pair.first = {{/*min*/ {0, 0}, /*max*/ {10, 10}}};
bad_viewports.emplace_back(pair);
}
{ // 2: Malformed extents: Min bigger than max.
fuchsia::ui::pointerinjector::Viewport viewport;
ViewportPair pair;
pair.first = {{/*min*/ {-100, 100}, /*max*/ {100, -100}}};
pair.second = kIdentityMatrix;
bad_viewports.emplace_back(pair);
}
{ // 3: Malformed extents: Min equal to max.
fuchsia::ui::pointerinjector::Viewport viewport;
ViewportPair pair;
pair.first = {{/*min*/ {0, -100}, /*max*/ {0, 100}}};
pair.second = kIdentityMatrix;
bad_viewports.emplace_back(pair);
}
{ // 4: Malformed extents: Contains NaN
fuchsia::ui::pointerinjector::Viewport viewport;
ViewportPair pair;
pair.first = {{/*min*/ {0, 0}, /*max*/ {100, std::numeric_limits<double>::quiet_NaN()}}};
pair.second = kIdentityMatrix;
bad_viewports.emplace_back(pair);
}
{ // 5: Malformed extents: Contains Inf
fuchsia::ui::pointerinjector::Viewport viewport;
ViewportPair pair;
pair.first = {{/*min*/ {0, 0}, /*max*/ {100, std::numeric_limits<double>::infinity()}}};
pair.second = kIdentityMatrix;
bad_viewports.emplace_back(pair);
}
{ // 6: Malformed transform: Non-invertible matrix
// clang-format off
const std::array<float, 9> non_invertible_matrix = {
1, 0, 0,
1, 0, 0,
0, 0, 1,
};
// clang-format on
fuchsia::ui::pointerinjector::Viewport viewport;
ViewportPair pair;
pair.first = {{{/*min*/ {0, 0}, /*max*/ {10, 10}}}};
pair.second = non_invertible_matrix;
bad_viewports.emplace_back(pair);
}
{ // 7: Malformed transform: Contains NaN
// clang-format off
const std::array<float, 9> nan_matrix = {
1, std::numeric_limits<double>::quiet_NaN(), 0,
0, 1, 0,
0, 0, 1,
};
// clang-format on
fuchsia::ui::pointerinjector::Viewport viewport;
ViewportPair pair;
pair.first = {{{/*min*/ {0, 0}, /*max*/ {10, 10}}}};
pair.second = nan_matrix;
bad_viewports.emplace_back(pair);
}
{ // 8: Malformed transform: Contains Inf
// clang-format off
const std::array<float, 9> inf_matrix = {
1, std::numeric_limits<double>::infinity(), 0,
0, 1, 0,
0, 0, 1,
};
// clang-format on
fuchsia::ui::pointerinjector::Viewport viewport;
ViewportPair pair;
pair.first = {{{/*min*/ {0, 0}, /*max*/ {10, 10}}}};
pair.second = inf_matrix;
bad_viewports.emplace_back(pair);
}
return bad_viewports;
}
class ParameterizedInputInjectionTest : public InputInjectionTest,
public testing::WithParamInterface<ViewportPair> {};
INSTANTIATE_TEST_SUITE_P(RegisterAttemptWithBadViewport_ShouldFail, ParameterizedInputInjectionTest,
testing::ValuesIn(BadViewportTestData()));
TEST_P(ParameterizedInputInjectionTest, RegisterAttemptWithBadViewport_ShouldFail) {
const auto [parent_view_ref, child_view_ref] = SetupSceneWithParentAndChildViews();
fuchsia::ui::pointerinjector::DevicePtr injector;
bool register_callback_fired = false;
bool error_callback_fired = false;
injector.set_error_handler(
[&error_callback_fired](zx_status_t status) { error_callback_fired = true; });
fuchsia::ui::pointerinjector::Config config =
ConfigTemplate(std::move(parent_view_ref), std::move(child_view_ref));
{
ViewportPair params = GetParam();
fuchsia::ui::pointerinjector::Viewport viewport;
if (params.first)
viewport.set_extents(params.first.value());
if (params.second)
viewport.set_viewport_to_context_transform(params.second.value());
config.set_viewport(std::move(viewport));
}
input_system()->Register(std::move(config), injector.NewRequest(),
[&register_callback_fired] { register_callback_fired = true; });
RunLoopUntilIdle();
EXPECT_FALSE(register_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
INSTANTIATE_TEST_SUITE_P(InjectBadViewport_ShouldCloseChannel, InjectorBadViewportTest,
testing::ValuesIn(BadViewportTestData()));
TEST_P(InjectorBadViewportTest, InjectBadViewport_ShouldCloseChannel) {
async::TestLoop test_loop;
async_set_default_dispatcher(test_loop.dispatcher());
fuchsia::ui::pointerinjector::DevicePtr injector;
bool error_callback_fired = false;
injector.set_error_handler([&error_callback_fired](zx_status_t) { error_callback_fired = true; });
bool inject_lambda_fired = false;
scenic_impl::input::Injector injector_impl(
InjectorSettingsTemplate(), ViewportTemplate(), injector.NewRequest(),
/*is_descendant_and_connected=*/[](zx_koid_t, zx_koid_t) { return true; },
/*inject=*/[&inject_lambda_fired](auto...) { inject_lambda_fired = true; });
fuchsia::ui::pointerinjector::Event event;
{
event.set_timestamp(1);
fuchsia::ui::pointerinjector::Data data;
ViewportPair params = GetParam();
fuchsia::ui::pointerinjector::Viewport viewport;
if (params.first)
viewport.set_extents(params.first.value());
if (params.second)
viewport.set_viewport_to_context_transform(params.second.value());
data.set_viewport(std::move(viewport));
event.set_data(std::move(data));
}
std::vector<fuchsia::ui::pointerinjector::Event> events;
events.emplace_back(std::move(event));
bool injection_callback_fired = false;
injector->Inject({std::move(events)},
[&injection_callback_fired] { injection_callback_fired = true; });
test_loop.RunUntilIdle();
EXPECT_FALSE(injection_callback_fired);
EXPECT_TRUE(error_callback_fired);
}
} // namespace
} // namespace lib_ui_input_tests