diff --git a/flutter/shell/platform/tizen/BUILD.gn b/flutter/shell/platform/tizen/BUILD.gn index c6000a6..713206b 100644 --- a/flutter/shell/platform/tizen/BUILD.gn +++ b/flutter/shell/platform/tizen/BUILD.gn @@ -13,6 +13,7 @@ config("flutter_tizen_config") { include_dirs = [ "${sysroot_path}/usr/include/appfw", "${sysroot_path}/usr/include/base", + "${sysroot_path}/usr/include/cbhm", "${sysroot_path}/usr/include/dali", "${sysroot_path}/usr/include/dali-adaptor", "${sysroot_path}/usr/include/dali-toolkit", @@ -165,6 +166,11 @@ template("embedder") { ] } + if (target_name == "flutter_tizen_mobile" || + target_name == "flutter_tizen_common") { + libs += [ "cbhm" ] + } + defines += invoker.defines defines += [ "FLUTTER_ENGINE_NO_PROTOTYPES" ] diff --git a/flutter/shell/platform/tizen/channels/app_control.cc b/flutter/shell/platform/tizen/channels/app_control.cc index 473a211..48da55e 100644 --- a/flutter/shell/platform/tizen/channels/app_control.cc +++ b/flutter/shell/platform/tizen/channels/app_control.cc @@ -323,6 +323,8 @@ AppControlResult AppControl::SendLaunchRequest() { AppControlResult AppControl::SendLaunchRequestWithReply( ReplyCallback on_reply) { + on_reply_ = std::move(on_reply); + auto reply_callback = [](app_control_h request, app_control_h reply, app_control_result_e result, void* user_data) { auto* app_control = static_cast(user_data); @@ -342,7 +344,6 @@ AppControlResult AppControl::SendLaunchRequestWithReply( app_control->on_reply_(EncodableValue(map)); app_control->on_reply_ = nullptr; }; - on_reply_ = on_reply; return app_control_send_launch_request(handle_, reply_callback, this); } diff --git a/flutter/shell/platform/tizen/channels/platform_channel.cc b/flutter/shell/platform/tizen/channels/platform_channel.cc index 2dd575a..a3dae27 100644 --- a/flutter/shell/platform/tizen/channels/platform_channel.cc +++ b/flutter/shell/platform/tizen/channels/platform_channel.cc @@ -57,10 +57,6 @@ constexpr char kPortraitDown[] = "DeviceOrientation.portraitDown"; constexpr char kLandscapeLeft[] = "DeviceOrientation.landscapeLeft"; constexpr char kLandscapeRight[] = "DeviceOrientation.landscapeRight"; -// Naive implementation using std::string as a container of internal clipboard -// data. -std::string text_clipboard = ""; - } // namespace PlatformChannel::PlatformChannel(BinaryMessenger* messenger, @@ -70,6 +66,13 @@ PlatformChannel::PlatformChannel(BinaryMessenger* messenger, kChannelName, &JsonMethodCodec::GetInstance())), view_(view) { +#if defined(MOBILE_PROFILE) || defined(COMMON_PROFILE) + int ret = cbhm_open_service(&cbhm_handle_); + if (ret != CBHM_ERROR_NONE) { + FT_LOG(Error) << "Failed to initialize the clipboard service."; + } +#endif + channel_->SetMethodCallHandler( [this](const MethodCall& call, std::unique_ptr> result) { @@ -77,7 +80,11 @@ PlatformChannel::PlatformChannel(BinaryMessenger* messenger, }); } -PlatformChannel::~PlatformChannel() {} +PlatformChannel::~PlatformChannel() { +#if defined(MOBILE_PROFILE) || defined(COMMON_PROFILE) + cbhm_close_service(cbhm_handle_); +#endif +} void PlatformChannel::HandleMethodCall( const MethodCall& method_call, @@ -100,18 +107,21 @@ void PlatformChannel::HandleMethodCall( result->Success(); } else if (method == kGetClipboardDataMethod) { // https://api.flutter.dev/flutter/services/Clipboard/kTextPlain-constant.html - // The API supports only kTextPlain format. + // The API only supports the plain text format. if (strcmp(arguments[0].GetString(), kTextPlainFormat) != 0) { result->Error(kUnknownClipboardFormatError, "Clipboard API only supports text."); return; } - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); - document.AddMember(rapidjson::Value(kTextKey, allocator), - rapidjson::Value(text_clipboard, allocator), allocator); - result->Success(document); + GetClipboardData([result = result.release()](const std::string& data) { + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember(rapidjson::Value(kTextKey, allocator), + rapidjson::Value(data, allocator), allocator); + result->Success(document); + delete result; + }); } else if (method == kSetClipboardDataMethod) { const rapidjson::Value& document = *arguments; auto iter = document.FindMember(kTextKey); @@ -119,14 +129,14 @@ void PlatformChannel::HandleMethodCall( result->Error(kUnknownClipboardError, "Invalid message format."); return; } - text_clipboard = iter->value.GetString(); + SetClipboardData(iter->value.GetString()); result->Success(); } else if (method == kClipboardHasStringsMethod) { rapidjson::Document document; document.SetObject(); rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); document.AddMember(rapidjson::Value(kValueKey, allocator), - rapidjson::Value(!text_clipboard.empty()), allocator); + rapidjson::Value(ClipboardHasStrings()), allocator); result->Success(document); } else if (method == kRestoreSystemUiOverlaysMethod) { RestoreSystemUiOverlays(); @@ -177,6 +187,59 @@ void PlatformChannel::HapticFeedbackVibrate(const std::string& feedback_type) { FeedbackManager::GetInstance().Vibrate(); } +void PlatformChannel::GetClipboardData(ClipboardCallback on_data) { + on_clipboard_data_ = std::move(on_data); + +#if defined(MOBILE_PROFILE) || defined(COMMON_PROFILE) + int ret = cbhm_selection_get( + cbhm_handle_, CBHM_SEL_TYPE_TEXT, + [](cbhm_h cbhm_handle, const char* buf, size_t len, + void* user_data) -> int { + auto* self = static_cast(user_data); + std::string data; + if (buf) { + data = std::string(buf, len); + } + self->on_clipboard_data_(data); + self->on_clipboard_data_ = nullptr; + return CBHM_ERROR_NONE; + }, + this); + if (ret != CBHM_ERROR_NONE) { + if (ret == CBHM_ERROR_NO_DATA) { + FT_LOG(Info) << "No clipboard data available."; + } else { + FT_LOG(Error) << "Failed to get clipboard data."; + } + on_clipboard_data_(""); + on_clipboard_data_ = nullptr; + } +#else + on_clipboard_data_(clipboard_); + on_clipboard_data_ = nullptr; +#endif +} + +void PlatformChannel::SetClipboardData(const std::string& data) { +#if defined(MOBILE_PROFILE) || defined(COMMON_PROFILE) + int ret = cbhm_selection_set(cbhm_handle_, CBHM_SEL_TYPE_TEXT, data.c_str(), + data.length()); + if (ret != CBHM_ERROR_NONE) { + FT_LOG(Error) << "Failed to set clipboard data."; + } +#else + clipboard_ = data; +#endif +} + +bool PlatformChannel::ClipboardHasStrings() { +#if defined(MOBILE_PROFILE) || defined(COMMON_PROFILE) + return cbhm_item_count_get(cbhm_handle_) > 0; +#else + return !clipboard_.empty(); +#endif +} + void PlatformChannel::RestoreSystemUiOverlays() { if (view_->GetType() != TizenViewType::kWindow) { return; diff --git a/flutter/shell/platform/tizen/channels/platform_channel.h b/flutter/shell/platform/tizen/channels/platform_channel.h index 67d47e2..cc7c1ea 100644 --- a/flutter/shell/platform/tizen/channels/platform_channel.h +++ b/flutter/shell/platform/tizen/channels/platform_channel.h @@ -5,6 +5,11 @@ #ifndef EMBEDDER_PLATFORM_CHANNEL_H_ #define EMBEDDER_PLATFORM_CHANNEL_H_ +#if defined(MOBILE_PROFILE) || defined(COMMON_PROFILE) +#include +#endif + +#include #include #include #include @@ -22,6 +27,8 @@ class PlatformChannel { virtual ~PlatformChannel(); private: + using ClipboardCallback = std::function; + void HandleMethodCall( const MethodCall& call, std::unique_ptr> result); @@ -29,6 +36,9 @@ class PlatformChannel { void SystemNavigatorPop(); void PlaySystemSound(const std::string& sound_type); void HapticFeedbackVibrate(const std::string& feedback_type); + void GetClipboardData(ClipboardCallback on_data); + void SetClipboardData(const std::string& data); + bool ClipboardHasStrings(); void RestoreSystemUiOverlays(); void SetEnabledSystemUiOverlays(const std::vector& overlays); void SetPreferredOrientations(const std::vector& orientations); @@ -36,7 +46,20 @@ class PlatformChannel { std::unique_ptr> channel_; // A reference to the native view managed by FlutterTizenView. - TizenViewBase* view_; + TizenViewBase* view_ = nullptr; + +#if defined(MOBILE_PROFILE) || defined(COMMON_PROFILE) + // The clipboard history manager. + cbhm_h cbhm_handle_ = nullptr; +#else + // A container that holds clipboard data during the engine lifetime. + // + // Only used by profiles that do not support the Tizen clipboard API + // (wearable and TV). + std::string clipboard_; +#endif + + ClipboardCallback on_clipboard_data_ = nullptr; }; } // namespace flutter diff --git a/flutter/shell/platform/tizen/channels/window_channel.h b/flutter/shell/platform/tizen/channels/window_channel.h index 73ed2e1..f23466f 100644 --- a/flutter/shell/platform/tizen/channels/window_channel.h +++ b/flutter/shell/platform/tizen/channels/window_channel.h @@ -27,7 +27,7 @@ class WindowChannel { std::unique_ptr> channel_; // A reference to the native window managed by FlutterTizenView. - TizenWindow* window_; + TizenWindow* window_ = nullptr; }; } // namespace flutter