blob: 2467b48df36b893ba701e721a07e60410359ac9a [file] [log] [blame]
// Copyright 2022 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/input/report/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/ui/display/singleton/cpp/fidl.h>
#include <fuchsia/ui/input3/cpp/fidl.h>
#include <fuchsia/ui/pointer/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/sys/cpp/component_context.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/view_creation_tokens.h>
#include <zircon/errors.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "src/ui/tests/conformance_input_tests/conformance-test-base.h"
namespace ui_conformance_testing {
namespace futi = fuchsia::ui::test::input;
namespace fir = fuchsia::input::report;
const std::string PUPPET_UNDER_TEST_FACTORY_SERVICE = "puppet-under-test-factory-service";
const std::string AUXILIARY_PUPPET_FACTORY_SERVICE = "auxiliary-puppet-factory-service";
// Two physical pixel coordinates are considered equivalent if their distance is less than 1 unit.
constexpr double kPixelEpsilon = 0.5f;
// Epsilon for floating error.
constexpr double kEpsilon = 0.0001f;
// TODO(https://fxbug.dev/42076606): Two coordinates (x/y) systems can differ in scale (size of
// pixels).
void ExpectLocationAndPhase(const std::string& scoped_message,
const futi::TouchInputListenerReportTouchInputRequest& e,
float expected_pixel_ratio, double expected_x, double expected_y,
fuchsia::ui::pointer::EventPhase expected_phase,
const uint32_t expected_pointer_id) {
SCOPED_TRACE(scoped_message);
auto pixel_scale = e.has_device_pixel_ratio() ? e.device_pixel_ratio() : 1;
EXPECT_NEAR(static_cast<double>(expected_pixel_ratio), pixel_scale, kEpsilon);
auto actual_x = pixel_scale * e.local_x();
auto actual_y = pixel_scale * e.local_y();
EXPECT_NEAR(expected_x, actual_x, kPixelEpsilon);
EXPECT_NEAR(expected_y, actual_y, kPixelEpsilon);
EXPECT_EQ(expected_phase, e.phase());
EXPECT_EQ(expected_pointer_id, e.pointer_id());
}
// Input stack does not guarantee the order of pointer in 1 contact report.
// So tests sort the request vector by pointer id and keep the order of
// event time.
void SortTouchInputRequestByTimeAndTimestampAndPointerId(
std::vector<futi::TouchInputListenerReportTouchInputRequest>& requests) {
std::sort(requests.begin(), requests.end(), [](const auto& a, const auto& b) {
if (a.time_received() != b.time_received()) {
return a.time_received() < b.time_received();
}
return a.pointer_id() < b.pointer_id();
});
}
class TouchListener : public futi::TouchInputListener {
public:
TouchListener() : binding_(this) {}
// |futi::TouchInputListener|
void ReportTouchInput(futi::TouchInputListenerReportTouchInputRequest request) override {
events_received_.push_back(std::move(request));
}
// Returns a client end bound to this object.
fidl::InterfaceHandle<futi::TouchInputListener> NewBinding() { return binding_.NewBinding(); }
const std::vector<futi::TouchInputListenerReportTouchInputRequest>& events_received() {
return events_received_;
}
std::vector<futi::TouchInputListenerReportTouchInputRequest> cloned_events_received() {
auto res = std::vector<futi::TouchInputListenerReportTouchInputRequest>();
for (auto& req : events_received_) {
futi::TouchInputListenerReportTouchInputRequest clone;
req.Clone(&clone);
res.push_back(std::move(clone));
}
return res;
}
void clear_events() { events_received_.clear(); }
bool LastEventReceivedMatchesPhase(fuchsia::ui::pointer::EventPhase phase) {
if (events_received_.empty()) {
return false;
}
const auto& last_event = events_received_.back();
const auto actual_phase = last_event.phase();
FX_LOGS(INFO) << "Expecting event at phase (" << static_cast<uint32_t>(phase) << ")";
FX_LOGS(INFO) << "Received event at phase (" << static_cast<uint32_t>(actual_phase) << ")";
return phase == actual_phase;
}
private:
fidl::Binding<futi::TouchInputListener> binding_;
std::vector<futi::TouchInputListenerReportTouchInputRequest> events_received_;
};
// Holds resources associated with a single puppet instance.
struct TouchPuppet {
fuchsia::ui::test::conformance::PuppetSyncPtr puppet_ptr;
TouchListener touch_listener;
};
using device_pixel_ratio = float;
class TouchConformanceTest : public ui_conformance_test_base::ConformanceTest,
public ::testing::WithParamInterface<device_pixel_ratio> {
public:
~TouchConformanceTest() override = default;
float DevicePixelRatio() const override { return GetParam(); }
void SetUp() override {
ui_conformance_test_base::ConformanceTest::SetUp();
// Register fake touch screen.
{
FX_LOGS(INFO) << "Connecting to input registry";
auto input_registry = ConnectSyncIntoRealm<futi::Registry>();
FX_LOGS(INFO) << "Registering fake touch screen";
futi::RegistryRegisterTouchScreenRequest request;
request.set_device(fake_touch_screen_.NewRequest());
request.set_coordinate_unit(futi::CoordinateUnit::PHYSICAL_PIXELS);
ASSERT_EQ(input_registry->RegisterTouchScreen(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_
<< ")";
}
}
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::TouchScreenSyncPtr fake_touch_screen_;
uint32_t display_width_ = 0;
uint32_t display_height_ = 0;
};
class SingleViewTouchConformanceTest : public TouchConformanceTest {
public:
~SingleViewTouchConformanceTest() override = default;
void SetUp() override {
TouchConformanceTest::SetUp();
fuchsia::ui::views::ViewCreationToken root_view_token;
// 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);
}
auto flatland = ConnectSyncIntoRealm<fuchsia::ui::composition::Flatland>();
auto keyboard = ConnectSyncIntoRealm<fuchsia::ui::input3::Keyboard>();
{
FX_LOGS(INFO) << "Create puppet under test";
fuchsia::ui::test::conformance::PuppetFactorySyncPtr puppet_factory;
ASSERT_EQ(LocalServiceDirectory()->Connect(puppet_factory.NewRequest(),
PUPPET_UNDER_TEST_FACTORY_SERVICE),
ZX_OK);
fuchsia::ui::test::conformance::PuppetFactoryCreateResponse resp;
puppet_ = std::make_unique<TouchPuppet>();
fuchsia::ui::test::conformance::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_touch_listener(puppet_->touch_listener.NewBinding());
creation_args.set_flatland_client(std::move(flatland));
creation_args.set_keyboard_client(std::move(keyboard));
creation_args.set_device_pixel_ratio(DevicePixelRatio());
ASSERT_EQ(puppet_factory->Create(std::move(creation_args), &resp), ZX_OK);
ASSERT_EQ(resp.result(), fuchsia::ui::test::conformance::Result::SUCCESS);
}
}
protected:
std::unique_ptr<TouchPuppet> puppet_;
};
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, SingleViewTouchConformanceTest,
::testing::Values(1.0, 2.0));
TEST_P(SingleViewTouchConformanceTest, SimpleTap) {
const auto kTapX = 3 * display_width_as_int() / 4;
const auto kTapY = display_height_as_int() / 4;
// Inject tap in the middle of the top-right quadrant.
futi::TouchScreenSimulateTapRequest tap_request;
tap_request.mutable_tap_location()->x = kTapX;
tap_request.mutable_tap_location()->y = kTapY;
FX_LOGS(INFO) << "Injecting tap at (" << kTapX << ", " << kTapY << ")";
fake_touch_screen_->SimulateTap(std::move(tap_request));
FX_LOGS(INFO) << "Waiting for touch event listener to receive response";
RunLoopUntil([this]() {
return this->puppet_->touch_listener.LastEventReceivedMatchesPhase(
fuchsia::ui::pointer::EventPhase::REMOVE);
});
const auto& events_received = this->puppet_->touch_listener.events_received();
ASSERT_EQ(events_received.size(), 2u);
// The puppet's view matches the display dimensions exactly, and the device
// pixel ratio is 1. Therefore, the puppet's logical coordinate space will
// match the physical coordinate space, so we expect the puppet to report
// the event at (kTapX, kTapY).
ExpectLocationAndPhase("[0]", events_received[0], DevicePixelRatio(), kTapX, kTapY,
fuchsia::ui::pointer::EventPhase::ADD, 1);
ExpectLocationAndPhase("[1]", events_received[1], DevicePixelRatio(), kTapX, kTapY,
fuchsia::ui::pointer::EventPhase::REMOVE, 1);
}
// Send input report contains 3 contacts and then release all of them to
// simultate multi touch down on touchscreen. UI client expects 3 touch
// add events, and 3 touch remove events.
TEST_P(SingleViewTouchConformanceTest, MultiTap) {
const int kTap1X = 3 * display_width_as_int() / 4;
const int kTapY = display_height_as_int() / 4;
const int kTap0X = kTap1X - 5;
const int kTap2X = kTap1X + 5;
futi::TouchScreenSimulateMultiTapRequest multi_tap_req;
multi_tap_req.mutable_tap_locations()->push_back({.x = kTap0X, .y = kTapY});
multi_tap_req.mutable_tap_locations()->push_back({.x = kTap1X, .y = kTapY});
multi_tap_req.mutable_tap_locations()->push_back({.x = kTap2X, .y = kTapY});
FX_LOGS(INFO) << "Injecting tap at (" << kTap0X << ", " << kTapY << ") (" << kTap1X << ", "
<< kTapY << ") (" << kTap2X << ", " << kTapY << ")";
fake_touch_screen_->SimulateMultiTap(std::move(multi_tap_req));
FX_LOGS(INFO) << "Waiting for touch event listener to receive response";
// There are 3 * touch down and 3 * touch remove, totally 6 events.
const size_t kExpectedEventCounts = 6;
RunLoopUntil([this]() {
return this->puppet_->touch_listener.events_received().size() >= kExpectedEventCounts;
});
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), kExpectedEventCounts);
SortTouchInputRequestByTimeAndTimestampAndPointerId(events_received);
ExpectLocationAndPhase("add [0]", events_received[0], DevicePixelRatio(), kTap0X, kTapY,
fuchsia::ui::pointer::EventPhase::ADD, 0);
ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTap1X, kTapY,
fuchsia::ui::pointer::EventPhase::ADD, 1);
ExpectLocationAndPhase("add [2]", events_received[2], DevicePixelRatio(), kTap2X, kTapY,
fuchsia::ui::pointer::EventPhase::ADD, 2);
ExpectLocationAndPhase("remove [0]", events_received[3], DevicePixelRatio(), kTap0X, kTapY,
fuchsia::ui::pointer::EventPhase::REMOVE, 0);
ExpectLocationAndPhase("remove [1]", events_received[4], DevicePixelRatio(), kTap1X, kTapY,
fuchsia::ui::pointer::EventPhase::REMOVE, 1);
ExpectLocationAndPhase("remove [2]", events_received[5], DevicePixelRatio(), kTap2X, kTapY,
fuchsia::ui::pointer::EventPhase::REMOVE, 2);
}
// 2 finger contact then move apart horizontally to simulate pinch zoom.
// UI Client expects touch add, touch change and touch remove for
// 2 fingers.
TEST_P(SingleViewTouchConformanceTest, Pinch) {
const int kTapX = 3 * display_width_as_int() / 4;
const int kTapY = display_height_as_int() / 4;
futi::TouchScreenSimulateMultiFingerGestureRequest pinch_req;
pinch_req.set_start_locations({fuchsia::math::Vec{.x = kTapX - 5, .y = kTapY},
fuchsia::math::Vec{.x = kTapX + 5, .y = kTapY}});
pinch_req.set_end_locations({fuchsia::math::Vec{.x = kTapX - 20, .y = kTapY},
fuchsia::math::Vec{.x = kTapX + 20, .y = kTapY}});
pinch_req.set_move_event_count(3);
pinch_req.set_finger_count(2);
FX_LOGS(INFO) << "Injecting pinch from (" << pinch_req.start_locations()[0].x << ", "
<< pinch_req.start_locations()[0].y << ") (" << pinch_req.start_locations()[1].x
<< ", " << pinch_req.start_locations()[1].y << ") to ("
<< pinch_req.end_locations()[0].x << ", " << pinch_req.end_locations()[0].y << ") ("
<< pinch_req.end_locations()[1].x << ", " << pinch_req.end_locations()[1].y
<< ") with move_event_count = " << pinch_req.move_event_count();
fake_touch_screen_->SimulateMultiFingerGesture(std::move(pinch_req));
FX_LOGS(INFO) << "Waiting for touch event listener to receive response";
// There are 2 * touch down + 4 * touch change + 2 * touch remove, totally 8 events.
const size_t kExpectedEventCounts = 8;
RunLoopUntil([this]() {
return this->puppet_->touch_listener.events_received().size() >= kExpectedEventCounts;
});
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), kExpectedEventCounts);
SortTouchInputRequestByTimeAndTimestampAndPointerId(events_received);
ExpectLocationAndPhase("add [0]", events_received[0], DevicePixelRatio(), kTapX - 5, kTapY,
fuchsia::ui::pointer::EventPhase::ADD, 0);
ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTapX + 5, kTapY,
fuchsia::ui::pointer::EventPhase::ADD, 1);
ExpectLocationAndPhase("change [0] 1", events_received[2], DevicePixelRatio(), kTapX - 10, kTapY,
fuchsia::ui::pointer::EventPhase::CHANGE, 0);
ExpectLocationAndPhase("change [1] 1", events_received[3], DevicePixelRatio(), kTapX + 10, kTapY,
fuchsia::ui::pointer::EventPhase::CHANGE, 1);
ExpectLocationAndPhase("change [0] 2", events_received[4], DevicePixelRatio(), kTapX - 15, kTapY,
fuchsia::ui::pointer::EventPhase::CHANGE, 0);
ExpectLocationAndPhase("change [1] 2", events_received[5], DevicePixelRatio(), kTapX + 15, kTapY,
fuchsia::ui::pointer::EventPhase::CHANGE, 1);
ExpectLocationAndPhase("remove [1]", events_received[6], DevicePixelRatio(), kTapX - 15, kTapY,
fuchsia::ui::pointer::EventPhase::REMOVE, 0);
ExpectLocationAndPhase("remove [2]", events_received[7], DevicePixelRatio(), kTapX + 15, kTapY,
fuchsia::ui::pointer::EventPhase::REMOVE, 1);
}
TEST_P(SingleViewTouchConformanceTest, TouchEventFields) {
const uint32_t kID = 1u;
const int kX = 3 * display_width_as_int() / 4;
const int kY = display_height_as_int() / 4;
const int64_t kHeight = 10;
const int64_t kWidth = 15;
const int64_t kPressure = 20;
fir::ContactInputReport contact;
contact.set_contact_id(kID);
contact.set_position_x(kX);
contact.set_position_y(kY);
// following fields are not passing to UI client yet.
contact.set_contact_height(kHeight);
contact.set_contact_width(kWidth);
contact.set_confidence(true);
contact.set_pressure(kPressure);
fir::TouchInputReport report;
report.mutable_contacts()->push_back(std::move(contact));
fake_touch_screen_->SimulateTouchEvent(std::move(report));
RunLoopUntil([this]() { return this->puppet_->touch_listener.events_received().size() >= 1; });
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), 1u);
ExpectLocationAndPhase("add [1]", events_received[0], DevicePixelRatio(), kX, kY,
fuchsia::ui::pointer::EventPhase::ADD, kID);
puppet_->touch_listener.clear_events();
}
// The test wants to ensure UI client will receive events in order:
// finger 1 down:
// 1. finger 1 add
//
// finger 2 down:
// 1. finger 1 change
// 2. finger 2 add
//
// keep 2 fingers down:
// finger 1/2 change <- in any order
//
// finger 2 up:
// 1. finger 1 update
// 2. finger 2 remove
//
// finger 1 up:
// 1. finger 1 remove
TEST_P(SingleViewTouchConformanceTest, OneFingerDownThenAnotherThenLift) {
const int kFinger1X = 3 * display_width_as_int() / 4;
const int kY = display_height_as_int() / 4;
const int kFinger2X = kFinger1X + 5;
auto build_contact = [](uint32_t id, int64_t x, int64_t y) {
fir::ContactInputReport contact;
contact.set_contact_id(id);
contact.set_position_x(x);
contact.set_position_y(y);
return contact;
};
{
fir::TouchInputReport finger1_down;
finger1_down.mutable_contacts()->push_back(build_contact(1u, kFinger1X, kY));
fake_touch_screen_->SimulateTouchEvent(std::move(finger1_down));
RunLoopUntil([this]() { return this->puppet_->touch_listener.events_received().size() >= 1; });
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), 1u);
ExpectLocationAndPhase("add [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY,
fuchsia::ui::pointer::EventPhase::ADD, 1);
puppet_->touch_listener.clear_events();
}
{
fir::TouchInputReport finger_1_2_down;
finger_1_2_down.mutable_contacts()->push_back(build_contact(1u, kFinger1X, kY));
finger_1_2_down.mutable_contacts()->push_back(build_contact(2u, kFinger2X, kY));
fake_touch_screen_->SimulateTouchEvent(std::move(finger_1_2_down));
RunLoopUntil([this]() { return this->puppet_->touch_listener.events_received().size() >= 2; });
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), 2u);
ExpectLocationAndPhase("change [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY,
fuchsia::ui::pointer::EventPhase::CHANGE, 1);
ExpectLocationAndPhase("add [2]", events_received[1], DevicePixelRatio(), kFinger2X, kY,
fuchsia::ui::pointer::EventPhase::ADD, 2);
puppet_->touch_listener.clear_events();
}
{
fir::TouchInputReport keep_finger_1_2_down;
keep_finger_1_2_down.mutable_contacts()->push_back(build_contact(2u, kFinger2X, kY));
keep_finger_1_2_down.mutable_contacts()->push_back(build_contact(1u, kFinger1X, kY));
fake_touch_screen_->SimulateTouchEvent(std::move(keep_finger_1_2_down));
RunLoopUntil([this]() { return this->puppet_->touch_listener.events_received().size() >= 2; });
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), 2u);
std::sort(events_received.begin(), events_received.end(),
[](const auto& a, const auto& b) { return a.pointer_id() < b.pointer_id(); });
ExpectLocationAndPhase("change [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY,
fuchsia::ui::pointer::EventPhase::CHANGE, 1);
ExpectLocationAndPhase("change [2]", events_received[1], DevicePixelRatio(), kFinger2X, kY,
fuchsia::ui::pointer::EventPhase::CHANGE, 2);
puppet_->touch_listener.clear_events();
}
{
fir::TouchInputReport finger_2_lift;
finger_2_lift.mutable_contacts()->push_back(build_contact(1u, kFinger1X, kY));
fake_touch_screen_->SimulateTouchEvent(std::move(finger_2_lift));
RunLoopUntil([this]() { return this->puppet_->touch_listener.events_received().size() >= 2; });
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), 2u);
ExpectLocationAndPhase("change [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY,
fuchsia::ui::pointer::EventPhase::CHANGE, 1);
ExpectLocationAndPhase("remove [2]", events_received[1], DevicePixelRatio(), kFinger2X, kY,
fuchsia::ui::pointer::EventPhase::REMOVE, 2);
puppet_->touch_listener.clear_events();
}
{
fir::TouchInputReport finger_1_lift;
fake_touch_screen_->SimulateTouchEvent(std::move(finger_1_lift));
RunLoopUntil([this]() { return this->puppet_->touch_listener.events_received().size() >= 1; });
auto events_received = this->puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), 1u);
ExpectLocationAndPhase("remove [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY,
fuchsia::ui::pointer::EventPhase::REMOVE, 1);
puppet_->touch_listener.clear_events();
}
}
class EmbeddedViewTouchConformanceTest : public TouchConformanceTest {
public:
~EmbeddedViewTouchConformanceTest() override = default;
void SetUp() override {
TouchConformanceTest::SetUp();
fuchsia::ui::views::ViewCreationToken root_view_token;
// 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);
}
{
FX_LOGS(INFO) << "Create parent puppet";
fuchsia::ui::test::conformance::PuppetFactorySyncPtr puppet_factory;
ASSERT_EQ(LocalServiceDirectory()->Connect(puppet_factory.NewRequest(),
AUXILIARY_PUPPET_FACTORY_SERVICE),
ZX_OK);
fuchsia::ui::test::conformance::PuppetFactoryCreateResponse resp;
parent_puppet_ = std::make_unique<TouchPuppet>();
auto flatland = ConnectSyncIntoRealm<fuchsia::ui::composition::Flatland>();
auto keyboard = ConnectSyncIntoRealm<fuchsia::ui::input3::Keyboard>();
fuchsia::ui::test::conformance::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_touch_listener(parent_puppet_->touch_listener.NewBinding());
creation_args.set_flatland_client(std::move(flatland));
creation_args.set_keyboard_client(std::move(keyboard));
creation_args.set_device_pixel_ratio(DevicePixelRatio());
ASSERT_EQ(puppet_factory->Create(std::move(creation_args), &resp), ZX_OK);
ASSERT_EQ(resp.result(), fuchsia::ui::test::conformance::Result::SUCCESS);
}
// Create child viewport.
fuchsia::ui::test::conformance::PuppetEmbedRemoteViewResponse embed_remote_view_response;
{
FX_LOGS(INFO) << "Creating child viewport";
const uint64_t kChildViewportId = 1u;
fuchsia::ui::test::conformance::PuppetEmbedRemoteViewRequest embed_remote_view_request;
embed_remote_view_request.set_id(kChildViewportId);
// set_size needs logical coordinators.
embed_remote_view_request.mutable_properties()->mutable_bounds()->set_size({
.width =
static_cast<uint32_t>(static_cast<float>(display_width_) / DevicePixelRatio() / 2.0),
.height =
static_cast<uint32_t>(static_cast<float>(display_height_) / DevicePixelRatio() / 2.0),
});
// set_origin needs logical coordinators.
embed_remote_view_request.mutable_properties()->mutable_bounds()->set_origin(
{.x = static_cast<int32_t>(static_cast<float>(display_width_) / DevicePixelRatio() / 2.0),
.y = static_cast<int32_t>(static_cast<float>(display_height_) / DevicePixelRatio() /
2.0)});
this->parent_puppet_->puppet_ptr->EmbedRemoteView(std::move(embed_remote_view_request),
&embed_remote_view_response);
}
// Create child view.
{
FX_LOGS(INFO) << "Creating child puppet";
fuchsia::ui::test::conformance::PuppetFactorySyncPtr puppet_factory;
ASSERT_EQ(LocalServiceDirectory()->Connect(puppet_factory.NewRequest(),
PUPPET_UNDER_TEST_FACTORY_SERVICE),
ZX_OK);
fuchsia::ui::test::conformance::PuppetFactoryCreateResponse resp;
child_puppet_ = std::make_unique<TouchPuppet>();
auto flatland = ConnectSyncIntoRealm<fuchsia::ui::composition::Flatland>();
auto keyboard = ConnectSyncIntoRealm<fuchsia::ui::input3::Keyboard>();
fuchsia::ui::test::conformance::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_touch_listener(child_puppet_->touch_listener.NewBinding());
creation_args.set_flatland_client(std::move(flatland));
creation_args.set_keyboard_client(std::move(keyboard));
creation_args.set_device_pixel_ratio(DevicePixelRatio());
ASSERT_EQ(puppet_factory->Create(std::move(creation_args), &resp), ZX_OK);
ASSERT_EQ(resp.result(), fuchsia::ui::test::conformance::Result::SUCCESS);
}
}
protected:
std::unique_ptr<TouchPuppet> parent_puppet_;
std::unique_ptr<TouchPuppet> child_puppet_;
};
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, EmbeddedViewTouchConformanceTest,
::testing::Values(1.0, 2.0));
TEST_P(EmbeddedViewTouchConformanceTest, EmbeddedViewTap) {
const auto kTapX = 3 * display_width_as_int() / 4;
const auto kTapY = 3 * display_height_as_int() / 4;
// Inject tap in the middle of the bottom-right quadrant.
futi::TouchScreenSimulateTapRequest tap_request;
tap_request.mutable_tap_location()->x = kTapX;
tap_request.mutable_tap_location()->y = kTapY;
FX_LOGS(INFO) << "Injecting tap at (" << kTapX << ", " << kTapY << ")";
fake_touch_screen_->SimulateTap(std::move(tap_request));
FX_LOGS(INFO) << "Waiting for child touch event listener to receive response";
RunLoopUntil([this]() {
return this->child_puppet_->touch_listener.LastEventReceivedMatchesPhase(
fuchsia::ui::pointer::EventPhase::REMOVE);
});
auto& events_received = this->child_puppet_->touch_listener.events_received();
ASSERT_EQ(events_received.size(), 2u);
// The child view's origin is in the center of the screen, so the center of
// the bottom-right quadrant is at (display_width_ / 4, display_height_ / 4)
// in the child's local coordinate space.
const double quarter_display_width = static_cast<double>(display_width_) / 4.f;
const double quarter_display_height = static_cast<double>(display_height_) / 4.f;
ExpectLocationAndPhase("[0]", events_received[0], DevicePixelRatio(), quarter_display_width,
quarter_display_height, fuchsia::ui::pointer::EventPhase::ADD, 1);
ExpectLocationAndPhase("[1]", events_received[1], DevicePixelRatio(), quarter_display_width,
quarter_display_height, fuchsia::ui::pointer::EventPhase::REMOVE, 1);
// The parent should not have received any pointer events.
EXPECT_TRUE(this->parent_puppet_->touch_listener.events_received().empty());
}
// Send input report contains 3 contacts and then release all of them to
// simultate multi touch down on touchscreen. Embedded view expects 3 touch
// add events, and 3 touch remove events. And no events on parenet view.
TEST_P(EmbeddedViewTouchConformanceTest, EmbeddedViewMultiTap) {
const int kTap1X = 3 * display_width_as_int() / 4;
const int kTapY = 3 * display_height_as_int() / 4;
const int kTap0X = kTap1X - 5;
const int kTap2X = kTap1X + 5;
futi::TouchScreenSimulateMultiTapRequest multi_tap_req;
multi_tap_req.mutable_tap_locations()->push_back({.x = kTap0X, .y = kTapY});
multi_tap_req.mutable_tap_locations()->push_back({.x = kTap1X, .y = kTapY});
multi_tap_req.mutable_tap_locations()->push_back({.x = kTap2X, .y = kTapY});
FX_LOGS(INFO) << "Injecting tap at (" << kTap0X << ", " << kTapY << ") (" << kTap1X << ", "
<< kTapY << ") (" << kTap2X << ", " << kTapY << ")";
fake_touch_screen_->SimulateMultiTap(std::move(multi_tap_req));
FX_LOGS(INFO) << "Waiting for touch event listener to receive response";
// There are 3 * touch down and 3 * touch remove, totally 6 events.
const size_t kExpectedEventCounts = 6;
RunLoopUntil([this]() {
return this->child_puppet_->touch_listener.events_received().size() >= kExpectedEventCounts;
});
auto events_received = this->child_puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), kExpectedEventCounts);
const double kTap1XChildView = static_cast<double>(display_width_) / 4.f;
const double kTapYChildView = static_cast<double>(display_height_) / 4.f;
const double kTap0XChildView = kTap1XChildView - 5;
const double kTap2XChildView = kTap1XChildView + 5;
SortTouchInputRequestByTimeAndTimestampAndPointerId(events_received);
ExpectLocationAndPhase("add [0]", events_received[0], DevicePixelRatio(), kTap0XChildView,
kTapYChildView, fuchsia::ui::pointer::EventPhase::ADD, 0);
ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTap1XChildView,
kTapYChildView, fuchsia::ui::pointer::EventPhase::ADD, 1);
ExpectLocationAndPhase("add [2]", events_received[2], DevicePixelRatio(), kTap2XChildView,
kTapYChildView, fuchsia::ui::pointer::EventPhase::ADD, 2);
ExpectLocationAndPhase("remove [1]", events_received[3], DevicePixelRatio(), kTap0XChildView,
kTapYChildView, fuchsia::ui::pointer::EventPhase::REMOVE, 0);
ExpectLocationAndPhase("remove [2]", events_received[4], DevicePixelRatio(), kTap1XChildView,
kTapYChildView, fuchsia::ui::pointer::EventPhase::REMOVE, 1);
ExpectLocationAndPhase("remove [3]", events_received[5], DevicePixelRatio(), kTap2XChildView,
kTapYChildView, fuchsia::ui::pointer::EventPhase::REMOVE, 2);
// The parent should not have received any pointer events.
EXPECT_TRUE(this->parent_puppet_->touch_listener.events_received().empty());
}
// 2 finger contact then move apart horizontally to simulate pinch zoom.
// Embededd view expects touch add, touch change and touch remove for
// 2 fingers.
TEST_P(EmbeddedViewTouchConformanceTest, Pinch) {
const int kTapX = 3 * display_width_as_int() / 4;
const int kTapY = 3 * display_height_as_int() / 4;
futi::TouchScreenSimulateMultiFingerGestureRequest pinch_req;
pinch_req.set_start_locations({fuchsia::math::Vec{.x = kTapX - 5, .y = kTapY},
fuchsia::math::Vec{.x = kTapX + 5, .y = kTapY}});
pinch_req.set_end_locations({fuchsia::math::Vec{.x = kTapX - 20, .y = kTapY},
fuchsia::math::Vec{.x = kTapX + 20, .y = kTapY}});
pinch_req.set_move_event_count(3);
pinch_req.set_finger_count(2);
FX_LOGS(INFO) << "Injecting pinch from (" << pinch_req.start_locations()[0].x << ", "
<< pinch_req.start_locations()[0].y << ") (" << pinch_req.start_locations()[1].x
<< ", " << pinch_req.start_locations()[1].y << ") to ("
<< pinch_req.end_locations()[0].x << ", " << pinch_req.end_locations()[0].y << ") ("
<< pinch_req.end_locations()[1].x << ", " << pinch_req.end_locations()[1].y
<< ") with move_event_count = " << pinch_req.move_event_count();
fake_touch_screen_->SimulateMultiFingerGesture(std::move(pinch_req));
FX_LOGS(INFO) << "Waiting for touch event listener to receive response";
// There are 2 * touch down + 4 * touch change + 2 * touch remove, totally 8 events.
const size_t kExpectedEventCounts = 8;
RunLoopUntil([this]() {
return this->child_puppet_->touch_listener.events_received().size() >= kExpectedEventCounts;
});
auto events_received = this->child_puppet_->touch_listener.cloned_events_received();
ASSERT_EQ(events_received.size(), kExpectedEventCounts);
const int kTapXChildView = display_width_as_int() / 4;
const int kTapYChildView = display_height_as_int() / 4;
SortTouchInputRequestByTimeAndTimestampAndPointerId(events_received);
ExpectLocationAndPhase("add [0]", events_received[0], DevicePixelRatio(), kTapXChildView - 5,
kTapYChildView, fuchsia::ui::pointer::EventPhase::ADD, 0);
ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTapXChildView + 5,
kTapYChildView, fuchsia::ui::pointer::EventPhase::ADD, 1);
ExpectLocationAndPhase("change [0] 1", events_received[2], DevicePixelRatio(),
kTapXChildView - 10, kTapYChildView,
fuchsia::ui::pointer::EventPhase::CHANGE, 0);
ExpectLocationAndPhase("change [1] 1", events_received[3], DevicePixelRatio(),
kTapXChildView + 10, kTapYChildView,
fuchsia::ui::pointer::EventPhase::CHANGE, 1);
ExpectLocationAndPhase("change [0] 2", events_received[4], DevicePixelRatio(),
kTapXChildView - 15, kTapYChildView,
fuchsia::ui::pointer::EventPhase::CHANGE, 0);
ExpectLocationAndPhase("change [1] 2", events_received[5], DevicePixelRatio(),
kTapXChildView + 15, kTapYChildView,
fuchsia::ui::pointer::EventPhase::CHANGE, 1);
ExpectLocationAndPhase("remove [0]", events_received[6], DevicePixelRatio(), kTapXChildView - 15,
kTapYChildView, fuchsia::ui::pointer::EventPhase::REMOVE, 0);
ExpectLocationAndPhase("remove [1]", events_received[7], DevicePixelRatio(), kTapXChildView + 15,
kTapYChildView, fuchsia::ui::pointer::EventPhase::REMOVE, 1);
// The parent should not have received any pointer events.
EXPECT_TRUE(this->parent_puppet_->touch_listener.events_received().empty());
}
} // namespace ui_conformance_testing