diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 3dca5f1ffbf84..6b0c75c965e64 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1289,6 +1289,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_dart_project_private.h FILE: ../../../flutter/shell/platform/linux/fl_dart_project_test.cc FILE: ../../../flutter/shell/platform/linux/fl_engine.cc FILE: ../../../flutter/shell/platform/linux/fl_engine_private.h +FILE: ../../../flutter/shell/platform/linux/fl_event_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_event_channel_test.cc FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec.cc @@ -1340,6 +1342,7 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_codec FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_engine.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_event_channel.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_message_codec.h diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 31dd919bccd3d..0d6a1cbafb8ee 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -47,6 +47,7 @@ _public_headers = [ "public/flutter_linux/fl_binary_messenger.h", "public/flutter_linux/fl_dart_project.h", "public/flutter_linux/fl_engine.h", + "public/flutter_linux/fl_event_channel.h", "public/flutter_linux/fl_json_message_codec.h", "public/flutter_linux/fl_json_method_codec.h", "public/flutter_linux/fl_message_codec.h", @@ -92,6 +93,7 @@ source_set("flutter_linux_sources") { "fl_binary_messenger.cc", "fl_dart_project.cc", "fl_engine.cc", + "fl_event_channel.cc", "fl_json_message_codec.cc", "fl_json_method_codec.cc", "fl_key_event_plugin.cc", @@ -153,6 +155,7 @@ executable("flutter_linux_unittests") { "fl_binary_codec_test.cc", "fl_binary_messenger_test.cc", "fl_dart_project_test.cc", + "fl_event_channel_test.cc", "fl_json_message_codec_test.cc", "fl_json_method_codec_test.cc", "fl_key_event_plugin_test.cc", diff --git a/shell/platform/linux/fl_event_channel.cc b/shell/platform/linux/fl_event_channel.cc new file mode 100644 index 0000000000000..ddbd4d385c3a7 --- /dev/null +++ b/shell/platform/linux/fl_event_channel.cc @@ -0,0 +1,244 @@ +// 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 "flutter/shell/platform/linux/public/flutter_linux/fl_event_channel.h" + +#include + +#include "flutter/shell/platform/linux/fl_method_codec_private.h" + +static constexpr char kListenMethod[] = "listen"; +static constexpr char kCancelMethod[] = "cancel"; +static constexpr char kEventRequestError[] = "error"; + +struct _FlEventChannel { + GObject parent_instance; + + // Messenger to communicate on. + FlBinaryMessenger* messenger; + + // Channel name. + gchar* name; + + // Codec to en/decode messages. + FlMethodCodec* codec; + + // Function called when the stream is listened to / cancelled. + FlEventChannelHandler listen_handler; + FlEventChannelHandler cancel_handler; + gpointer handler_data; + GDestroyNotify handler_data_destroy_notify; +}; + +struct _FlEventChannelResponseHandle { + GObject parent_instance; + + FlBinaryMessengerResponseHandle* response_handle; +}; + +// Added here to stop the compiler from optimising this function away. +G_MODULE_EXPORT GType fl_event_channel_get_type(); + +G_DEFINE_TYPE(FlEventChannel, fl_event_channel, G_TYPE_OBJECT) + +// Handle method calls from the Dart side of the channel. +static FlMethodErrorResponse* handle_method_call(FlEventChannel* self, + const gchar* name, + FlValue* args) { + FlEventChannelHandler handler; + if (g_strcmp0(name, kListenMethod) == 0) { + handler = self->listen_handler; + } else if (g_strcmp0(name, kCancelMethod) == 0) { + handler = self->cancel_handler; + } else { + g_autofree gchar* message = + g_strdup_printf("Unknown event channel request '%s'", name); + return fl_method_error_response_new(kEventRequestError, message, nullptr); + } + + // If not handled, just accept requests. + if (handler == nullptr) { + return nullptr; + } + + return handler(self, args, self->handler_data); +} + +// Called when a binary message is received on this channel. +static void message_cb(FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + FlBinaryMessengerResponseHandle* response_handle, + gpointer user_data) { + FlEventChannel* self = FL_EVENT_CHANNEL(user_data); + + g_autofree gchar* name = nullptr; + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) args = nullptr; + if (!fl_method_codec_decode_method_call(self->codec, message, &name, &args, + &error)) { + g_warning("Failed to decode message on event channel %s: %s", self->name, + error->message); + fl_binary_messenger_send_response(messenger, response_handle, nullptr, + nullptr); + return; + } + + g_autoptr(FlMethodErrorResponse) response = + handle_method_call(self, name, args); + + g_autoptr(GBytes) data = nullptr; + if (response == nullptr) { + g_autoptr(GError) codec_error = nullptr; + data = fl_method_codec_encode_success_envelope(self->codec, nullptr, + &codec_error); + if (data == nullptr) { + g_warning("Failed to encode event channel %s success response: %s", + self->name, codec_error->message); + } + } else { + g_autoptr(GError) codec_error = nullptr; + data = fl_method_codec_encode_error_envelope( + self->codec, fl_method_error_response_get_code(response), + fl_method_error_response_get_message(response), + fl_method_error_response_get_details(response), &codec_error); + if (data == nullptr) { + g_warning("Failed to encode event channel %s error response: %s", + self->name, codec_error->message); + } + } + + if (!fl_binary_messenger_send_response(messenger, response_handle, data, + &error)) { + g_warning("Failed to send event channel response: %s", error->message); + } +} + +// Removes handlers and their associated data. +static void remove_handlers(FlEventChannel* self) { + if (self->handler_data_destroy_notify != nullptr) { + self->handler_data_destroy_notify(self->handler_data); + } + self->listen_handler = nullptr; + self->cancel_handler = nullptr; + self->handler_data = nullptr; + self->handler_data_destroy_notify = nullptr; +} + +// Called when the channel handler is closed. +static void channel_closed_cb(gpointer user_data) { + g_autoptr(FlEventChannel) self = FL_EVENT_CHANNEL(user_data); + remove_handlers(self); +} + +static void fl_event_channel_dispose(GObject* object) { + FlEventChannel* self = FL_EVENT_CHANNEL(object); + + if (self->messenger != nullptr) { + fl_binary_messenger_set_message_handler_on_channel( + self->messenger, self->name, nullptr, nullptr, nullptr); + } + + g_clear_object(&self->messenger); + g_clear_pointer(&self->name, g_free); + g_clear_object(&self->codec); + + remove_handlers(self); + + G_OBJECT_CLASS(fl_event_channel_parent_class)->dispose(object); +} + +static void fl_event_channel_class_init(FlEventChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_event_channel_dispose; +} + +static void fl_event_channel_init(FlEventChannel* self) {} + +G_MODULE_EXPORT FlEventChannel* fl_event_channel_new( + FlBinaryMessenger* messenger, + const gchar* name, + FlMethodCodec* codec) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(name != nullptr, nullptr); + g_return_val_if_fail(FL_IS_METHOD_CODEC(codec), nullptr); + + FlEventChannel* self = + FL_EVENT_CHANNEL(g_object_new(fl_event_channel_get_type(), nullptr)); + + self->messenger = FL_BINARY_MESSENGER(g_object_ref(messenger)); + self->name = g_strdup(name); + self->codec = FL_METHOD_CODEC(g_object_ref(codec)); + + fl_binary_messenger_set_message_handler_on_channel( + self->messenger, self->name, message_cb, g_object_ref(self), + channel_closed_cb); + + return self; +} + +G_MODULE_EXPORT void fl_event_channel_set_stream_handlers( + FlEventChannel* self, + FlEventChannelHandler listen_handler, + FlEventChannelHandler cancel_handler, + gpointer user_data, + GDestroyNotify destroy_notify) { + g_return_if_fail(FL_IS_EVENT_CHANNEL(self)); + + remove_handlers(self); + self->listen_handler = listen_handler; + self->cancel_handler = cancel_handler; + self->handler_data = user_data; + self->handler_data_destroy_notify = destroy_notify; +} + +G_MODULE_EXPORT gboolean fl_event_channel_send(FlEventChannel* self, + FlValue* event, + GCancellable* cancellable, + GError** error) { + g_return_val_if_fail(FL_IS_EVENT_CHANNEL(self), FALSE); + g_return_val_if_fail(event != nullptr, FALSE); + + g_autoptr(GBytes) data = + fl_method_codec_encode_success_envelope(self->codec, event, error); + if (data == nullptr) { + return FALSE; + } + + fl_binary_messenger_send_on_channel(self->messenger, self->name, data, + cancellable, nullptr, nullptr); + + return TRUE; +} + +G_MODULE_EXPORT gboolean fl_event_channel_send_error(FlEventChannel* self, + const gchar* code, + const gchar* message, + FlValue* details, + GCancellable* cancellable, + GError** error) { + g_return_val_if_fail(FL_IS_EVENT_CHANNEL(self), FALSE); + g_return_val_if_fail(code != nullptr, FALSE); + g_return_val_if_fail(message != nullptr, FALSE); + + g_autoptr(GBytes) data = fl_method_codec_encode_error_envelope( + self->codec, code, message, details, error); + if (data == nullptr) { + return FALSE; + } + + fl_binary_messenger_send_on_channel(self->messenger, self->name, data, + cancellable, nullptr, nullptr); + + return TRUE; +} + +G_MODULE_EXPORT gboolean +fl_event_channel_send_end_of_stream(FlEventChannel* self, + GCancellable* cancellable, + GError** error) { + g_return_val_if_fail(FL_IS_EVENT_CHANNEL(self), FALSE); + fl_binary_messenger_send_on_channel(self->messenger, self->name, nullptr, + cancellable, nullptr, nullptr); + return TRUE; +} diff --git a/shell/platform/linux/fl_event_channel_test.cc b/shell/platform/linux/fl_event_channel_test.cc new file mode 100644 index 0000000000000..cff868b0d6b79 --- /dev/null +++ b/shell/platform/linux/fl_event_channel_test.cc @@ -0,0 +1,390 @@ +// 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. + +// Included first as it collides with the X11 headers. +#include "gtest/gtest.h" + +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_event_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" +#include "flutter/shell/platform/linux/testing/mock_renderer.h" + +// Data passed in tests. +typedef struct { + GMainLoop* loop; + int count; +} TestData; + +// Creates a mock engine that responds to platform messages. +static FlEngine* make_mock_engine() { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project, FL_RENDERER(renderer)); + g_autoptr(GError) engine_error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &engine_error)); + EXPECT_EQ(engine_error, nullptr); + + return static_cast(g_object_ref(engine)); +} + +// Triggers the engine to start listening to the channel. +static void listen_channel(FlBinaryMessenger* messenger, FlValue* args) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + messenger, "test/standard-method", FL_METHOD_CODEC(codec)); + + // Trigger the engine to make a method call. + g_autoptr(FlValue) invoke_args = fl_value_new_list(); + fl_value_append_take(invoke_args, fl_value_new_string("test/standard-event")); + fl_value_append_take(invoke_args, fl_value_new_string("listen")); + fl_value_append(invoke_args, + args != nullptr ? fl_value_ref(args) : fl_value_new_null()); + fl_method_channel_invoke_method(channel, "InvokeMethod", invoke_args, nullptr, + nullptr, nullptr); +} + +// Triggers the engine to cancel the subscription to the channel. +static void cancel_channel(FlBinaryMessenger* messenger, FlValue* args) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + messenger, "test/standard-method", FL_METHOD_CODEC(codec)); + + // Trigger the engine to make a method call. + g_autoptr(FlValue) invoke_args = fl_value_new_list(); + fl_value_append_take(invoke_args, fl_value_new_string("test/standard-event")); + fl_value_append_take(invoke_args, fl_value_new_string("cancel")); + fl_value_append_take( + invoke_args, args != nullptr ? fl_value_ref(args) : fl_value_new_null()); + fl_method_channel_invoke_method(channel, "InvokeMethod", invoke_args, nullptr, + nullptr, nullptr); +} + +// Called when when the remote end starts listening on the channel. +static FlMethodErrorResponse* listen_listen_cb(FlEventChannel* channel, + FlValue* args, + gpointer user_data) { + EXPECT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_NULL); + + g_main_loop_quit(static_cast(user_data)); + + return nullptr; +} + +// Checks we detect a listen event. +TEST(FlEventChannelTest, Listen) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + FlEventChannel* channel = fl_event_channel_new( + messenger, "test/standard-event", FL_METHOD_CODEC(codec)); + fl_event_channel_set_stream_handlers(channel, listen_listen_cb, nullptr, loop, + nullptr); + + listen_channel(messenger, nullptr); + + // Blocks here until listen_listen_cb called. + g_main_loop_run(loop); + + // Manually unref because the compiler complains 'channel' is unused. + g_object_unref(channel); +} + +// Called when when the remote end starts listening on the channel. +static FlMethodErrorResponse* listen_exception_listen_cb( + FlEventChannel* channel, + FlValue* args, + gpointer user_data) { + return fl_method_error_response_new("LISTEN-ERROR", "LISTEN-ERROR-MESSAGE", + nullptr); +} + +// Called when a the test engine notifies us what response we sent in the +// ListenException test. +static void listen_exception_response_cb( + FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + FlBinaryMessengerResponseHandle* response_handle, + gpointer user_data) { + fl_binary_messenger_send_response(messenger, response_handle, nullptr, + nullptr); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + + EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response)); + EXPECT_STREQ( + fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)), + "LISTEN-ERROR"); + EXPECT_STREQ( + fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)), + "LISTEN-ERROR-MESSAGE"); + + g_main_loop_quit(static_cast(user_data)); +} + +// Checks we can generate a listen exception. +TEST(FlEventChannelTest, ListenException) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + FlEventChannel* channel = fl_event_channel_new( + messenger, "test/standard-event", FL_METHOD_CODEC(codec)); + fl_event_channel_set_stream_handlers(channel, listen_exception_listen_cb, + nullptr, loop, nullptr); + + // Listen for response to the engine. + fl_binary_messenger_set_message_handler_on_channel( + messenger, "test/responses", listen_exception_response_cb, loop, nullptr); + + listen_channel(messenger, nullptr); + + // Blocks here until listen_exception_response_cb called. + g_main_loop_run(loop); + + // Manually unref because the compiler complains 'channel' is unused. + g_object_unref(channel); +} + +// Called when when the remote end cancels their subscription. +static FlMethodErrorResponse* cancel_cancel_cb(FlEventChannel* channel, + FlValue* args, + gpointer user_data) { + EXPECT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_NULL); + + g_main_loop_quit(static_cast(user_data)); + + return nullptr; +} + +// Checks we detect a cancel event. +TEST(FlEventChannelTest, Cancel) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + FlEventChannel* channel = fl_event_channel_new( + messenger, "test/standard-event", FL_METHOD_CODEC(codec)); + fl_event_channel_set_stream_handlers(channel, nullptr, cancel_cancel_cb, loop, + nullptr); + + listen_channel(messenger, nullptr); + cancel_channel(messenger, nullptr); + + // Blocks here until cancel_cancel_cb called. + g_main_loop_run(loop); + + // Manually unref because the compiler complains 'channel' is unused. + g_object_unref(channel); +} + +// Called when when the remote end cancels their subscription. +static FlMethodErrorResponse* cancel_exception_cancel_cb( + FlEventChannel* channel, + FlValue* args, + gpointer user_data) { + return fl_method_error_response_new("CANCEL-ERROR", "CANCEL-ERROR-MESSAGE", + nullptr); +} + +// Called when a the test engine notifies us what response we sent in the +// CancelException test. +static void cancel_exception_response_cb( + FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + FlBinaryMessengerResponseHandle* response_handle, + gpointer user_data) { + TestData* data = static_cast(user_data); + + fl_binary_messenger_send_response(messenger, response_handle, nullptr, + nullptr); + + data->count++; + if (data->count == 2) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = fl_method_codec_decode_response( + FL_METHOD_CODEC(codec), message, &error); + EXPECT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + + EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response)); + EXPECT_STREQ( + fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)), + "CANCEL-ERROR"); + EXPECT_STREQ(fl_method_error_response_get_message( + FL_METHOD_ERROR_RESPONSE(response)), + "CANCEL-ERROR-MESSAGE"); + + g_main_loop_quit(data->loop); + } +} + +// Checks we can generate a cancel exception. +TEST(FlEventChannelTest, CancelException) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + TestData data; + data.loop = loop; + data.count = 0; + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + FlEventChannel* channel = fl_event_channel_new( + messenger, "test/standard-event", FL_METHOD_CODEC(codec)); + fl_event_channel_set_stream_handlers( + channel, nullptr, cancel_exception_cancel_cb, &data, nullptr); + + // Listen for response to the engine. + fl_binary_messenger_set_message_handler_on_channel( + messenger, "test/responses", cancel_exception_response_cb, &data, + nullptr); + + listen_channel(messenger, nullptr); + cancel_channel(messenger, nullptr); + + // Blocks here until cancel_exception_response_cb called. + g_main_loop_run(loop); + + // Manually unref because the compiler complains 'channel' is unused. + g_object_unref(channel); +} + +// Called when when the remote end starts listening on the channel. +static FlMethodErrorResponse* args_listen_cb(FlEventChannel* channel, + FlValue* args, + gpointer user_data) { + g_autoptr(FlValue) expected_args = fl_value_new_string("LISTEN-ARGS"); + EXPECT_TRUE(fl_value_equal(args, expected_args)); + + return nullptr; +} + +// Called when when the remote end cancels their subscription. +static FlMethodErrorResponse* args_cancel_cb(FlEventChannel* channel, + FlValue* args, + gpointer user_data) { + g_autoptr(FlValue) expected_args = fl_value_new_string("CANCEL-ARGS"); + EXPECT_TRUE(fl_value_equal(args, expected_args)); + + g_main_loop_quit(static_cast(user_data)); + + return nullptr; +} + +// Checks args are passed to listen/cancel. +TEST(FlEventChannelTest, Args) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + FlEventChannel* channel = fl_event_channel_new( + messenger, "test/standard-event", FL_METHOD_CODEC(codec)); + fl_event_channel_set_stream_handlers(channel, args_listen_cb, args_cancel_cb, + loop, nullptr); + + g_autoptr(FlValue) listen_args = fl_value_new_string("LISTEN-ARGS"); + listen_channel(messenger, listen_args); + g_autoptr(FlValue) cancel_args = fl_value_new_string("CANCEL-ARGS"); + cancel_channel(messenger, cancel_args); + + // Blocks here until args_cancel_cb called. + g_main_loop_run(loop); + + // Manually unref because the compiler complains 'channel' is unused. + g_object_unref(channel); +} + +// Called when when the remote end starts listening on the channel. +static FlMethodErrorResponse* send_events_listen_cb(FlEventChannel* channel, + FlValue* args, + gpointer user_data) { + // Send some events. + for (int i = 0; i < 5; i++) { + g_autoptr(FlValue) event = fl_value_new_int(i); + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_event_channel_send(channel, event, nullptr, &error)); + EXPECT_EQ(error, nullptr); + } + + return nullptr; +} + +// Called when a the test engine notifies us what event we sent in the +// Test test. +static void send_events_events_cb( + FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + FlBinaryMessengerResponseHandle* response_handle, + gpointer user_data) { + TestData* data = static_cast(user_data); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + + FlValue* result = fl_method_response_get_result(response, &error); + EXPECT_NE(result, nullptr); + EXPECT_EQ(error, nullptr); + + EXPECT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(result), data->count); + data->count++; + + fl_binary_messenger_send_response(messenger, response_handle, nullptr, + nullptr); + + // Got all the results! + if (data->count == 5) { + g_main_loop_quit(data->loop); + } +} + +// Checks can send events. +TEST(FlEventChannelTest, Test) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + TestData data; + data.loop = loop; + data.count = 0; + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + FlEventChannel* channel = fl_event_channel_new( + messenger, "test/standard-event", FL_METHOD_CODEC(codec)); + fl_event_channel_set_stream_handlers(channel, send_events_listen_cb, nullptr, + &data, nullptr); + + // Listen for events from the engine. + fl_binary_messenger_set_message_handler_on_channel( + messenger, "test/events", send_events_events_cb, &data, nullptr); + + listen_channel(messenger, nullptr); + cancel_channel(messenger, nullptr); + + // Blocks here until send_events_events_cb receives the last event. + g_main_loop_run(loop); + + // Manually unref because the compiler complains 'channel' is unused. + g_object_unref(channel); +} diff --git a/shell/platform/linux/public/flutter_linux/fl_event_channel.h b/shell/platform/linux/public/flutter_linux/fl_event_channel.h new file mode 100644 index 0000000000000..7141defbed72b --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_event_channel.h @@ -0,0 +1,187 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_EVENT_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_EVENT_CHANNEL_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "fl_binary_messenger.h" +#include "fl_method_channel.h" +#include "fl_method_response.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlEventChannel, + fl_event_channel, + FL, + EVENT_CHANNEL, + GObject) + +/** + * FlEventChannel: + * + * #FlEventChannel is an object that allows sending + * an events stream to Dart code over platform channels. + * + * The following example shows how to send events on a channel: + * + * |[ + * static FlEventChannel *channel = NULL; + * static gboolean send_events = FALSE; + * + * static void event_occurs_cb (FooEvent *event) { + * if (send_events) { + * g_autoptr(FlValue) message = foo_event_to_value (event); + * g_autoptr(GError) error = NULL; + * if (!fl_event_channel_send (channel, message, NULL, &error)) { + * g_warning ("Failed to send event: %s", error->message); + * } + * } + * } + * + * static FlMethodErrorResponse* listen_cb (FlEventChannel* channel, + * FlValue *args, + * gpointer user_data) { + * send_events = TRUE; + * return NULL; + * } + * + * static FlMethodErrorResponse* cancel_cb (GObject *object, + * FlValue *args, + * gpointer user_data) { + * send_events = FALSE; + * return NULL; + * } + * + * static void setup_channel () { + * g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new (); + * channel = fl_event_channel_new (messenger, "flutter/foo", + * FL_METHOD_CODEC (codec)); + * fl_event_channel_set_stream_handlers (channel, listen_cb, cancel_cb, + * NULL, NULL); + * } + * ]| + * + * #FlEventChannel matches the EventChannel class in the Flutter + * services library. + */ + +/** + * FlEventChannelHandler: + * @channel: an #FlEventChannel. + * @args: arguments passed from the Dart end of the channel. + * @user_data: (closure): data provided when registering this handler. + * + * Function called when the stream is listened to or cancelled. + * + * Returns: (transfer full): an #FlMethodErrorResponse or %NULL if no error. + */ +typedef FlMethodErrorResponse* (*FlEventChannelHandler)(FlEventChannel* channel, + FlValue* args, + gpointer user_data); + +/** + * fl_event_channel_new: + * @messenger: an #FlBinaryMessenger. + * @name: a channel name. + * @codec: the message codec. + * + * Creates an event channel. @codec must match the codec used on the Dart + * end of the channel. + * + * Returns: a new #FlEventChannel. + */ +FlEventChannel* fl_event_channel_new(FlBinaryMessenger* messenger, + const gchar* name, + FlMethodCodec* codec); + +/** + * fl_event_channel_set_stream_handlers: + * @channel: an #FlEventChannel. + * @listen_handler: (allow-none): function to call when the Dart side of the + * channel starts listening to the stream. + * @cancel_handler: (allow-none): function to call when the Dart side of the + * channel cancels their subscription to the stream. + * @user_data: (closure): user data to pass to @listen_handler and + * @cancel_handler. + * @destroy_notify: (allow-none): a function which gets called to free + * @user_data, or %NULL. + * + * Sets the functions called when the Dart side requests the stream to start and + * finish. + * + * The handlers are removed if the channel is closed or is replaced by another + * handler, set @destroy_notify if you want to detect this. + */ +void fl_event_channel_set_stream_handlers(FlEventChannel* channel, + FlEventChannelHandler listen_handler, + FlEventChannelHandler cancel_handler, + gpointer user_data, + GDestroyNotify destroy_notify); + +/** + * fl_event_channel_send: + * @channel: an #FlEventChannel. + * @event: event to send, must match what the #FlMethodCodec supports. + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Sends an event on the channel. + * Events should only be sent once the channel is being listened to. + * + * Returns: %TRUE if successful. + */ +gboolean fl_event_channel_send(FlEventChannel* channel, + FlValue* event, + GCancellable* cancellable, + GError** error); + +/** + * fl_event_channel_send_error: + * @channel: an #FlEventChannel. + * @code: error code to send. + * @message: error message to send. + * @details: (allow-none): error details or %NULL. + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Sends an error on the channel. + * Errors should only be sent once the channel is being listened to. + * + * Returns: %TRUE if successful. + */ +gboolean fl_event_channel_send_error(FlEventChannel* channel, + const gchar* code, + const gchar* message, + FlValue* details, + GCancellable* cancellable, + GError** error); + +/** + * fl_event_channel_send_end_of_stream: + * @channel: an #FlEventChannel. + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Indicates the stream has completed. + * It is a programmer error to send any more events after calling this. + * + * Returns: %TRUE if successful. + */ +gboolean fl_event_channel_send_end_of_stream(FlEventChannel* channel, + GCancellable* cancellable, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_EVENT_CHANNEL_H_ diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index aeaef59aead42..f80bef47d730b 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/shell/platform/linux/testing/mock_engine.cc b/shell/platform/linux/testing/mock_engine.cc index c32d0b138100b..60aad75dbeab6 100644 --- a/shell/platform/linux/testing/mock_engine.cc +++ b/shell/platform/linux/testing/mock_engine.cc @@ -294,6 +294,10 @@ FlutterEngineResult FlutterEngineSendPlatformMessage( // Sends a null response. send_response(engine, message->channel, message->response_handle, nullptr, 0); + } else if (strcmp(message->channel, "test/standard-event") == 0) { + // Send a message so the shell can check the events sent. + send_message(engine, "test/events", message->message, + message->message_size); } else if (strcmp(message->channel, "test/failure") == 0) { // Generates an internal error. return kInternalInconsistency;