| // Copyright 2013 The Flutter 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/pointer/cpp/fidl.h> |
| #include <gtest/gtest.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| |
| #include <array> |
| #include <optional> |
| #include <vector> |
| |
| #include "flutter/fml/logging.h" |
| #include "flutter/fml/macros.h" |
| #include "pointer_delegate.h" |
| #include "tests/fakes/mouse_source.h" |
| #include "tests/fakes/touch_source.h" |
| #include "tests/pointer_event_utility.h" |
| |
| namespace flutter_runner::testing { |
| |
| using fup_EventPhase = fuchsia::ui::pointer::EventPhase; |
| using fup_TouchEvent = fuchsia::ui::pointer::TouchEvent; |
| using fup_TouchIxnId = fuchsia::ui::pointer::TouchInteractionId; |
| using fup_TouchIxnResult = fuchsia::ui::pointer::TouchInteractionResult; |
| using fup_TouchIxnStatus = fuchsia::ui::pointer::TouchInteractionStatus; |
| using fup_TouchPointerSample = fuchsia::ui::pointer::TouchPointerSample; |
| using fup_TouchResponse = fuchsia::ui::pointer::TouchResponse; |
| using fup_TouchResponseType = fuchsia::ui::pointer::TouchResponseType; |
| using fup_ViewParameters = fuchsia::ui::pointer::ViewParameters; |
| using fup_MouseEvent = fuchsia::ui::pointer::MouseEvent; |
| |
| constexpr std::array<std::array<float, 2>, 2> kRect = {{{0, 0}, {20, 20}}}; |
| constexpr std::array<float, 9> kIdentity = {1, 0, 0, 0, 1, 0, 0, 0, 1}; |
| constexpr fup_TouchIxnId kIxnOne = {.device_id = 1u, |
| .pointer_id = 1u, |
| .interaction_id = 2u}; |
| |
| constexpr uint32_t kMouseDeviceId = 123; |
| constexpr std::array<int64_t, 2> kNoScrollInPhysicalPixelDelta = {0, 0}; |
| const bool kNotPrecisionScroll = false; |
| const bool kPrecisionScroll = true; |
| |
| // Fixture to exercise Flutter runner's implementation for |
| // fuchsia.ui.pointer.TouchSource. |
| class PointerDelegateTest : public ::testing::Test { |
| protected: |
| PointerDelegateTest() : loop_(&kAsyncLoopConfigAttachToCurrentThread) { |
| touch_source_ = std::make_unique<FakeTouchSource>(); |
| mouse_source_ = std::make_unique<FakeMouseSource>(); |
| pointer_delegate_ = std::make_unique<flutter_runner::PointerDelegate>( |
| touch_source_bindings_.AddBinding(touch_source_.get()), |
| mouse_source_bindings_.AddBinding(mouse_source_.get())); |
| } |
| |
| void RunLoopUntilIdle() { loop_.RunUntilIdle(); } |
| |
| std::unique_ptr<FakeTouchSource> touch_source_; |
| std::unique_ptr<FakeMouseSource> mouse_source_; |
| std::unique_ptr<flutter_runner::PointerDelegate> pointer_delegate_; |
| |
| private: |
| async::Loop loop_; |
| fidl::BindingSet<fuchsia::ui::pointer::TouchSource> touch_source_bindings_; |
| fidl::BindingSet<fuchsia::ui::pointer::MouseSource> mouse_source_bindings_; |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(PointerDelegateTest); |
| }; |
| |
| TEST_F(PointerDelegateTest, Data_FuchsiaTimeVersusFlutterTime) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD -> Flutter ADD+DOWN |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(/* in nanoseconds */ 1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .AddResult( |
| {.interaction = kIxnOne, .status = fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 2u); |
| EXPECT_EQ((*pointers)[0].time_stamp, /* in microseconds */ 1111u); |
| EXPECT_EQ((*pointers)[1].time_stamp, /* in microseconds */ 1111u); |
| } |
| |
| TEST_F(PointerDelegateTest, Phase_FlutterPhasesAreSynthesized) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD -> Flutter ADD+DOWN |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .AddResult( |
| {.interaction = kIxnOne, .status = fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 2u); |
| EXPECT_EQ((*pointers)[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ((*pointers)[1].change, flutter::PointerData::Change::kDown); |
| |
| // Fuchsia CHANGE -> Flutter MOVE |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 1u); |
| EXPECT_EQ((*pointers)[0].change, flutter::PointerData::Change::kMove); |
| |
| // Fuchsia REMOVE -> Flutter UP+REMOVE |
| events = TouchEventBuilder::New() |
| .AddTime(3333000u) |
| .AddSample(kIxnOne, fup_EventPhase::REMOVE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 2u); |
| EXPECT_EQ((*pointers)[0].change, flutter::PointerData::Change::kUp); |
| EXPECT_EQ((*pointers)[1].change, flutter::PointerData::Change::kRemove); |
| } |
| |
| TEST_F(PointerDelegateTest, Phase_FuchsiaCancelBecomesFlutterCancel) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD -> Flutter ADD+DOWN |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .AddResult( |
| {.interaction = kIxnOne, .status = fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 2u); |
| EXPECT_EQ((*pointers)[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ((*pointers)[1].change, flutter::PointerData::Change::kDown); |
| |
| // Fuchsia CANCEL -> Flutter CANCEL |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddSample(kIxnOne, fup_EventPhase::CANCEL, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 1u); |
| EXPECT_EQ((*pointers)[0].change, flutter::PointerData::Change::kCancel); |
| } |
| |
| TEST_F(PointerDelegateTest, Coordinates_CorrectMapping) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD event, with a view parameter that maps the viewport identically |
| // to the view. Then the center point of the viewport should map to the center |
| // of the view, (10.f, 10.f). |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddViewParameters(/*view*/ {{{0, 0}, {20, 20}}}, |
| /*viewport*/ {{{0, 0}, {20, 20}}}, |
| /*matrix*/ {1, 0, 0, 0, 1, 0, 0, 0, 1}) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .AddResult( |
| {.interaction = kIxnOne, .status = fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 2u); // ADD - DOWN |
| EXPECT_FLOAT_EQ((*pointers)[0].physical_x, 10.f); |
| EXPECT_FLOAT_EQ((*pointers)[0].physical_y, 10.f); |
| pointers = {}; |
| |
| // Fuchsia CHANGE event, with a view parameter that translates the viewport by |
| // (10, 10) within the view. Then the minimal point in the viewport (its |
| // origin) should map to the center of the view, (10.f, 10.f). |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddViewParameters(/*view*/ {{{0, 0}, {20, 20}}}, |
| /*viewport*/ {{{0, 0}, {20, 20}}}, |
| /*matrix*/ {1, 0, 0, 0, 1, 0, 10, 10, 1}) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {0.f, 0.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 1u); // MOVE |
| EXPECT_FLOAT_EQ((*pointers)[0].physical_x, 10.f); |
| EXPECT_FLOAT_EQ((*pointers)[0].physical_y, 10.f); |
| |
| // Fuchsia CHANGE event, with a view parameter that scales the viewport by |
| // (0.5, 0.5) within the view. Then the maximal point in the viewport should |
| // map to the center of the view, (10.f, 10.f). |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddViewParameters(/*view*/ {{{0, 0}, {20, 20}}}, |
| /*viewport*/ {{{0, 0}, {20, 20}}}, |
| /*matrix*/ {0.5f, 0, 0, 0, 0.5f, 0, 0, 0, 1}) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {20.f, 20.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 1u); // MOVE |
| EXPECT_FLOAT_EQ((*pointers)[0].physical_x, 10.f); |
| EXPECT_FLOAT_EQ((*pointers)[0].physical_y, 10.f); |
| } |
| |
| TEST_F(PointerDelegateTest, Coordinates_DownEventClampedToView) { |
| const float kSmallDiscrepancy = -0.00003f; |
| |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, kSmallDiscrepancy}) |
| .AddResult( |
| {.interaction = kIxnOne, .status = fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers->size(), 2u); |
| |
| const auto& add_event = (*pointers)[0]; |
| EXPECT_FLOAT_EQ(add_event.physical_x, 10.f); |
| EXPECT_FLOAT_EQ(add_event.physical_y, kSmallDiscrepancy); |
| |
| const auto& down_event = (*pointers)[1]; |
| EXPECT_FLOAT_EQ(down_event.physical_x, 10.f); |
| EXPECT_EQ(down_event.physical_y, 0.f); |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_FirstResponseIsEmpty) { |
| bool called = false; |
| pointer_delegate_->WatchLoop( |
| [&called](std::vector<flutter::PointerData> events) { called = true; }); |
| RunLoopUntilIdle(); // Server gets Watch call. |
| |
| EXPECT_FALSE(called); // No events yet received to forward to client. |
| // Server sees an initial "response" from client, which is empty, by contract. |
| const auto responses = touch_source_->UploadedResponses(); |
| ASSERT_TRUE(responses.has_value()); |
| ASSERT_EQ(responses->size(), 0u); |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_ResponseMatchesEarlierEvents) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia view parameter only. Empty response. |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .BuildAsVector(); |
| |
| // Fuchsia ptr 1 ADD sample. Yes response. |
| fup_TouchEvent e1 = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample({.device_id = 0u, .pointer_id = 1u, .interaction_id = 3u}, |
| fup_EventPhase::ADD, {10.f, 10.f}) |
| .Build(); |
| events.emplace_back(std::move(e1)); |
| |
| // Fuchsia ptr 2 ADD sample. Yes response. |
| fup_TouchEvent e2 = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddSample({.device_id = 0u, .pointer_id = 2u, .interaction_id = 3u}, |
| fup_EventPhase::ADD, {5.f, 5.f}) |
| .Build(); |
| events.emplace_back(std::move(e2)); |
| |
| // Fuchsia ptr 3 ADD sample. Yes response. |
| fup_TouchEvent e3 = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddSample({.device_id = 0u, .pointer_id = 3u, .interaction_id = 3u}, |
| fup_EventPhase::ADD, {1.f, 1.f}) |
| .Build(); |
| events.emplace_back(std::move(e3)); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| const auto responses = touch_source_->UploadedResponses(); |
| ASSERT_TRUE(responses.has_value()); |
| ASSERT_EQ(responses.value().size(), 4u); |
| // Event 0 did not carry a sample, so no response. |
| EXPECT_FALSE(responses.value()[0].has_response_type()); |
| // Events 1-3 had a sample, must have a response. |
| EXPECT_TRUE(responses.value()[1].has_response_type()); |
| EXPECT_EQ(responses.value()[1].response_type(), fup_TouchResponseType::YES); |
| EXPECT_TRUE(responses.value()[2].has_response_type()); |
| EXPECT_EQ(responses.value()[2].response_type(), fup_TouchResponseType::YES); |
| EXPECT_TRUE(responses.value()[3].has_response_type()); |
| EXPECT_EQ(responses.value()[3].response_type(), fup_TouchResponseType::YES); |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_LateGrant) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD, no grant result - buffer it. |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia CHANGE, no grant result - buffer it. |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia result: ownership granted. Buffered pointers released. |
| events = TouchEventBuilder::New() |
| .AddTime(3333000u) |
| .AddResult({kIxnOne, fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 3u); // ADD - DOWN - MOVE |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ(pointers.value()[1].change, flutter::PointerData::Change::kDown); |
| EXPECT_EQ(pointers.value()[2].change, flutter::PointerData::Change::kMove); |
| pointers = {}; |
| |
| // Fuchsia CHANGE, grant result - release immediately. |
| events = TouchEventBuilder::New() |
| .AddTime(/* in nanoseconds */ 4444000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kMove); |
| EXPECT_EQ(pointers.value()[0].time_stamp, /* in microseconds */ 4444u); |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_LateGrantCombo) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD, no grant result - buffer it. |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia CHANGE, no grant result - buffer it. |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia CHANGE, with grant result - release buffered events. |
| events = TouchEventBuilder::New() |
| .AddTime(3333000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .AddResult({kIxnOne, fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 4u); // ADD - DOWN - MOVE - MOVE |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ(pointers.value()[0].time_stamp, 1111u); |
| EXPECT_EQ(pointers.value()[1].change, flutter::PointerData::Change::kDown); |
| EXPECT_EQ(pointers.value()[1].time_stamp, 1111u); |
| EXPECT_EQ(pointers.value()[2].change, flutter::PointerData::Change::kMove); |
| EXPECT_EQ(pointers.value()[2].time_stamp, 2222u); |
| EXPECT_EQ(pointers.value()[3].change, flutter::PointerData::Change::kMove); |
| EXPECT_EQ(pointers.value()[3].time_stamp, 3333u); |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_EarlyGrant) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD, with grant result - release immediately. |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .AddResult({kIxnOne, fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 2u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ(pointers.value()[1].change, flutter::PointerData::Change::kDown); |
| pointers = {}; |
| |
| // Fuchsia CHANGE, after grant result - release immediately. |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kMove); |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_LateDeny) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD, no grant result - buffer it. |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia CHANGE, no grant result - buffer it. |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia result: ownership denied. Buffered pointers deleted. |
| events = TouchEventBuilder::New() |
| .AddTime(3333000u) |
| .AddResult({kIxnOne, fup_TouchIxnStatus::DENIED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 0u); // Do not release to client! |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_LateDenyCombo) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| // Fuchsia ADD, no grant result - buffer it. |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia CHANGE, no grant result - buffer it. |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddSample(kIxnOne, fup_EventPhase::CHANGE, {10.f, 10.f}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Fuchsia result: ownership denied. Buffered pointers deleted. |
| events = TouchEventBuilder::New() |
| .AddTime(3333000u) |
| .AddSample(kIxnOne, fup_EventPhase::CANCEL, {10.f, 10.f}) |
| .AddResult({kIxnOne, fup_TouchIxnStatus::DENIED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 0u); // Do not release to client! |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, Protocol_PointersAreIndependent) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| constexpr fup_TouchIxnId kIxnTwo = { |
| .device_id = 1u, .pointer_id = 2u, .interaction_id = 2u}; |
| |
| // Fuchsia ptr1 ADD and ptr2 ADD, no grant result for either - buffer them. |
| std::vector<fup_TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fup_EventPhase::ADD, {10.f, 10.f}) |
| .BuildAsVector(); |
| fup_TouchEvent ptr2 = |
| TouchEventBuilder::New() |
| .AddTime(1111000u) |
| .AddSample(kIxnTwo, fup_EventPhase::ADD, {15.f, 15.f}) |
| .Build(); |
| events.emplace_back(std::move(ptr2)); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| EXPECT_EQ(pointers.value().size(), 0u); |
| pointers = {}; |
| |
| // Server grants win to pointer 2. |
| events = TouchEventBuilder::New() |
| .AddTime(2222000u) |
| .AddResult({kIxnTwo, fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| // Note: Fuchsia's device and pointer IDs (both 32 bit) are coerced together |
| // to fit in Flutter's 64-bit device ID. However, Flutter's pointer_identifier |
| // is not set by platform runner code - PointerDataCaptureConverter (PDPC) |
| // sets it. |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 2u); |
| EXPECT_EQ(pointers.value()[0].pointer_identifier, 0); // reserved for PDPC |
| EXPECT_EQ(pointers.value()[0].device, (int64_t)((1ul << 32) | 2u)); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ(pointers.value()[1].pointer_identifier, 0); // reserved for PDPC |
| EXPECT_EQ(pointers.value()[1].device, (int64_t)((1ul << 32) | 2u)); |
| EXPECT_EQ(pointers.value()[1].change, flutter::PointerData::Change::kDown); |
| pointers = {}; |
| |
| // Server grants win to pointer 1. |
| events = TouchEventBuilder::New() |
| .AddTime(3333000u) |
| .AddResult({kIxnOne, fup_TouchIxnStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 2u); |
| EXPECT_EQ(pointers.value()[0].pointer_identifier, 0); // reserved for PDPC |
| EXPECT_EQ(pointers.value()[0].device, (int64_t)((1ul << 32) | 1u)); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ(pointers.value()[1].pointer_identifier, 0); // reserved for PDPC |
| EXPECT_EQ(pointers.value()[1].device, (int64_t)((1ul << 32) | 1u)); |
| EXPECT_EQ(pointers.value()[1].change, flutter::PointerData::Change::kDown); |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, MouseWheel_TickBased) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| std::vector<fup_MouseEvent> events = |
| MouseEventBuilder() |
| .AddTime(1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {0, 1}, |
| kNoScrollInPhysicalPixelDelta, kNotPrecisionScroll) |
| .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2}) |
| .BuildAsVector(); |
| mouse_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kHover); |
| EXPECT_EQ(pointers.value()[0].signal_kind, |
| flutter::PointerData::SignalKind::kScroll); |
| EXPECT_EQ(pointers.value()[0].kind, flutter::PointerData::DeviceKind::kMouse); |
| EXPECT_EQ(pointers.value()[0].buttons, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_x, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_y, -20); |
| pointers = {}; |
| |
| // receive a horizontal scroll |
| events = MouseEventBuilder() |
| .AddTime(1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {1, 0}, |
| kNoScrollInPhysicalPixelDelta, kNotPrecisionScroll) |
| .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2}) |
| .BuildAsVector(); |
| mouse_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kHover); |
| EXPECT_EQ(pointers.value()[0].signal_kind, |
| flutter::PointerData::SignalKind::kScroll); |
| EXPECT_EQ(pointers.value()[0].kind, flutter::PointerData::DeviceKind::kMouse); |
| EXPECT_EQ(pointers.value()[0].buttons, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_x, 20); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_y, 0); |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, MouseWheel_PixelBased) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| std::vector<fup_MouseEvent> events = |
| MouseEventBuilder() |
| .AddTime(1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {0, 1}, {0, 120}, |
| kNotPrecisionScroll) |
| .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2}) |
| .BuildAsVector(); |
| mouse_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kHover); |
| EXPECT_EQ(pointers.value()[0].signal_kind, |
| flutter::PointerData::SignalKind::kScroll); |
| EXPECT_EQ(pointers.value()[0].kind, flutter::PointerData::DeviceKind::kMouse); |
| EXPECT_EQ(pointers.value()[0].buttons, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_x, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_y, -120); |
| pointers = {}; |
| |
| // receive a horizontal scroll |
| events = MouseEventBuilder() |
| .AddTime(1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {1, 0}, {120, 0}, |
| kNotPrecisionScroll) |
| .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2}) |
| .BuildAsVector(); |
| mouse_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kHover); |
| EXPECT_EQ(pointers.value()[0].signal_kind, |
| flutter::PointerData::SignalKind::kScroll); |
| EXPECT_EQ(pointers.value()[0].kind, flutter::PointerData::DeviceKind::kMouse); |
| EXPECT_EQ(pointers.value()[0].buttons, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_x, 120); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_y, 0); |
| pointers = {}; |
| } |
| |
| TEST_F(PointerDelegateTest, MouseWheel_TouchpadPixelBased) { |
| std::optional<std::vector<flutter::PointerData>> pointers; |
| pointer_delegate_->WatchLoop( |
| [&pointers](std::vector<flutter::PointerData> events) { |
| pointers = std::move(events); |
| }); |
| RunLoopUntilIdle(); // Server gets watch call. |
| |
| std::vector<fup_MouseEvent> events = |
| MouseEventBuilder() |
| .AddTime(1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {0, 1}, {0, 120}, |
| kPrecisionScroll) |
| .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2}) |
| .BuildAsVector(); |
| mouse_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kHover); |
| EXPECT_EQ(pointers.value()[0].signal_kind, |
| flutter::PointerData::SignalKind::kScroll); |
| EXPECT_EQ(pointers.value()[0].kind, flutter::PointerData::DeviceKind::kMouse); |
| EXPECT_EQ(pointers.value()[0].buttons, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_x, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_y, -120); |
| pointers = {}; |
| |
| // receive a horizontal scroll |
| events = MouseEventBuilder() |
| .AddTime(1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kMouseDeviceId, {10.f, 10.f}, {}, {1, 0}, {120, 0}, |
| kPrecisionScroll) |
| .AddMouseDeviceInfo(kMouseDeviceId, {0, 1, 2}) |
| .BuildAsVector(); |
| mouse_source_->ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(pointers.has_value()); |
| ASSERT_EQ(pointers.value().size(), 1u); |
| EXPECT_EQ(pointers.value()[0].change, flutter::PointerData::Change::kHover); |
| EXPECT_EQ(pointers.value()[0].signal_kind, |
| flutter::PointerData::SignalKind::kScroll); |
| EXPECT_EQ(pointers.value()[0].kind, flutter::PointerData::DeviceKind::kMouse); |
| EXPECT_EQ(pointers.value()[0].buttons, 0); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_x, 120); |
| EXPECT_EQ(pointers.value()[0].scroll_delta_y, 0); |
| pointers = {}; |
| } |
| |
| } // namespace flutter_runner::testing |