diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d190e032fd7d4..a3583b03979c1 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -782,10 +782,15 @@ FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/basic_message_ch FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/byte_stream_wrappers.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/encodable_value_unittests.cc FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/engine_method_result.cc +FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/event_channel_unittests.cc FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/basic_message_channel.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/engine_method_result.h +FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/event_channel.h +FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/event_sink.h +FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler.h +FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler_functions.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/message_codec.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_call.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_channel.h diff --git a/shell/platform/common/cpp/client_wrapper/BUILD.gn b/shell/platform/common/cpp/client_wrapper/BUILD.gn index 94e68dc86084a..844be28bc3e2b 100644 --- a/shell/platform/common/cpp/client_wrapper/BUILD.gn +++ b/shell/platform/common/cpp/client_wrapper/BUILD.gn @@ -44,6 +44,7 @@ executable("client_wrapper_unittests") { sources = [ "basic_message_channel_unittests.cc", "encodable_value_unittests.cc", + "event_channel_unittests.cc", "method_call_unittests.cc", "method_channel_unittests.cc", "method_result_functions_unittests.cc", diff --git a/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni b/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni index 86c3280ea3699..18b0897e2a987 100644 --- a/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni +++ b/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni @@ -8,6 +8,10 @@ core_cpp_client_wrapper_includes = "include/flutter/binary_messenger.h", "include/flutter/encodable_value.h", "include/flutter/engine_method_result.h", + "include/flutter/event_channel.h", + "include/flutter/event_sink.h", + "include/flutter/event_stream_handler_functions.h", + "include/flutter/event_stream_handler.h", "include/flutter/message_codec.h", "include/flutter/method_call.h", "include/flutter/method_channel.h", diff --git a/shell/platform/common/cpp/client_wrapper/event_channel_unittests.cc b/shell/platform/common/cpp/client_wrapper/event_channel_unittests.cc new file mode 100644 index 0000000000000..b06ff03f8dfdf --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/event_channel_unittests.cc @@ -0,0 +1,206 @@ +// 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/common/cpp/client_wrapper/include/flutter/event_channel.h" + +#include +#include + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler_functions.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h" +#include "gtest/gtest.h" + +namespace flutter { + +namespace { + +class TestBinaryMessenger : public BinaryMessenger { + public: + void Send(const std::string& channel, + const uint8_t* message, + const size_t message_size, + BinaryReply reply) const override {} + + void SetMessageHandler(const std::string& channel, + BinaryMessageHandler handler) override { + last_message_handler_channel_ = channel; + last_message_handler_ = handler; + } + + std::string last_message_handler_channel() { + return last_message_handler_channel_; + } + + BinaryMessageHandler last_message_handler() { return last_message_handler_; } + + private: + std::string last_message_handler_channel_; + BinaryMessageHandler last_message_handler_; +}; + +} // namespace + +// Tests that SetStreamHandler sets a handler that correctly interacts with +// the binary messenger. +TEST(EventChannelTest, Registration) { + TestBinaryMessenger messenger; + const std::string channel_name("some_channel"); + const StandardMethodCodec& codec = StandardMethodCodec::GetInstance(); + EventChannel channel(&messenger, channel_name, &codec); + + bool on_listen_called = false; + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [&on_listen_called]( + const flutter::EncodableValue* arguments, + std::unique_ptr>&& events) + -> std::unique_ptr> { + on_listen_called = true; + return nullptr; + }, + [](const flutter::EncodableValue* arguments) + -> std::unique_ptr> { + return nullptr; + }); + channel.SetStreamHandler(std::move(handler)); + EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); + EXPECT_NE(messenger.last_message_handler(), nullptr); + + // Send dummy listen message. + MethodCall call("listen", nullptr); + auto message = codec.EncodeMethodCall(call); + messenger.last_message_handler()( + message->data(), message->size(), + [](const uint8_t* reply, const size_t reply_size) {}); + + // Check results. + EXPECT_EQ(on_listen_called, true); +} + +// Tests that SetStreamHandler with a null handler unregisters the handler. +TEST(EventChannelTest, Unregistration) { + TestBinaryMessenger messenger; + const std::string channel_name("some_channel"); + const StandardMethodCodec& codec = StandardMethodCodec::GetInstance(); + EventChannel channel(&messenger, channel_name, &codec); + + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [](const flutter::EncodableValue* arguments, + std::unique_ptr>&& events) + -> std::unique_ptr> { + return nullptr; + }, + [](const flutter::EncodableValue* arguments) + -> std::unique_ptr> { + return nullptr; + }); + channel.SetStreamHandler(std::move(handler)); + EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); + EXPECT_NE(messenger.last_message_handler(), nullptr); + + channel.SetStreamHandler(nullptr); + EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); + EXPECT_EQ(messenger.last_message_handler(), nullptr); +} + +// Test that OnCancel callback sequence. +TEST(EventChannelTest, Cancel) { + TestBinaryMessenger messenger; + const std::string channel_name("some_channel"); + const StandardMethodCodec& codec = StandardMethodCodec::GetInstance(); + EventChannel channel(&messenger, channel_name, &codec); + + bool on_listen_called = false; + bool on_cancel_called = false; + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [&on_listen_called]( + const flutter::EncodableValue* arguments, + std::unique_ptr>&& events) + -> std::unique_ptr> { + on_listen_called = true; + return nullptr; + }, + [&on_cancel_called](const flutter::EncodableValue* arguments) + -> std::unique_ptr> { + on_cancel_called = true; + return nullptr; + }); + channel.SetStreamHandler(std::move(handler)); + EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); + EXPECT_NE(messenger.last_message_handler(), nullptr); + + // Send dummy listen message. + MethodCall call_listen("listen", nullptr); + auto message = codec.EncodeMethodCall(call_listen); + messenger.last_message_handler()( + message->data(), message->size(), + [](const uint8_t* reply, const size_t reply_size) {}); + EXPECT_EQ(on_listen_called, true); + + // Send dummy cancel message. + MethodCall call_cancel("cancel", nullptr); + message = codec.EncodeMethodCall(call_cancel); + messenger.last_message_handler()( + message->data(), message->size(), + [](const uint8_t* reply, const size_t reply_size) {}); + + // Check results. + EXPECT_EQ(on_cancel_called, true); +} + +// Pseudo test when user re-registers or call OnListen to the same channel. +// Confirm that OnCancel is called and OnListen is called again +// when user re-registers the same channel that has already started +// communication. +TEST(EventChannelTest, ReRegistration) { + TestBinaryMessenger messenger; + const std::string channel_name("some_channel"); + const StandardMethodCodec& codec = StandardMethodCodec::GetInstance(); + EventChannel channel(&messenger, channel_name, &codec); + + bool on_listen_called = false; + bool on_cancel_called = false; + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [&on_listen_called]( + const flutter::EncodableValue* arguments, + std::unique_ptr>&& events) + -> std::unique_ptr> { + on_listen_called = true; + return nullptr; + }, + [&on_cancel_called](const flutter::EncodableValue* arguments) + -> std::unique_ptr> { + on_cancel_called = true; + return nullptr; + }); + channel.SetStreamHandler(std::move(handler)); + EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); + EXPECT_NE(messenger.last_message_handler(), nullptr); + + // Send dummy listen message. + MethodCall call("listen", nullptr); + auto message = codec.EncodeMethodCall(call); + messenger.last_message_handler()( + message->data(), message->size(), + [](const uint8_t* reply, const size_t reply_size) {}); + EXPECT_EQ(on_listen_called, true); + + // Send second dummy message to test StreamHandler's OnCancel + // method is called before OnListen method is called. + on_listen_called = false; + message = codec.EncodeMethodCall(call); + messenger.last_message_handler()( + message->data(), message->size(), + [](const uint8_t* reply, const size_t reply_size) {}); + + // Check results. + EXPECT_EQ(on_cancel_called, true); + EXPECT_EQ(on_listen_called, true); +} + +} // namespace flutter diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/event_channel.h b/shell/platform/common/cpp/client_wrapper/include/flutter/event_channel.h new file mode 100644 index 0000000000000..301c73a418195 --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/event_channel.h @@ -0,0 +1,171 @@ +// 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_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_ + +#include +#include +#include + +#include "binary_messenger.h" +#include "engine_method_result.h" +#include "event_sink.h" +#include "event_stream_handler.h" + +namespace flutter { + +// A named channel for communicating with the Flutter application using +// asynchronous event streams. Incoming requests for event stream setup are +// decoded from binary on receipt, and C++ responses and events are encoded into +// binary before being transmitted back to Flutter. The MethodCodec used must be +// compatible with the one used by the Flutter application. This can be achieved +// by creating an EventChannel +// ("https://docs.flutter.io/flutter/services/EventChannel-class.html") +// counterpart of this channel on the Dart side. +// The C++ type of stream configuration arguments, events, and error details are +// templated, but only values supported by the specified MethodCodec can be +// used. +template +class EventChannel { + public: + // Creates an instance that sends and receives event handler on the channel + // named |name|, encoded with |codec| and dispatched via |messenger|. + EventChannel(BinaryMessenger* messenger, + const std::string& name, + const MethodCodec* codec) + : messenger_(messenger), name_(name), codec_(codec) {} + ~EventChannel() = default; + + // Prevent copying. + EventChannel(EventChannel const&) = delete; + EventChannel& operator=(EventChannel const&) = delete; + + // Registers a stream handler on this channel. + // If no handler has been registered, any incoming stream setup requests will + // be handled silently by providing an empty stream. + void SetStreamHandler(std::unique_ptr> handler) { + if (!handler) { + messenger_->SetMessageHandler(name_, nullptr); + is_listening_ = false; + return; + } + + // std::function requires a copyable lambda, so convert to a shared pointer. + // This is safe since only one copy of the shared_pointer will ever be + // accessed. + std::shared_ptr> shared_handler(handler.release()); + const MethodCodec* codec = codec_; + const std::string channel_name = name_; + const BinaryMessenger* messenger = messenger_; + BinaryMessageHandler binary_handler = [shared_handler, codec, channel_name, + messenger, + this](const uint8_t* message, + const size_t message_size, + BinaryReply reply) { + constexpr char kOnListenMethod[] = "listen"; + constexpr char kOnCancelMethod[] = "cancel"; + + std::unique_ptr> method_call = + codec->DecodeMethodCall(message, message_size); + if (!method_call) { + std::cerr << "Unable to construct method call from message on channel: " + << channel_name << std::endl; + reply(nullptr, 0); + return; + } + + const std::string& method = method_call->method_name(); + if (method.compare(kOnListenMethod) == 0) { + if (is_listening_) { + std::unique_ptr> error = + shared_handler->OnCancel(nullptr); + if (error) { + std::cerr << "Failed to cancel existing stream: " + << (error->error_code) << ", " << (error->error_message) + << ", " << (error->error_details); + } + } + is_listening_ = true; + + std::unique_ptr> result; + auto sink = std::make_unique( + messenger, channel_name, codec); + std::unique_ptr> error = + shared_handler->OnListen(method_call->arguments(), std::move(sink)); + if (error) { + result = codec->EncodeErrorEnvelope( + error->error_code, error->error_message, error->error_details); + } else { + result = codec->EncodeSuccessEnvelope(); + } + reply(result->data(), result->size()); + } else if (method.compare(kOnCancelMethod) == 0) { + std::unique_ptr> result; + if (is_listening_) { + std::unique_ptr> error = + shared_handler->OnCancel(method_call->arguments()); + if (error) { + result = codec->EncodeErrorEnvelope( + error->error_code, error->error_message, error->error_details); + } else { + result = codec->EncodeSuccessEnvelope(); + } + is_listening_ = false; + } else { + result = codec->EncodeErrorEnvelope( + "error", "No active stream to cancel", nullptr); + } + reply(result->data(), result->size()); + } else { + reply(nullptr, 0); + } + }; + messenger_->SetMessageHandler(name_, std::move(binary_handler)); + } + + private: + class EventSinkImplementation : public EventSink { + public: + EventSinkImplementation(const BinaryMessenger* messenger, + const std::string& name, + const MethodCodec* codec) + : messenger_(messenger), name_(name), codec_(codec) {} + ~EventSinkImplementation() = default; + + // Prevent copying. + EventSinkImplementation(EventSinkImplementation const&) = delete; + EventSinkImplementation& operator=(EventSinkImplementation const&) = delete; + + private: + const BinaryMessenger* messenger_; + const std::string name_; + const MethodCodec* codec_; + + protected: + void SuccessInternal(T* event = nullptr) override { + auto result = codec_->EncodeSuccessEnvelope(event); + messenger_->Send(name_, result->data(), result->size()); + } + + void ErrorInternal(const std::string& error_code, + const std::string& error_message, + T* error_details) override { + auto result = + codec_->EncodeErrorEnvelope(error_code, error_message, error_details); + messenger_->Send(name_, result->data(), result->size()); + } + + void EndOfStreamInternal() override { messenger_->Send(name_, nullptr, 0); } + }; + + BinaryMessenger* messenger_; + const std::string name_; + const MethodCodec* codec_; + bool is_listening_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_ diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/event_sink.h b/shell/platform/common/cpp/client_wrapper/include/flutter/event_sink.h new file mode 100644 index 0000000000000..6223ad7b6a572 --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/event_sink.h @@ -0,0 +1,51 @@ +// 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_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_SINK_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_SINK_H_ + +namespace flutter { + +// Event callback. Events to be sent to Flutter application +// act as clients of this interface for sending events. +template +class EventSink { + public: + EventSink() = default; + virtual ~EventSink() = default; + + // Prevent copying. + EventSink(EventSink const&) = delete; + EventSink& operator=(EventSink const&) = delete; + + // Consumes a successful event. + void Success(T* event = nullptr) { SuccessInternal(event); } + + // Consumes an error event. + void Error(const std::string& error_code, + const std::string& error_message = "", + T* error_details = nullptr) { + ErrorInternal(error_code, error_message, error_details); + } + + // Consumes end of stream. Ensuing calls to Success() or + // Error(), if any, are ignored. + void EndOfStream() { EndOfStreamInternal(); } + + protected: + // Implementation of the public interface, to be provided by subclasses. + virtual void SuccessInternal(T* event = nullptr) = 0; + + // Implementation of the public interface, to be provided by subclasses. + virtual void ErrorInternal(const std::string& error_code, + const std::string& error_message, + T* error_details) = 0; + + // Implementation of the public interface, to be provided by subclasses. + virtual void EndOfStreamInternal() = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_SINK_H_ diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler.h b/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler.h new file mode 100644 index 0000000000000..50d57b887f029 --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler.h @@ -0,0 +1,72 @@ +// 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_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_H_ + +#include "event_sink.h" + +namespace flutter { + +template +struct StreamHandlerError { + const std::string& error_code; + const std::string& error_message; + const T* error_details; + + StreamHandlerError(const std::string& error_code, + const std::string& error_message, + const T* error_details) + : error_code(error_code), + error_message(error_message), + error_details(error_details) {} +}; + +// Handler of stream setup and tear-down requests. +// Implementations must be prepared to accept sequences of alternating calls to +// OnListen() and OnCancel(). Implementations should ideally consume no +// resources when the last such call is not OnListen(). In typical situations, +// this means that the implementation should register itself with +// platform-specific event sources OnListen() and deregister again OnCancel(). +template +class StreamHandler { + public: + StreamHandler() = default; + virtual ~StreamHandler() = default; + + // Prevent copying. + StreamHandler(StreamHandler const&) = delete; + StreamHandler& operator=(StreamHandler const&) = delete; + + // Handles a request to set up an event stream. Returns nullptr on success, + // or an error on failure. + // |arguments| is stream configuration arguments and + // |events| is an EventSink for emitting events to the Flutter receiver. + std::unique_ptr> OnListen( + const T* arguments, + std::unique_ptr>&& events) { + return OnListenInternal(arguments, std::move(events)); + } + + // Handles a request to tear down the most recently created event stream. + // Returns nullptr on success, or an error on failure. + // |arguments| is stream configuration arguments. + std::unique_ptr> OnCancel(const T* arguments) { + return OnCancelInternal(arguments); + } + + protected: + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr> OnListenInternal( + const T* arguments, + std::unique_ptr>&& events) = 0; + + // Implementation of the public interface, to be provided by subclasses. + virtual std::unique_ptr> OnCancelInternal( + const T* arguments) = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_H_ diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler_functions.h b/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler_functions.h new file mode 100644 index 0000000000000..88fe8a1bc3639 --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/event_stream_handler_functions.h @@ -0,0 +1,76 @@ +// 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_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_FUNCTIONS_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_FUNCTIONS_H_ + +#include + +#include "event_sink.h" +#include "event_stream_handler.h" + +namespace flutter { + +// Handler types for each of the StreamHandler setup and tear-down +// requests. +template +using StreamHandlerListen = + std::function>( + const T* arguments, + std::unique_ptr>&& events)>; + +template +using StreamHandlerCancel = + std::function>(const T* arguments)>; + +// An implementation of StreamHandler that pass calls through to +// provided function objects. +template +class StreamHandlerFunctions : public StreamHandler { + public: + // Creates a handler object that calls the provided functions + // for the corresponding StreamHandler outcomes. + StreamHandlerFunctions(StreamHandlerListen on_listen, + StreamHandlerCancel on_cancel) + : on_listen_(on_listen), on_cancel_(on_cancel) {} + + virtual ~StreamHandlerFunctions() = default; + + // Prevent copying. + StreamHandlerFunctions(StreamHandlerFunctions const&) = delete; + StreamHandlerFunctions& operator=(StreamHandlerFunctions const&) = delete; + + protected: + // |flutter::StreamHandler| + std::unique_ptr> OnListenInternal( + const T* arguments, + std::unique_ptr>&& events) override { + if (on_listen_) { + return on_listen_(arguments, std::move(events)); + } + + auto error = std::make_unique>( + "error", "No OnListen handler set", nullptr); + return std::move(error); + } + + // |flutter::StreamHandler| + std::unique_ptr> OnCancelInternal( + const T* arguments) override { + if (on_cancel_) { + return on_cancel_(arguments); + } + + auto error = std::make_unique>( + "error", "No OnCancel handler set", nullptr); + return std::move(error); + } + + StreamHandlerListen on_listen_; + StreamHandlerCancel on_cancel_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_STREAM_HANDLER_FUNCTIONS_H_