From 0345b95be5e22e8834555a4feb2b7b2b7ddfd9b8 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 11 Nov 2018 14:00:55 -0500 Subject: [PATCH] [linux] Add MethodChannel This adds the MethodChannel and related APIs on Linux, further aligning with the Flutter APIs. As with the current state on macOS, this is implemented as an implementation detail of the existing plugin classes, so that there's no plugin API breakage at this stage. This currently deviates from the MethodChannel API on other platforms by using InvokeMethodCall instead of InvokeMethod. Addressing this will be done in a follow-up change. Part of issue #102 --- .../binary_messenger.h | 24 ++++++- .../flutter_desktop_embedding/json_plugin.h | 16 ++++- .../method_channel.h | 72 +++++++++++++++++++ .../flutter_desktop_embedding/plugin.h | 15 ++-- library/linux/src/embedder.cc | 1 - .../src/internal/engine_method_result.cc | 26 +++---- .../linux/src/internal/engine_method_result.h | 17 ++--- library/linux/src/internal/plugin_handler.cc | 51 ++++++++----- library/linux/src/internal/plugin_handler.h | 3 + library/linux/src/json_plugin.cc | 13 +++- library/linux/src/method_channel.cc | 56 +++++++++++++++ library/linux/src/plugin.cc | 5 ++ 12 files changed, 244 insertions(+), 55 deletions(-) create mode 100644 library/linux/include/flutter_desktop_embedding/method_channel.h create mode 100644 library/linux/src/method_channel.cc diff --git a/library/linux/include/flutter_desktop_embedding/binary_messenger.h b/library/linux/include/flutter_desktop_embedding/binary_messenger.h index 06baf5038..d08bd6a3b 100644 --- a/library/linux/include/flutter_desktop_embedding/binary_messenger.h +++ b/library/linux/include/flutter_desktop_embedding/binary_messenger.h @@ -14,10 +14,26 @@ #ifndef LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_BINARY_MESSENGER_H_ #define LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_BINARY_MESSENGER_H_ +#include #include +// TODO: Consider adding absl as a dependency and using absl::Span for all of +// the message/message_size pairs. namespace flutter_desktop_embedding { +// A message reply callback. +// +// Used for submitting a reply back to a Flutter message sender. +typedef std::function + BinaryReply; + +// A message handler callback. +// +// Used for receiving messages from Flutter and providing an asynchronous reply. +typedef std::function + BinaryMessageHandler; + // A protocol for a class that handles communication of binary data on named // channels to and from the Flutter engine. class BinaryMessenger { @@ -32,7 +48,13 @@ class BinaryMessenger { // TODO: Add support for a version of Send expecting a reply once // https://github.com/flutter/flutter/issues/18852 is fixed. - // TODO: Add SetMessageHandler. See Issue #102. + // Registers a message handler for incoming binary messages from the Flutter + // side on the specified channel. + // + // Replaces any existing handler. Provide a null handler to unregister the + // existing handler. + virtual void SetMessageHandler(const std::string &channel, + BinaryMessageHandler handler) = 0; }; } // namespace flutter_desktop_embedding diff --git a/library/linux/include/flutter_desktop_embedding/json_plugin.h b/library/linux/include/flutter_desktop_embedding/json_plugin.h index 06bf1e322..0eaefde2e 100644 --- a/library/linux/include/flutter_desktop_embedding/json_plugin.h +++ b/library/linux/include/flutter_desktop_embedding/json_plugin.h @@ -14,7 +14,11 @@ #ifndef LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_JSON_PLUGIN_H_ #define LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_JSON_PLUGIN_H_ +#include +#include + #include "json_method_call.h" +#include "method_channel.h" #include "plugin.h" namespace flutter_desktop_embedding { @@ -38,16 +42,24 @@ class JsonPlugin : public Plugin { std::unique_ptr result) override; protected: + // Plugin implementation: + void RegisterMethodChannels(BinaryMessenger *messenger) override; + // Identical to HandleMethodCall, except that the call has been cast to the // more specific type. Subclasses must implement this instead of // HandleMethodCall. virtual void HandleJsonMethodCall(const JsonMethodCall &method_call, std::unique_ptr result) = 0; - // Calls InvokeMethodCall with a JsonMethodCall constructed from the given - // values. + // Calls InvokeMethodCall on |method_channel_| with a JsonMethodCall + // constructed from the given values. void InvokeMethod(const std::string &method, const Json::Value &arguments = Json::Value()); + + private: + // The MethodChannel used by this plugin for communication with the Flutter + // engine. + std::unique_ptr method_channel_; }; } // namespace flutter_desktop_embedding diff --git a/library/linux/include/flutter_desktop_embedding/method_channel.h b/library/linux/include/flutter_desktop_embedding/method_channel.h new file mode 100644 index 000000000..6f4f0f604 --- /dev/null +++ b/library/linux/include/flutter_desktop_embedding/method_channel.h @@ -0,0 +1,72 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_METHOD_CHANNEL_H_ +#define LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_METHOD_CHANNEL_H_ + +#include + +#include "binary_messenger.h" +#include "method_call.h" +#include "method_codec.h" +#include "method_result.h" + +namespace flutter_desktop_embedding { + +// A handler for receiving a method call from the Flutter engine. +// +// Implementations must asynchronously call exactly one of the methods on +// |result| to indicate the resust of the method call. +typedef std::function result)> + MethodCallHandler; + +// A channel for communicating with the Flutter engine using invocation of +// asynchronous methods. +class MethodChannel { + public: + // Creates an instance that sends and receives method calls on the channel + // named |name|, encoded with |codec| and dispatched via |messenger|. + // + // TODO: Make codec optional once the standard codec is supported (Issue #67). + MethodChannel(BinaryMessenger *messenger, const std::string &name, + const MethodCodec *codec); + ~MethodChannel(); + + // Prevent copying. + MethodChannel(MethodChannel const &) = delete; + MethodChannel &operator=(MethodChannel const &) = delete; + + // Sends |method_call| to the Flutter engine on this channel. + // + // TODO: Implement InovkeMethod and remove this. This is a temporary + // implementation, since supporting InvokeMethod involves significant changes + // to other classes. + void InvokeMethodCall(const MethodCall &method_call) const; + + // TODO: Add support for a version expecting a reply once + // https://github.com/flutter/flutter/issues/18852 is fixed. + + // Registers a handler that should be called any time a method call is + // received on this channel. + void SetMethodCallHandler(MethodCallHandler handler) const; + + private: + BinaryMessenger *messenger_; + std::string name_; + const MethodCodec *codec_; +}; + +} // namespace flutter_desktop_embedding + +#endif // LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_METHOD_CHANNEL_H_ diff --git a/library/linux/include/flutter_desktop_embedding/plugin.h b/library/linux/include/flutter_desktop_embedding/plugin.h index b9dfe86b1..02affff86 100644 --- a/library/linux/include/flutter_desktop_embedding/plugin.h +++ b/library/linux/include/flutter_desktop_embedding/plugin.h @@ -59,21 +59,26 @@ class Plugin { // while waiting for this plugin to handle its platform message. virtual bool input_blocking() const { return input_blocking_; } - // Sets the pointer to the caller-owned binary messenger. + // Binds this plugin to the given caller-owned binary messenger. It must + // remain valid for the life of the plugin. // // The embedder typically sets this pointer rather than the client. - virtual void set_binary_messenger(BinaryMessenger *messenger) { - messenger_ = messenger; - } + void SetBinaryMessenger(BinaryMessenger *messenger); protected: + // Implementers should register any MethodChannels that should receive + // messages from Flutter with |messenger| when this is called. + virtual void RegisterMethodChannels(BinaryMessenger *messenger) = 0; + // Calls a method in the Flutter engine on this Plugin's channel. + // + // Deprecated. Use MethodChannel's InvokeMethodCall instead. void InvokeMethodCall(const MethodCall &method_call); private: std::string channel_; // Caller-owned instance of the binary messenger used to message the engine. - BinaryMessenger *messenger_; + const BinaryMessenger *messenger_; bool input_blocking_; }; diff --git a/library/linux/src/embedder.cc b/library/linux/src/embedder.cc index c7b0acc6b..41e36ef97 100644 --- a/library/linux/src/embedder.cc +++ b/library/linux/src/embedder.cc @@ -242,7 +242,6 @@ namespace flutter_desktop_embedding { bool AddPlugin(GLFWwindow *flutter_window, std::unique_ptr plugin) { auto state = GetSavedEmbedderState(flutter_window); - plugin->set_binary_messenger(state->plugin_handler.get()); return state->plugin_handler->AddPlugin(std::move(plugin)); } diff --git a/library/linux/src/internal/engine_method_result.cc b/library/linux/src/internal/engine_method_result.cc index 86d39e463..46850f3f2 100644 --- a/library/linux/src/internal/engine_method_result.cc +++ b/library/linux/src/internal/engine_method_result.cc @@ -17,19 +17,17 @@ namespace flutter_desktop_embedding { -EngineMethodResult::EngineMethodResult( - FlutterEngine engine, - const FlutterPlatformMessageResponseHandle *response_handle, - const MethodCodec *codec) - : engine_(engine), response_handle_(response_handle), codec_(codec) { - if (!response_handle_) { - std::cerr << "Error: Response handle must be provided for a response." +EngineMethodResult::EngineMethodResult(BinaryReply reply_handler, + const MethodCodec *codec) + : reply_handler_(std::move(reply_handler)), codec_(codec) { + if (!reply_handler_) { + std::cerr << "Error: Reply handler must be provided for a response." << std::endl; } } EngineMethodResult::~EngineMethodResult() { - if (response_handle_) { + if (reply_handler_) { // Warn, rather than send a not-implemented response, since the engine may // no longer be valid at this point. std::cerr @@ -55,20 +53,18 @@ void EngineMethodResult::ErrorInternal(const std::string &error_code, void EngineMethodResult::NotImplementedInternal() { SendResponseData(nullptr); } void EngineMethodResult::SendResponseData(const std::vector *data) { - if (!response_handle_) { + if (!reply_handler_) { std::cerr - << "Error: Response can be set only once. Ignoring duplicate response." + << "Error: Only one of Success, Error, or NotImplemented can be called," + << " and it can be called exactyl once. Ignoring duplicate result." << std::endl; return; } const uint8_t *message = data && !data->empty() ? data->data() : nullptr; size_t message_size = data ? data->size() : 0; - FlutterEngineSendPlatformMessageResponse(engine_, response_handle_, message, - message_size); - // The engine frees the response handle once - // FlutterEngineSendPlatformMessageResponse is called. - response_handle_ = nullptr; + reply_handler_(message, message_size); + reply_handler_ = nullptr; } } // namespace flutter_desktop_embedding diff --git a/library/linux/src/internal/engine_method_result.h b/library/linux/src/internal/engine_method_result.h index 467fec11a..23d9427d8 100644 --- a/library/linux/src/internal/engine_method_result.h +++ b/library/linux/src/internal/engine_method_result.h @@ -17,8 +17,7 @@ #include #include -#include - +#include "library/linux/include/flutter_desktop_embedding/binary_messenger.h" #include "library/linux/include/flutter_desktop_embedding/method_codec.h" #include "library/linux/include/flutter_desktop_embedding/method_result.h" @@ -27,15 +26,12 @@ namespace flutter_desktop_embedding { // Implemention of MethodResult that sends responses to the Flutter egnine. class EngineMethodResult : public MethodResult { public: - // Creates a result object that will send results to |engine|, tagged as - // associated with |response_handle|, encoded using |codec|. The |engine| - // and |codec| pointers must remain valid for as long as this object exists. + // Creates a result object that will send results to |reply_handler|, encoded + // using |codec|. The |codec| pointer must remain valid for as long as this + // object exists. // // If the codec is null, only NotImplemented() may be called. - EngineMethodResult( - FlutterEngine engine, - const FlutterPlatformMessageResponseHandle *response_handle, - const MethodCodec *codec); + EngineMethodResult(BinaryReply reply_handler, const MethodCodec *codec); ~EngineMethodResult(); protected: @@ -52,8 +48,7 @@ class EngineMethodResult : public MethodResult { // the engine. void SendResponseData(const std::vector *data); - FlutterEngine engine_; - const FlutterPlatformMessageResponseHandle *response_handle_; + BinaryReply reply_handler_; const MethodCodec *codec_; }; diff --git a/library/linux/src/internal/plugin_handler.cc b/library/linux/src/internal/plugin_handler.cc index 601a11f0c..4acb94e5f 100644 --- a/library/linux/src/internal/plugin_handler.cc +++ b/library/linux/src/internal/plugin_handler.cc @@ -13,6 +13,7 @@ // limitations under the License. #include "library/linux/src/internal/plugin_handler.h" +#include "library/linux/include/flutter_desktop_embedding/method_channel.h" #include "library/linux/src/internal/engine_method_result.h" #include @@ -27,6 +28,7 @@ bool PluginHandler::AddPlugin(std::unique_ptr plugin) { if (plugins_.find(plugin->channel()) != plugins_.end()) { return false; } + plugin->SetBinaryMessenger(this); plugins_.insert(std::make_pair(plugin->channel(), std::move(plugin))); return true; } @@ -36,36 +38,42 @@ void PluginHandler::HandleMethodCallMessage( std::function input_block_cb, std::function input_unblock_cb) { std::string channel(message->channel); + auto *response_handle = message->response_handle; + auto *response_engine = engine_; + BinaryReply reply_handler = [response_engine, response_handle]( + const uint8_t *reply, + const size_t reply_size) mutable { + if (!response_handle) { + std::cerr << "Error: Response can be set only once. Ignoring " + "duplicate response." + << std::endl; + return; + } + FlutterEngineSendPlatformMessageResponse(response_engine, response_handle, + reply, reply_size); + // The engine frees the response handle once + // FlutterEngineSendPlatformMessageResponse is called. + response_handle = nullptr; + }; - // Find the plugin for the channel; if there isn't one, report the failure. - if (plugins_.find(channel) == plugins_.end()) { + // Find the handler for the channel; if there isn't one, report the failure. + if (handlers_.find(channel) == handlers_.end()) { auto result = std::make_unique( - engine_, message->response_handle, nullptr); + std::move(reply_handler), nullptr); result->NotImplemented(); return; } + const BinaryMessageHandler &message_handler = handlers_[channel]; const std::unique_ptr &plugin = plugins_[channel]; - // Use the plugin's codec to decode the call and build a result handler. - const flutter_desktop_embedding::MethodCodec &codec = plugin->GetCodec(); - auto result = std::make_unique( - engine_, message->response_handle, &codec); - std::unique_ptr method_call = - codec.DecodeMethodCall(message->message, message->message_size); - if (!method_call) { - std::cerr << "Unable to construct method call from message on channel " - << message->channel << std::endl; - result->NotImplemented(); - return; - } - // Process the call, handling input blocking if requested by the plugin. - if (plugin->input_blocking()) { + if (plugin && plugin->input_blocking()) { input_block_cb(); } - plugin->HandleMethodCall(*method_call, std::move(result)); - if (plugin->input_blocking()) { + message_handler(message->message, message->message_size, + std::move(reply_handler)); + if (plugin && plugin->input_blocking()) { input_unblock_cb(); } } @@ -81,4 +89,9 @@ void PluginHandler::Send(const std::string &channel, const uint8_t *message, FlutterEngineSendPlatformMessage(engine_, &platform_message); } +void PluginHandler::SetMessageHandler(const std::string &channel, + BinaryMessageHandler handler) { + handlers_[channel] = std::move(handler); +} + } // namespace flutter_desktop_embedding diff --git a/library/linux/src/internal/plugin_handler.h b/library/linux/src/internal/plugin_handler.h index e924c28a7..46c1e06c1 100644 --- a/library/linux/src/internal/plugin_handler.h +++ b/library/linux/src/internal/plugin_handler.h @@ -63,10 +63,13 @@ class PluginHandler : public BinaryMessenger { // BinaryMessenger implementation: void Send(const std::string &channel, const uint8_t *message, const size_t message_size) const override; + void SetMessageHandler(const std::string &channel, + BinaryMessageHandler handler) override; private: FlutterEngine engine_; std::map> plugins_; + std::map handlers_; }; } // namespace flutter_desktop_embedding diff --git a/library/linux/src/json_plugin.cc b/library/linux/src/json_plugin.cc index ec2779595..2cb7b56d9 100644 --- a/library/linux/src/json_plugin.cc +++ b/library/linux/src/json_plugin.cc @@ -32,9 +32,20 @@ void JsonPlugin::HandleMethodCall(const MethodCall &method_call, std::move(result)); } +void JsonPlugin::RegisterMethodChannels(BinaryMessenger *messenger) { + method_channel_ = + std::make_unique(messenger, channel(), &GetCodec()); + + MethodCallHandler handler = [this](const MethodCall &call, + std::unique_ptr result) { + HandleMethodCall(call, std::move(result)); + }; + method_channel_->SetMethodCallHandler(std::move(handler)); +} + void JsonPlugin::InvokeMethod(const std::string &method, const Json::Value &arguments) { - InvokeMethodCall(JsonMethodCall(method, arguments)); + method_channel_->InvokeMethodCall(JsonMethodCall(method, arguments)); } } // namespace flutter_desktop_embedding diff --git a/library/linux/src/method_channel.cc b/library/linux/src/method_channel.cc new file mode 100644 index 000000000..b4201310f --- /dev/null +++ b/library/linux/src/method_channel.cc @@ -0,0 +1,56 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "library/linux/include/flutter_desktop_embedding/method_channel.h" + +#include + +#include "library/linux/src/internal/engine_method_result.h" + +namespace flutter_desktop_embedding { + +MethodChannel::MethodChannel(BinaryMessenger *messenger, + const std::string &name, const MethodCodec *codec) + : messenger_(messenger), name_(name), codec_(codec) {} + +MethodChannel::~MethodChannel() {} + +void MethodChannel::InvokeMethodCall(const MethodCall &method_call) const { + std::unique_ptr> message = + codec_->EncodeMethodCall(method_call); + messenger_->Send(name_, message->data(), message->size()); +} + +void MethodChannel::SetMethodCallHandler(MethodCallHandler handler) const { + const auto *codec = codec_; + std::string channel_name = name_; + BinaryMessageHandler binary_handler = [handler, codec, channel_name]( + const uint8_t *message, + const size_t message_size, + BinaryReply reply) { + // Use this channel's codec to decode the call and build a result handler. + auto result = std::make_unique(std::move(reply), codec); + 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; + result->NotImplemented(); + return; + } + handler(*method_call, std::move(result)); + }; + messenger_->SetMessageHandler(name_, std::move(binary_handler)); +} + +} // namespace flutter_desktop_embedding diff --git a/library/linux/src/plugin.cc b/library/linux/src/plugin.cc index 041f6c651..aaf63642e 100644 --- a/library/linux/src/plugin.cc +++ b/library/linux/src/plugin.cc @@ -22,6 +22,11 @@ Plugin::Plugin(const std::string &channel, bool input_blocking) Plugin::~Plugin() {} +void Plugin::SetBinaryMessenger(BinaryMessenger *messenger) { + messenger_ = messenger; + RegisterMethodChannels(messenger); +} + void Plugin::InvokeMethodCall(const MethodCall &method_call) { if (!messenger_) { return;