| // Copyright 2024 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fidl/fuchsia.input.report/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.display.singleton/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.focus/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.input/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.input/cpp/natural_ostream.h> |
| #include <fidl/fuchsia.ui.input3/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.policy/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 <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <src/ui/testing/util/fidl_cpp_helpers.h> |
| #include <src/ui/tests/conformance_input_tests/conformance-test-base.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace ui_conformance_testing { |
| |
| const std::string PUPPET_UNDER_TEST_FACTORY_SERVICE = "/svc/puppet-under-test-factory-service"; |
| |
| namespace futi = fuchsia_ui_test_input; |
| namespace fir = fuchsia_input_report; |
| namespace fui = fuchsia_ui_input; |
| namespace fup = fuchsia_ui_policy; |
| namespace futc = fuchsia_ui_test_conformance; |
| |
| class ButtonsListener final : public fidl::Server<fup::MediaButtonsListener> { |
| public: |
| // |MediaButtonsListener| |
| void OnEvent(OnEventRequest& request, OnEventCompleter::Sync& completer) override { |
| events_received_.push_back(std::move(request.event())); |
| completer.Reply(); |
| } |
| |
| void OnMediaButtonsEvent(OnMediaButtonsEventRequest& request, |
| OnMediaButtonsEventCompleter::Sync& completer) override { |
| ZX_PANIC("Not Implemented"); |
| } |
| |
| fidl::ClientEnd<fup::MediaButtonsListener> ServeAndGetClientEnd(async_dispatcher_t* dispatcher) { |
| auto [client_end, server_end] = fidl::Endpoints<fup::MediaButtonsListener>::Create(); |
| binding_.AddBinding(dispatcher, std::move(server_end), this, fidl::kIgnoreBindingClosure); |
| return std::move(client_end); |
| } |
| |
| const std::vector<fui::MediaButtonsEvent>& events_received() const { return events_received_; } |
| |
| void clear_events() { events_received_.clear(); } |
| |
| private: |
| fidl::ServerBindingGroup<fup::MediaButtonsListener> binding_; |
| std::vector<fui::MediaButtonsEvent> events_received_; |
| }; |
| |
| class MediaButtonConformanceTest : public ui_conformance_test_base::ConformanceTest { |
| public: |
| void SetUp() override { |
| ui_conformance_test_base::ConformanceTest::SetUp(); |
| |
| fuchsia_ui_views::ViewCreationToken root_view_token; |
| { |
| FX_LOGS(INFO) << "Creating root view token"; |
| |
| auto controller = ConnectSyncIntoRealm<fuchsia_ui_test_scene::Controller>(); |
| |
| fuchsia_ui_test_scene::ControllerPresentClientViewRequest req; |
| auto [view_token, viewport_token] = scenic::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 flatland = ConnectIntoRealm<fuchsia_ui_composition::Flatland>(); |
| auto keyboard = ConnectIntoRealm<fuchsia_ui_input3::Keyboard>(); |
| auto [puppet_client_end, puppet_server_end] = fidl::Endpoints<futc::Puppet>::Create(); |
| puppet_ = fidl::SyncClient(std::move(puppet_client_end)); |
| |
| 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.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); |
| } |
| |
| // Register fake media button. |
| { |
| FX_LOGS(INFO) << "Connecting to input registry"; |
| auto input_registry = ConnectSyncIntoRealm<futi::Registry>(); |
| |
| FX_LOGS(INFO) << "Registering fake media button device"; |
| auto [client_end, server_end] = fidl::Endpoints<futi::MediaButtonsDevice>::Create(); |
| media_buttons_ = fidl::SyncClient(std::move(client_end)); |
| |
| futi::RegistryRegisterMediaButtonsDeviceAndGetDeviceInfoRequest request; |
| request.device(std::move(server_end)); |
| auto res = input_registry->RegisterMediaButtonsDeviceAndGetDeviceInfo(std::move(request)); |
| ZX_ASSERT_OK(res); |
| |
| device_id_ = res->device_id().value(); |
| } |
| } |
| |
| void SimulatePress(fir::ConsumerControlButton button) { |
| futi::MediaButtonsDeviceSimulateButtonPressRequest request; |
| request.button(button); |
| ZX_ASSERT_OK(media_buttons_->SimulateButtonPress(request)); |
| } |
| |
| void SimulatePress(std::vector<fir::ConsumerControlButton> buttons) { |
| futi::MediaButtonsDeviceSendButtonsStateRequest request; |
| request.buttons(std::move(buttons)); |
| ZX_ASSERT_OK(media_buttons_->SendButtonsState(request)); |
| } |
| |
| fui::MediaButtonsEvent MakeMediaButtonsEvent(int8_t volume, bool mic_mute, bool pause, |
| bool camera_disable, bool power, bool function) { |
| fui::MediaButtonsEvent e({.volume = volume, |
| .mic_mute = mic_mute, |
| .pause = pause, |
| .camera_disable = camera_disable, |
| .power = power, |
| .function = function, |
| .device_id = device_id_}); |
| return e; |
| } |
| |
| fui::MediaButtonsEvent MakeEmptyEvent() { |
| return MakeMediaButtonsEvent(0, false, false, false, false, false); |
| } |
| |
| fui::MediaButtonsEvent MakeVolumeUpEvent() { |
| return MakeMediaButtonsEvent(1, false, false, false, false, false); |
| } |
| |
| fui::MediaButtonsEvent MakeVolumeDownEvent() { |
| return MakeMediaButtonsEvent(-1, false, false, false, false, false); |
| } |
| |
| fui::MediaButtonsEvent MakePauseEvent() { |
| return MakeMediaButtonsEvent(0, false, true, false, false, false); |
| } |
| |
| fui::MediaButtonsEvent MakeMicMuteEvent() { |
| return MakeMediaButtonsEvent(0, true, false, false, false, false); |
| } |
| |
| fui::MediaButtonsEvent MakeCameraDisableEvent() { |
| return MakeMediaButtonsEvent(0, false, false, true, false, false); |
| } |
| |
| fui::MediaButtonsEvent MakePowerEvent() { |
| return MakeMediaButtonsEvent(0, false, false, false, true, false); |
| } |
| |
| fui::MediaButtonsEvent MakeFunctionEvent() { |
| return MakeMediaButtonsEvent(0, false, false, false, false, true); |
| } |
| |
| private: |
| fidl::SyncClient<futi::MediaButtonsDevice> media_buttons_; |
| fidl::SyncClient<futc::Puppet> puppet_; |
| fidl::SyncClient<futc::PuppetFactory> puppet_factory_; |
| uint32_t device_id_; |
| }; |
| |
| std::string ToString(const fui::MediaButtonsEvent& e) { |
| std::stringstream ss; |
| ss << e; |
| return ss.str(); |
| } |
| |
| TEST_F(MediaButtonConformanceTest, SimplePress) { |
| auto device_listener_registry = ConnectSyncIntoRealm<fup::DeviceListenerRegistry>(); |
| |
| ButtonsListener listener; |
| auto listener_client_end = listener.ServeAndGetClientEnd(dispatcher()); |
| ZX_ASSERT_OK(device_listener_registry->RegisterListener({std::move(listener_client_end)})); |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kVolumeUp); |
| FX_LOGS(INFO) << "wait for button VOLUME_UP"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeVolumeUpEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kVolumeDown); |
| FX_LOGS(INFO) << "wait for button VOLUME_DOWN"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeVolumeDownEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kPause); |
| FX_LOGS(INFO) << "wait for button PAUSE"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakePauseEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kMicMute); |
| FX_LOGS(INFO) << "wait for button MIC_MUTE"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeMicMuteEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kCameraDisable); |
| FX_LOGS(INFO) << "wait for button CAMERA_DISABLE"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeCameraDisableEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kFunction); |
| FX_LOGS(INFO) << "wait for button FUNCTION"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeFunctionEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kPower); |
| FX_LOGS(INFO) << "wait for button POWER"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakePowerEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| // The following button types are not yet supported. |
| { |
| SimulatePress(fir::ConsumerControlButton::kReboot); |
| FX_LOGS(INFO) << "wait for button REBOOT"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeEmptyEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| |
| { |
| SimulatePress(fir::ConsumerControlButton::kFactoryReset); |
| FX_LOGS(INFO) << "wait for button FACTORY_RESET"; |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 1; }); |
| EXPECT_EQ(listener.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeEmptyEvent())); |
| EXPECT_EQ(ToString(listener.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| } |
| |
| TEST_F(MediaButtonConformanceTest, MultiPress) { |
| auto device_listener_registry = ConnectSyncIntoRealm<fup::DeviceListenerRegistry>(); |
| |
| ButtonsListener listener; |
| auto listener_client_end = listener.ServeAndGetClientEnd(dispatcher()); |
| ZX_ASSERT_OK(device_listener_registry->RegisterListener({std::move(listener_client_end)})); |
| |
| // press multi buttons. |
| { |
| auto buttons = std::vector<fir::ConsumerControlButton>{ |
| fuchsia_input_report::ConsumerControlButton::kCameraDisable, |
| fuchsia_input_report::ConsumerControlButton::kMicMute, |
| fuchsia_input_report::ConsumerControlButton::kPause, |
| fuchsia_input_report::ConsumerControlButton::kVolumeUp, |
| }; |
| SimulatePress(std::move(buttons)); |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 0; }); |
| EXPECT_EQ(listener.events_received().size(), 1u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), |
| ToString(MakeMediaButtonsEvent(1, true, true, true, false, false))); |
| listener.clear_events(); |
| } |
| |
| // release multi buttons. |
| { |
| SimulatePress(std::vector<fir::ConsumerControlButton>()); |
| RunLoopUntil([&listener]() { return listener.events_received().size() > 0; }); |
| EXPECT_EQ(listener.events_received().size(), 1u); |
| EXPECT_EQ(ToString(listener.events_received()[0]), ToString(MakeEmptyEvent())); |
| listener.clear_events(); |
| } |
| } |
| |
| TEST_F(MediaButtonConformanceTest, MultiListener) { |
| auto device_listener_registry = ConnectSyncIntoRealm<fup::DeviceListenerRegistry>(); |
| |
| ButtonsListener listener1; |
| auto listener_client_end1 = listener1.ServeAndGetClientEnd(dispatcher()); |
| ZX_ASSERT_OK(device_listener_registry->RegisterListener({std::move(listener_client_end1)})); |
| |
| auto listener2 = std::make_unique<ButtonsListener>(); |
| auto listener_client_end2 = listener2->ServeAndGetClientEnd(dispatcher()); |
| ZX_ASSERT_OK(device_listener_registry->RegisterListener({std::move(listener_client_end2)})); |
| |
| // Both listener received events. |
| { |
| SimulatePress(fir::ConsumerControlButton::kVolumeUp); |
| FX_LOGS(INFO) << "wait for button VOLUME_UP"; |
| RunLoopUntil([&listener1]() { return listener1.events_received().size() > 1; }); |
| RunLoopUntil([&listener2]() { return listener2->events_received().size() > 1; }); |
| EXPECT_EQ(listener1.events_received().size(), 2u); |
| EXPECT_EQ(listener2->events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener1.events_received()[0]), ToString(MakeVolumeUpEvent())); |
| EXPECT_EQ(ToString(listener1.events_received()[1]), ToString(MakeEmptyEvent())); |
| EXPECT_EQ(ToString(listener2->events_received()[0]), ToString(MakeVolumeUpEvent())); |
| EXPECT_EQ(ToString(listener2->events_received()[1]), ToString(MakeEmptyEvent())); |
| listener1.clear_events(); |
| listener2->clear_events(); |
| } |
| |
| ButtonsListener listener3; |
| auto listener_client_end3 = listener3.ServeAndGetClientEnd(dispatcher()); |
| ZX_ASSERT_OK(device_listener_registry->RegisterListener({std::move(listener_client_end3)})); |
| |
| // new listener receives the last event. |
| { |
| FX_LOGS(INFO) << "listener wait for the last button"; |
| RunLoopUntil([&listener3]() { return listener3.events_received().size() > 0; }); |
| EXPECT_EQ(ToString(listener3.events_received()[0]), ToString(MakeEmptyEvent())); |
| listener3.clear_events(); |
| } |
| |
| // then new listener receive only new events. |
| { |
| SimulatePress(fir::ConsumerControlButton::kVolumeDown); |
| FX_LOGS(INFO) << "wait for button VOLUME_DOWN"; |
| RunLoopUntil([&listener1]() { return listener1.events_received().size() > 1; }); |
| RunLoopUntil([&listener2]() { return listener2->events_received().size() > 1; }); |
| RunLoopUntil([&listener3]() { return listener3.events_received().size() > 1; }); |
| EXPECT_EQ(listener1.events_received().size(), 2u); |
| EXPECT_EQ(listener2->events_received().size(), 2u); |
| EXPECT_EQ(listener3.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener1.events_received()[0]), ToString(MakeVolumeDownEvent())); |
| EXPECT_EQ(ToString(listener1.events_received()[1]), ToString(MakeEmptyEvent())); |
| EXPECT_EQ(ToString(listener2->events_received()[0]), ToString(MakeVolumeDownEvent())); |
| EXPECT_EQ(ToString(listener2->events_received()[1]), ToString(MakeEmptyEvent())); |
| EXPECT_EQ(ToString(listener3.events_received()[0]), ToString(MakeVolumeDownEvent())); |
| EXPECT_EQ(ToString(listener3.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener1.clear_events(); |
| listener2->clear_events(); |
| listener3.clear_events(); |
| } |
| |
| // drop listener2, and verify other listeners still working. |
| listener2 = {}; |
| { |
| SimulatePress(fir::ConsumerControlButton::kPause); |
| FX_LOGS(INFO) << "wait for button PAUSE"; |
| RunLoopUntil([&listener1]() { return listener1.events_received().size() > 1; }); |
| RunLoopUntil([&listener3]() { return listener3.events_received().size() > 1; }); |
| EXPECT_EQ(listener1.events_received().size(), 2u); |
| EXPECT_EQ(listener3.events_received().size(), 2u); |
| EXPECT_EQ(ToString(listener1.events_received()[0]), ToString(MakePauseEvent())); |
| EXPECT_EQ(ToString(listener1.events_received()[1]), ToString(MakeEmptyEvent())); |
| EXPECT_EQ(ToString(listener3.events_received()[0]), ToString(MakePauseEvent())); |
| EXPECT_EQ(ToString(listener3.events_received()[1]), ToString(MakeEmptyEvent())); |
| listener1.clear_events(); |
| listener3.clear_events(); |
| } |
| } |
| |
| } // namespace ui_conformance_testing |