| // 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 <fidl/fuchsia.input.report/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.composition/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.display.singleton/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.input3/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.pointer/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.test.conformance/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.test.input/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.test.scene/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.views/cpp/fidl.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/fidl/cpp/channel.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/ui/scenic/cpp/view_creation_tokens.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <src/ui/testing/util/fidl_cpp_helpers.h> |
| #include <src/ui/testing/util/zxtest_helpers.h> |
| #include <src/ui/tests/conformance_input_tests/conformance-test-base.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace ui_conformance_testing { |
| |
| namespace futi = fuchsia_ui_test_input; |
| namespace fir = fuchsia_input_report; |
| namespace futc = fuchsia_ui_test_conformance; |
| namespace fup = fuchsia_ui_pointer; |
| |
| const std::string PUPPET_UNDER_TEST_FACTORY_SERVICE = "/svc/puppet-under-test-factory-service"; |
| const std::string AUXILIARY_PUPPET_FACTORY_SERVICE = "/svc/auxiliary-puppet-factory-service"; |
| |
| // 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; |
| |
| // 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 fidl::Server<futi::TouchInputListener> { |
| public: |
| // |futi::TouchInputListener| |
| void ReportTouchInput(ReportTouchInputRequest& request, |
| ReportTouchInputCompleter::Sync& completer) override { |
| events_received_.push_back(std::move(request)); |
| } |
| |
| fidl::ClientEnd<futi::TouchInputListener> ServeAndGetClientEnd(async_dispatcher_t* dispatcher) { |
| auto [client_end, server_end] = fidl::Endpoints<futi::TouchInputListener>::Create(); |
| binding_.AddBinding(dispatcher, std::move(server_end), this, fidl::kIgnoreBindingClosure); |
| return std::move(client_end); |
| } |
| |
| 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); |
| res.push_back(std::move(clone)); |
| } |
| |
| return res; |
| } |
| |
| void clear_events() { events_received_.clear(); } |
| |
| bool LastEventReceivedMatchesPhase(fup::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.value()) |
| << ")"; |
| |
| return phase == actual_phase; |
| } |
| |
| private: |
| fidl::ServerBindingGroup<futi::TouchInputListener> binding_; |
| std::vector<futi::TouchInputListenerReportTouchInputRequest> events_received_; |
| }; |
| |
| // Holds resources associated with a single puppet instance. |
| struct TouchPuppet { |
| fidl::SyncClient<futc::Puppet> client; |
| TouchListener touch_listener; |
| }; |
| |
| using device_pixel_ratio = float; |
| |
| class TouchConformanceTest : public ui_conformance_test_base::ConformanceTest, |
| public zxtest::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"; |
| auto [client_end, server_end] = fidl::Endpoints<futi::TouchScreen>::Create(); |
| fake_touch_screen_ = fidl::SyncClient(std::move(client_end)); |
| |
| futi::RegistryRegisterTouchScreenAndGetDeviceInfoRequest request; |
| request.device(std::move(server_end)); |
| request.coordinate_unit(futi::CoordinateUnit::kPhysicalPixels); |
| auto res = input_registry->RegisterTouchScreenAndGetDeviceInfo(std::move(request)); |
| ZX_ASSERT_OK(res); |
| fake_touch_screen_device_id_ = res->device_id().value(); |
| } |
| |
| // Get display dimensions. |
| { |
| FX_LOGS(INFO) << "Reading display dimensions"; |
| auto display_info = ConnectSyncIntoRealm<fuchsia_ui_display_singleton::Info>(); |
| |
| auto res = display_info->GetMetrics(); |
| ZX_ASSERT_OK(res); |
| |
| display_width_ = res.value().info().extent_in_px()->width(); |
| display_height_ = res.value().info().extent_in_px()->height(); |
| |
| FX_LOGS(INFO) << "Received display dimensions (" << display_width_ << ", " << display_height_ |
| << ")"; |
| } |
| } |
| |
| // 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, |
| fup::EventPhase expected_phase, |
| const uint32_t expected_pointer_id) const { |
| SCOPED_TRACE(scoped_message); |
| auto pixel_scale = e.device_pixel_ratio().has_value() ? e.device_pixel_ratio().value() : 1; |
| EXPECT_NEAR(static_cast<double>(expected_pixel_ratio), pixel_scale, kEpsilon); |
| auto actual_x = pixel_scale * e.local_x().value(); |
| auto actual_y = pixel_scale * e.local_y().value(); |
| 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()); |
| EXPECT_EQ(fake_touch_screen_device_id_, e.device_id().value()); |
| } |
| |
| protected: |
| int32_t display_width_as_int() const { return static_cast<int32_t>(display_width_); } |
| int32_t display_height_as_int() const { return static_cast<int32_t>(display_height_); } |
| |
| fidl::SyncClient<futi::TouchScreen> fake_touch_screen_; |
| uint32_t fake_touch_screen_device_id_; |
| 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::cpp::ViewCreationTokenPair::New(); |
| req.viewport_creation_token(std::move(viewport_token)); |
| ZX_ASSERT_OK(controller->PresentClientView(std::move(req))); |
| root_view_token = std::move(view_token); |
| } |
| |
| { |
| FX_LOGS(INFO) << "Create puppet under test"; |
| auto puppet_factory_connect = |
| component::Connect<futc::PuppetFactory>(PUPPET_UNDER_TEST_FACTORY_SERVICE); |
| ZX_ASSERT_OK(puppet_factory_connect); |
| |
| puppet_factory_ = fidl::SyncClient(std::move(puppet_factory_connect.value())); |
| |
| auto [puppet_client_end, puppet_server_end] = fidl::Endpoints<futc::Puppet>::Create(); |
| puppet_.client = fidl::SyncClient(std::move(puppet_client_end)); |
| auto touch_listener_client_end = puppet_.touch_listener.ServeAndGetClientEnd(dispatcher()); |
| auto flatland = ConnectIntoRealm<fuchsia_ui_composition::Flatland>(); |
| auto keyboard = ConnectIntoRealm<fuchsia_ui_input3::Keyboard>(); |
| |
| futc::PuppetCreationArgs creation_args; |
| creation_args.server_end(std::move(puppet_server_end)); |
| creation_args.view_token(std::move(root_view_token)); |
| creation_args.flatland_client(std::move(flatland)); |
| creation_args.keyboard_client(std::move(keyboard)); |
| creation_args.touch_listener(std::move(touch_listener_client_end)); |
| creation_args.device_pixel_ratio(DevicePixelRatio()); |
| |
| auto res = puppet_factory_->Create(std::move(creation_args)); |
| ZX_ASSERT_OK(res); |
| ASSERT_EQ(res.value().result(), futc::Result::kSuccess); |
| } |
| } |
| |
| protected: |
| TouchPuppet puppet_; |
| |
| private: |
| fidl::SyncClient<futc::PuppetFactory> puppet_factory_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(/*no prefix*/, SingleViewTouchConformanceTest, ::zxtest::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.tap_location() = {kTapX, kTapY}; |
| FX_LOGS(INFO) << "Injecting tap at (" << kTapX << ", " << kTapY << ")"; |
| fake_touch_screen_->SimulateTap(tap_request); |
| |
| FX_LOGS(INFO) << "Waiting for touch event listener to receive response"; |
| RunLoopUntil([&]() { |
| return puppet_.touch_listener.LastEventReceivedMatchesPhase(fup::EventPhase::kRemove); |
| }); |
| |
| const auto& events_received = 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, |
| fup::EventPhase::kAdd, 1); |
| ExpectLocationAndPhase("[1]", events_received[1], DevicePixelRatio(), kTapX, kTapY, |
| fup::EventPhase::kRemove, 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.tap_locations() = {{kTap0X, kTapY}, {kTap1X, kTapY}, {kTap2X, kTapY}}; |
| FX_LOGS(INFO) << "Injecting tap at (" << kTap0X << ", " << kTapY << ") (" << kTap1X << ", " |
| << kTapY << ") (" << kTap2X << ", " << kTapY << ")"; |
| fake_touch_screen_->SimulateMultiTap(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( |
| [&]() { return puppet_.touch_listener.events_received().size() >= kExpectedEventCounts; }); |
| |
| auto events_received = puppet_.touch_listener.cloned_events_received(); |
| ASSERT_EQ(events_received.size(), kExpectedEventCounts); |
| |
| SortTouchInputRequestByTimeAndTimestampAndPointerId(events_received); |
| |
| ExpectLocationAndPhase("add [0]", events_received[0], DevicePixelRatio(), kTap0X, kTapY, |
| fup::EventPhase::kAdd, 0); |
| ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTap1X, kTapY, |
| fup::EventPhase::kAdd, 1); |
| ExpectLocationAndPhase("add [2]", events_received[2], DevicePixelRatio(), kTap2X, kTapY, |
| fup::EventPhase::kAdd, 2); |
| |
| ExpectLocationAndPhase("remove [0]", events_received[3], DevicePixelRatio(), kTap0X, kTapY, |
| fup::EventPhase::kRemove, 0); |
| ExpectLocationAndPhase("remove [1]", events_received[4], DevicePixelRatio(), kTap1X, kTapY, |
| fup::EventPhase::kRemove, 1); |
| ExpectLocationAndPhase("remove [2]", events_received[5], DevicePixelRatio(), kTap2X, kTapY, |
| fup::EventPhase::kRemove, 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.start_locations() = {fuchsia_math::Vec(kTapX - 5, kTapY), |
| fuchsia_math::Vec(kTapX + 5, kTapY)}; |
| pinch_req.end_locations() = {fuchsia_math::Vec(kTapX - 20, kTapY), |
| fuchsia_math::Vec(kTapX + 20, kTapY)}; |
| pinch_req.move_event_count(3); |
| pinch_req.finger_count(2); |
| FX_LOGS(INFO) << "Injecting pinch from (" << pinch_req.start_locations().value()[0].x() << ", " |
| << pinch_req.start_locations().value()[0].y() << ") (" |
| << pinch_req.start_locations().value()[1].x() << ", " |
| << pinch_req.start_locations().value()[1].y() << ") to (" |
| << pinch_req.end_locations().value()[0].x() << ", " |
| << pinch_req.end_locations().value()[0].y() << ") (" |
| << pinch_req.end_locations().value()[1].x() << ", " |
| << pinch_req.end_locations().value()[1].y() |
| << ") with move_event_count = " << pinch_req.move_event_count().value(); |
| fake_touch_screen_->SimulateMultiFingerGesture(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( |
| [&]() { return puppet_.touch_listener.events_received().size() >= kExpectedEventCounts; }); |
| |
| auto events_received = 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, |
| fup::EventPhase::kAdd, 0); |
| ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTapX + 5, kTapY, |
| fup::EventPhase::kAdd, 1); |
| |
| ExpectLocationAndPhase("change [0] 1", events_received[2], DevicePixelRatio(), kTapX - 10, kTapY, |
| fup::EventPhase::kChange, 0); |
| ExpectLocationAndPhase("change [1] 1", events_received[3], DevicePixelRatio(), kTapX + 10, kTapY, |
| fup::EventPhase::kChange, 1); |
| |
| ExpectLocationAndPhase("change [0] 2", events_received[4], DevicePixelRatio(), kTapX - 15, kTapY, |
| fup::EventPhase::kChange, 0); |
| ExpectLocationAndPhase("change [1] 2", events_received[5], DevicePixelRatio(), kTapX + 15, kTapY, |
| fup::EventPhase::kChange, 1); |
| |
| ExpectLocationAndPhase("remove [1]", events_received[6], DevicePixelRatio(), kTapX - 15, kTapY, |
| fup::EventPhase::kRemove, 0); |
| ExpectLocationAndPhase("remove [2]", events_received[7], DevicePixelRatio(), kTapX + 15, kTapY, |
| fup::EventPhase::kRemove, 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.contact_id(kID); |
| contact.position_x(kX); |
| contact.position_y(kY); |
| |
| // following fields are not passing to UI client yet. |
| contact.contact_height(kHeight); |
| contact.contact_width(kWidth); |
| contact.confidence(true); |
| contact.pressure(kPressure); |
| |
| fir::TouchInputReport report; |
| report.contacts() = {std::move(contact)}; |
| |
| fake_touch_screen_->SimulateTouchEvent(std::move(report)); |
| |
| RunLoopUntil([&]() { return puppet_.touch_listener.events_received().size() >= 1; }); |
| |
| auto events_received = puppet_.touch_listener.cloned_events_received(); |
| ASSERT_EQ(events_received.size(), 1u); |
| |
| ExpectLocationAndPhase("add [1]", events_received[0], DevicePixelRatio(), kX, kY, |
| fup::EventPhase::kAdd, 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.contact_id(id); |
| contact.position_x(x); |
| contact.position_y(y); |
| return contact; |
| }; |
| |
| { |
| fir::TouchInputReport finger1_down; |
| finger1_down.contacts() = {build_contact(1u, kFinger1X, kY)}; |
| |
| fake_touch_screen_->SimulateTouchEvent(std::move(finger1_down)); |
| |
| RunLoopUntil([&]() { return puppet_.touch_listener.events_received().size() >= 1; }); |
| |
| auto events_received = puppet_.touch_listener.cloned_events_received(); |
| ASSERT_EQ(events_received.size(), 1u); |
| |
| ExpectLocationAndPhase("add [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY, |
| fup::EventPhase::kAdd, 1); |
| |
| puppet_.touch_listener.clear_events(); |
| } |
| |
| { |
| fir::TouchInputReport finger_1_2_down; |
| finger_1_2_down.contacts() = { |
| build_contact(1u, kFinger1X, kY), |
| build_contact(2u, kFinger2X, kY), |
| }; |
| |
| fake_touch_screen_->SimulateTouchEvent(std::move(finger_1_2_down)); |
| |
| RunLoopUntil([&]() { return puppet_.touch_listener.events_received().size() >= 2; }); |
| |
| auto events_received = puppet_.touch_listener.cloned_events_received(); |
| ASSERT_EQ(events_received.size(), 2u); |
| |
| ExpectLocationAndPhase("change [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY, |
| fup::EventPhase::kChange, 1); |
| ExpectLocationAndPhase("add [2]", events_received[1], DevicePixelRatio(), kFinger2X, kY, |
| fup::EventPhase::kAdd, 2); |
| |
| puppet_.touch_listener.clear_events(); |
| } |
| |
| { |
| fir::TouchInputReport keep_finger_1_2_down; |
| keep_finger_1_2_down.contacts() = { |
| build_contact(1u, kFinger1X, kY), |
| build_contact(2u, kFinger2X, kY), |
| }; |
| |
| fake_touch_screen_->SimulateTouchEvent(std::move(keep_finger_1_2_down)); |
| |
| RunLoopUntil([&]() { return puppet_.touch_listener.events_received().size() >= 2; }); |
| |
| auto events_received = 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, |
| fup::EventPhase::kChange, 1); |
| ExpectLocationAndPhase("change [2]", events_received[1], DevicePixelRatio(), kFinger2X, kY, |
| fup::EventPhase::kChange, 2); |
| |
| puppet_.touch_listener.clear_events(); |
| } |
| |
| { |
| fir::TouchInputReport finger_2_lift; |
| finger_2_lift.contacts() = {build_contact(1u, kFinger1X, kY)}; |
| |
| fake_touch_screen_->SimulateTouchEvent(std::move(finger_2_lift)); |
| |
| RunLoopUntil([&]() { return puppet_.touch_listener.events_received().size() >= 2; }); |
| |
| auto events_received = puppet_.touch_listener.cloned_events_received(); |
| ASSERT_EQ(events_received.size(), 2u); |
| |
| ExpectLocationAndPhase("change [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY, |
| fup::EventPhase::kChange, 1); |
| ExpectLocationAndPhase("remove [2]", events_received[1], DevicePixelRatio(), kFinger2X, kY, |
| fup::EventPhase::kRemove, 2); |
| |
| puppet_.touch_listener.clear_events(); |
| } |
| |
| { |
| fir::TouchInputReport finger_1_lift; |
| |
| fake_touch_screen_->SimulateTouchEvent(std::move(finger_1_lift)); |
| |
| RunLoopUntil([&]() { return puppet_.touch_listener.events_received().size() >= 1; }); |
| |
| auto events_received = puppet_.touch_listener.cloned_events_received(); |
| ASSERT_EQ(events_received.size(), 1u); |
| |
| ExpectLocationAndPhase("remove [1]", events_received[0], DevicePixelRatio(), kFinger1X, kY, |
| fup::EventPhase::kRemove, 1); |
| |
| puppet_.touch_listener.clear_events(); |
| } |
| } |
| |
| class EmbeddedViewTouchConformanceTest : public TouchConformanceTest { |
| public: |
| 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::cpp::ViewCreationTokenPair::New(); |
| req.viewport_creation_token(std::move(viewport_token)); |
| ZX_ASSERT_OK(controller->PresentClientView(std::move(req))); |
| root_view_token = std::move(view_token); |
| } |
| |
| { |
| FX_LOGS(INFO) << "Create parent puppet"; |
| |
| auto puppet_factory_connect = |
| component::Connect<futc::PuppetFactory>(AUXILIARY_PUPPET_FACTORY_SERVICE); |
| ZX_ASSERT_OK(puppet_factory_connect); |
| |
| parent_puppet_factory_ = fidl::SyncClient(std::move(puppet_factory_connect.value())); |
| |
| auto [puppet_client_end, puppet_server_end] = fidl::Endpoints<futc::Puppet>::Create(); |
| parent_puppet_.client = fidl::SyncClient(std::move(puppet_client_end)); |
| auto touch_listener_client_end = |
| parent_puppet_.touch_listener.ServeAndGetClientEnd(dispatcher()); |
| auto flatland = ConnectIntoRealm<fuchsia_ui_composition::Flatland>(); |
| auto keyboard = ConnectIntoRealm<fuchsia_ui_input3::Keyboard>(); |
| |
| futc::PuppetCreationArgs creation_args; |
| creation_args.server_end(std::move(puppet_server_end)); |
| creation_args.view_token(std::move(root_view_token)); |
| creation_args.flatland_client(std::move(flatland)); |
| creation_args.keyboard_client(std::move(keyboard)); |
| creation_args.touch_listener(std::move(touch_listener_client_end)); |
| creation_args.device_pixel_ratio(DevicePixelRatio()); |
| |
| auto res = parent_puppet_factory_->Create(std::move(creation_args)); |
| ZX_ASSERT_OK(res); |
| ASSERT_EQ(res.value().result(), futc::Result::kSuccess); |
| } |
| |
| // Create child viewport. |
| fuchsia_ui_views::ViewCreationToken view_creation_token; |
| { |
| FX_LOGS(INFO) << "Creating child viewport"; |
| const uint64_t kChildViewportId = 1u; |
| |
| futc::ContentBounds bounds; |
| bounds.size() = { |
| static_cast<uint32_t>(static_cast<float>(display_width_) / DevicePixelRatio() / 2.0), |
| static_cast<uint32_t>(static_cast<float>(display_height_) / DevicePixelRatio() / 2.0)}; |
| bounds.origin() = { |
| static_cast<int32_t>(static_cast<float>(display_width_) / DevicePixelRatio() / 2.0), |
| static_cast<int32_t>(static_cast<float>(display_height_) / DevicePixelRatio() / 2.0), |
| }; |
| futc::EmbeddedViewProperties properties({ |
| .bounds = std::move(bounds), |
| }); |
| futc::PuppetEmbedRemoteViewRequest embed_remote_view_request({ |
| .id = kChildViewportId, |
| .properties = std::move(properties), |
| }); |
| auto res = parent_puppet_.client->EmbedRemoteView(embed_remote_view_request); |
| ZX_ASSERT_OK(res); |
| view_creation_token = std::move(res->view_creation_token()->value()); |
| } |
| |
| // Create child view. |
| { |
| FX_LOGS(INFO) << "Creating child puppet"; |
| auto puppet_factory_connect = |
| component::Connect<futc::PuppetFactory>(AUXILIARY_PUPPET_FACTORY_SERVICE); |
| ZX_ASSERT_OK(puppet_factory_connect); |
| |
| child_puppet_factory_ = fidl::SyncClient(std::move(puppet_factory_connect.value())); |
| |
| auto [puppet_client_end, puppet_server_end] = fidl::Endpoints<futc::Puppet>::Create(); |
| child_puppet_.client = fidl::SyncClient(std::move(puppet_client_end)); |
| auto touch_listener_client_end = |
| child_puppet_.touch_listener.ServeAndGetClientEnd(dispatcher()); |
| auto flatland = ConnectIntoRealm<fuchsia_ui_composition::Flatland>(); |
| auto keyboard = ConnectIntoRealm<fuchsia_ui_input3::Keyboard>(); |
| |
| futc::PuppetCreationArgs creation_args; |
| creation_args.server_end(std::move(puppet_server_end)); |
| creation_args.view_token(std::move(view_creation_token)); |
| creation_args.flatland_client(std::move(flatland)); |
| creation_args.keyboard_client(std::move(keyboard)); |
| creation_args.touch_listener(std::move(touch_listener_client_end)); |
| creation_args.device_pixel_ratio(DevicePixelRatio()); |
| |
| auto res = child_puppet_factory_->Create(std::move(creation_args)); |
| ZX_ASSERT_OK(res); |
| ASSERT_EQ(res.value().result(), futc::Result::kSuccess); |
| } |
| } |
| |
| protected: |
| TouchPuppet parent_puppet_; |
| TouchPuppet child_puppet_; |
| |
| private: |
| fidl::SyncClient<futc::PuppetFactory> parent_puppet_factory_; |
| fidl::SyncClient<futc::PuppetFactory> child_puppet_factory_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(/*no prefix*/, EmbeddedViewTouchConformanceTest, |
| ::zxtest::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.tap_location() = {kTapX, kTapY}; |
| FX_LOGS(INFO) << "Injecting tap at (" << kTapX << ", " << kTapY << ")"; |
| fake_touch_screen_->SimulateTap(tap_request); |
| |
| FX_LOGS(INFO) << "Waiting for child touch event listener to receive response"; |
| |
| RunLoopUntil([&]() { |
| return child_puppet_.touch_listener.LastEventReceivedMatchesPhase(fup::EventPhase::kRemove); |
| }); |
| |
| auto& events_received = 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, fup::EventPhase::kAdd, 1); |
| ExpectLocationAndPhase("[1]", events_received[1], DevicePixelRatio(), quarter_display_width, |
| quarter_display_height, fup::EventPhase::kRemove, 1); |
| |
| // The parent should not have received any pointer events. |
| EXPECT_TRUE(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.tap_locations() = {{kTap0X, kTapY}, {kTap1X, kTapY}, {kTap2X, kTapY}}; |
| FX_LOGS(INFO) << "Injecting tap at (" << kTap0X << ", " << kTapY << ") (" << kTap1X << ", " |
| << kTapY << ") (" << kTap2X << ", " << kTapY << ")"; |
| fake_touch_screen_->SimulateMultiTap(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([&]() { |
| return child_puppet_.touch_listener.events_received().size() >= kExpectedEventCounts; |
| }); |
| |
| auto events_received = 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, fup::EventPhase::kAdd, 0); |
| ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTap1XChildView, |
| kTapYChildView, fup::EventPhase::kAdd, 1); |
| ExpectLocationAndPhase("add [2]", events_received[2], DevicePixelRatio(), kTap2XChildView, |
| kTapYChildView, fup::EventPhase::kAdd, 2); |
| |
| ExpectLocationAndPhase("remove [1]", events_received[3], DevicePixelRatio(), kTap0XChildView, |
| kTapYChildView, fup::EventPhase::kRemove, 0); |
| ExpectLocationAndPhase("remove [2]", events_received[4], DevicePixelRatio(), kTap1XChildView, |
| kTapYChildView, fup::EventPhase::kRemove, 1); |
| ExpectLocationAndPhase("remove [3]", events_received[5], DevicePixelRatio(), kTap2XChildView, |
| kTapYChildView, fup::EventPhase::kRemove, 2); |
| |
| // The parent should not have received any pointer events. |
| EXPECT_TRUE(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.start_locations() = {fuchsia_math::Vec{kTapX - 5, kTapY}, |
| fuchsia_math::Vec{kTapX + 5, kTapY}}; |
| pinch_req.end_locations() = {fuchsia_math::Vec{kTapX - 20, kTapY}, |
| fuchsia_math::Vec{kTapX + 20, kTapY}}; |
| pinch_req.move_event_count(3); |
| pinch_req.finger_count(2); |
| |
| FX_LOGS(INFO) << "Injecting pinch from (" << pinch_req.start_locations().value()[0].x() << ", " |
| << pinch_req.start_locations().value()[0].y() << ") (" |
| << pinch_req.start_locations().value()[1].x() << ", " |
| << pinch_req.start_locations().value()[1].y() << ") to (" |
| << pinch_req.end_locations().value()[0].x() << ", " |
| << pinch_req.end_locations().value()[0].y() << ") (" |
| << pinch_req.end_locations().value()[1].x() << ", " |
| << pinch_req.end_locations().value()[1].y() |
| << ") with move_event_count = " << pinch_req.move_event_count().value(); |
| fake_touch_screen_->SimulateMultiFingerGesture(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([&]() { |
| return child_puppet_.touch_listener.events_received().size() >= kExpectedEventCounts; |
| }); |
| |
| auto events_received = 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, fup::EventPhase::kAdd, 0); |
| ExpectLocationAndPhase("add [1]", events_received[1], DevicePixelRatio(), kTapXChildView + 5, |
| kTapYChildView, fup::EventPhase::kAdd, 1); |
| |
| ExpectLocationAndPhase("change [0] 1", events_received[2], DevicePixelRatio(), |
| kTapXChildView - 10, kTapYChildView, fup::EventPhase::kChange, 0); |
| ExpectLocationAndPhase("change [1] 1", events_received[3], DevicePixelRatio(), |
| kTapXChildView + 10, kTapYChildView, fup::EventPhase::kChange, 1); |
| |
| ExpectLocationAndPhase("change [0] 2", events_received[4], DevicePixelRatio(), |
| kTapXChildView - 15, kTapYChildView, fup::EventPhase::kChange, 0); |
| ExpectLocationAndPhase("change [1] 2", events_received[5], DevicePixelRatio(), |
| kTapXChildView + 15, kTapYChildView, fup::EventPhase::kChange, 1); |
| |
| ExpectLocationAndPhase("remove [0]", events_received[6], DevicePixelRatio(), kTapXChildView - 15, |
| kTapYChildView, fup::EventPhase::kRemove, 0); |
| ExpectLocationAndPhase("remove [1]", events_received[7], DevicePixelRatio(), kTapXChildView + 15, |
| kTapYChildView, fup::EventPhase::kRemove, 1); |
| |
| // The parent should not have received any pointer events. |
| EXPECT_TRUE(parent_puppet_.touch_listener.events_received().empty()); |
| } |
| |
| } // namespace ui_conformance_testing |