diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 0f60c2c5faa73..5e651ebcdad56 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -951,6 +951,9 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/compositor_context.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_fakes.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_intl.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_intl.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_intl_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/extract_far.dart diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index 9ce49f4f634c0..452e3f76f94f3 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -256,6 +256,9 @@ executable("flutter_runner_unittests") { "accessibility_bridge.h", "accessibility_bridge_unittest.cc", "flutter_runner_fakes.h", + "fuchsia_intl.cc", + "fuchsia_intl.h", + "fuchsia_intl_unittest.cc", "logging.h", "platform_view.cc", "platform_view.h", @@ -298,6 +301,13 @@ fuchsia_archive("flutter_runner_tests") { binary = "$target_name" + resources = [ + { + path = rebase_path("//third_party/icu/common/icudtl.dat") + dest = "icudtl.dat" + }, + ] + meta_dir = "$flutter_root/shell/platform/fuchsia/flutter/meta" libraries = common_libs diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index 58088f12735ec..340468d66c30c 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -5,6 +5,7 @@ #include "engine.h" #include +#include #include #include "flutter/common/task_runners.h" @@ -14,6 +15,7 @@ #include "flutter/runtime/dart_vm_lifecycle.h" #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/common/run_configuration.h" +#include "fuchsia_intl.h" #include "platform_view.h" #include "runtime/dart/utils/files.h" #include "task_runner_adapter.h" @@ -39,6 +41,13 @@ static void UpdateNativeThreadLabelNames(const std::string& label, set_thread_name(runners.GetIOTaskRunner(), label, ".io"); } +static fml::RefPtr MakeLocalizationPlatformMessage( + const fuchsia::intl::Profile& intl_profile) { + return fml::MakeRefCounted( + "flutter/localization", MakeLocalizationPlatformMessageData(intl_profile), + nullptr); +} + Engine::Engine(Delegate& delegate, std::string thread_label, std::shared_ptr svc, @@ -110,8 +119,7 @@ Engine::Engine(Delegate& delegate, on_create_platform_view = fml::MakeCopyable( [debug_label = thread_label_, view_ref_control = std::move(view_ref_control), - view_ref = std::move(view_ref), - runner_services = std::move(runner_services), + view_ref = std::move(view_ref), runner_services, parent_environment_service_provider = std::move(parent_environment_service_provider), session_listener_request = std::move(session_listener_request), @@ -255,6 +263,49 @@ Engine::Engine(Delegate& delegate, // notification. Fire one eagerly. shell_->GetPlatformView()->NotifyCreated(); + // Connect to the intl property provider. + { + intl_property_provider_.set_error_handler([](zx_status_t status) { + FML_LOG(ERROR) << "Failed to connect to " + << fuchsia::intl::PropertyProvider::Name_ << ": " + << zx_status_get_string(status); + }); + + // Note that we're using the runner's services, not the component's. + // Flutter locales should be updated regardless of whether the component has + // direct access to the fuchsia.intl.PropertyProvider service. + ZX_ASSERT(runner_services->Connect(intl_property_provider_.NewRequest()) == + ZX_OK); + + auto get_profile_callback = [flutter_runner_engine = + weak_factory_.GetWeakPtr()]( + const fuchsia::intl::Profile& profile) { + if (!flutter_runner_engine) { + return; + } + if (!profile.has_locales()) { + FML_LOG(WARNING) << "Got intl Profile without locales"; + } + auto message = MakeLocalizationPlatformMessage(profile); + FML_VLOG(-1) << "Sending LocalizationPlatformMessage"; + flutter_runner_engine->shell_->GetPlatformView()->DispatchPlatformMessage( + message); + }; + + FML_VLOG(-1) << "Requesting intl Profile"; + + // Make the initial request + intl_property_provider_->GetProfile(get_profile_callback); + + // And register for changes + intl_property_provider_.events().OnChange = [this, runner_services, + get_profile_callback]() { + FML_VLOG(-1) << fuchsia::intl::PropertyProvider::Name_ << ": OnChange"; + runner_services->Connect(intl_property_provider_.NewRequest()); + intl_property_provider_->GetProfile(get_profile_callback); + }; + } + // Launch the engine in the appropriate configuration. auto run_configuration = flutter::RunConfiguration::InferFromSettings( settings_, task_runners.GetIOTaskRunner()); diff --git a/shell/platform/fuchsia/flutter/engine.h b/shell/platform/fuchsia/flutter/engine.h index a208a6a242996..7b233dcfcf4b4 100644 --- a/shell/platform/fuchsia/flutter/engine.h +++ b/shell/platform/fuchsia/flutter/engine.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_ENGINE_H_ #define FLUTTER_SHELL_PLATFORM_FUCHSIA_ENGINE_H_ +#include #include #include #include @@ -58,6 +59,8 @@ class Engine final { std::unique_ptr shell_; zx::event vsync_event_; fml::WeakPtrFactory weak_factory_; + // A stub for the FIDL protocol fuchsia.intl.PropertyProvider. + fuchsia::intl::PropertyProviderPtr intl_property_provider_; void OnMainIsolateStart(); diff --git a/shell/platform/fuchsia/flutter/engine_flutter_runner.gni b/shell/platform/fuchsia/flutter/engine_flutter_runner.gni index 7afc34a0c55bc..ce4fd421d6330 100644 --- a/shell/platform/fuchsia/flutter/engine_flutter_runner.gni +++ b/shell/platform/fuchsia/flutter/engine_flutter_runner.gni @@ -50,6 +50,8 @@ template("flutter_runner") { "compositor_context.h", "engine.cc", "engine.h", + "fuchsia_intl.cc", + "fuchsia_intl.h", "isolate_configurator.cc", "isolate_configurator.h", "logging.h", @@ -111,6 +113,7 @@ template("flutter_runner") { "$fuchsia_sdk_root/fidl:fuchsia.accessibility.semantics", "$fuchsia_sdk_root/fidl:fuchsia.fonts", "$fuchsia_sdk_root/fidl:fuchsia.images", + "$fuchsia_sdk_root/fidl:fuchsia.intl", "$fuchsia_sdk_root/fidl:fuchsia.io", "$fuchsia_sdk_root/fidl:fuchsia.modular", "$fuchsia_sdk_root/fidl:fuchsia.sys", diff --git a/shell/platform/fuchsia/flutter/fuchsia_intl.cc b/shell/platform/fuchsia/flutter/fuchsia_intl.cc new file mode 100644 index 0000000000000..e1f4fa6238ce0 --- /dev/null +++ b/shell/platform/fuchsia/flutter/fuchsia_intl.cc @@ -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. + +#include "fuchsia_intl.h" + +#include +#include +#include + +#include "loop.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" +#include "runner.h" +#include "runtime/dart/utils/tempfs.h" +#include "third_party/icu/source/common/unicode/bytestream.h" +#include "third_party/icu/source/common/unicode/errorcode.h" +#include "third_party/icu/source/common/unicode/locid.h" +#include "third_party/icu/source/common/unicode/strenum.h" +#include "third_party/icu/source/common/unicode/stringpiece.h" +#include "third_party/icu/source/common/unicode/uloc.h" + +using icu::Locale; + +namespace flutter_runner { + +using fuchsia::intl::Profile; + +std::vector MakeLocalizationPlatformMessageData( + const Profile& intl_profile) { + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + document.SetObject(); + document.AddMember("method", "setLocale", allocator); + rapidjson::Value args(rapidjson::kArrayType); + + for (const auto& locale_id : intl_profile.locales()) { + UErrorCode error_code = U_ZERO_ERROR; + icu::Locale locale = icu::Locale::forLanguageTag(locale_id.id, error_code); + if (U_FAILURE(error_code)) { + FML_LOG(ERROR) << "Error parsing locale ID \"" << locale_id.id << "\""; + continue; + } + args.PushBack(rapidjson::Value().SetString(locale.getLanguage(), allocator), + allocator); + + auto country = locale.getCountry() != nullptr ? locale.getCountry() : ""; + args.PushBack(rapidjson::Value().SetString(country, allocator), allocator); + + auto script = locale.getScript() != nullptr ? locale.getScript() : ""; + args.PushBack(rapidjson::Value().SetString(script, allocator), allocator); + + std::string variant = + locale.getVariant() != nullptr ? locale.getVariant() : ""; + // ICU4C capitalizes the variant for backward compatibility, even though + // the preferred form is lowercase. So we lowercase here. + std::transform(begin(variant), end(variant), begin(variant), + [](unsigned char c) { return std::tolower(c); }); + args.PushBack(rapidjson::Value().SetString(variant, allocator), allocator); + } + + document.AddMember("args", args, allocator); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + auto data = reinterpret_cast(buffer.GetString()); + return std::vector(data, data + buffer.GetSize()); +} + +} // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/fuchsia_intl.h b/shell/platform/fuchsia/flutter/fuchsia_intl.h new file mode 100644 index 0000000000000..df099d76acd18 --- /dev/null +++ b/shell/platform/fuchsia/flutter/fuchsia_intl.h @@ -0,0 +1,23 @@ +// 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_FUCHSIA_FUCHSIA_INTL_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FUCHSIA_INTL_H_ + +#include + +namespace flutter_runner { + +// Make a byte vector containing the JSON string used for a localization +// PlatformMessage, using the locale list in the given Profile. +// +// This method does not return a `fml::RefPtr` for +// testing convenience; that would require an unreasonably large set of +// dependencies for the unit tests. +std::vector MakeLocalizationPlatformMessageData( + const fuchsia::intl::Profile& intl_profile); + +} // namespace flutter_runner + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_FUCHSIA_INTL_H_ diff --git a/shell/platform/fuchsia/flutter/fuchsia_intl_unittest.cc b/shell/platform/fuchsia/flutter/fuchsia_intl_unittest.cc new file mode 100644 index 0000000000000..f7d1e09be1881 --- /dev/null +++ b/shell/platform/fuchsia/flutter/fuchsia_intl_unittest.cc @@ -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. + +#include +#include + +#include "flutter/fml/icu_util.h" + +#include "fuchsia_intl.h" + +using fuchsia::intl::CalendarId; +using fuchsia::intl::LocaleId; +using fuchsia::intl::Profile; +using fuchsia::intl::TemperatureUnit; +using fuchsia::intl::TimeZoneId; + +namespace flutter_runner { +namespace { + +class FuchsiaIntlTest : public testing::Test { + public: + static void SetUpTestCase() { + testing::Test::SetUpTestCase(); + // The icudtl data must be present as a resource in the package for this + // load to succeed. + fml::icu::InitializeICU("/pkg/data/icudtl.dat"); + } +}; + +TEST_F(FuchsiaIntlTest, MakeLocalizationPlatformMessageData_SimpleLocale) { + Profile profile{}; + profile.set_locales({LocaleId{.id = "en-US"}}); + const std::string expected = + R"({"method":"setLocale","args":["en","US","",""]})"; + const auto actual = MakeLocalizationPlatformMessageData(profile); + ASSERT_EQ(expected, std::string(actual.begin(), actual.end())); +} + +TEST_F(FuchsiaIntlTest, MakeLocalizationPlatformMessageData_OneLocale) { + Profile profile{}; + profile + .set_locales({LocaleId{.id = "en-US-u-ca-gregory-fw-sun-hc-h12-ms-" + "ussystem-nu-latn-tz-usnyc"}}) + .set_calendars({CalendarId{.id = "und-u-gregory"}}) + .set_time_zones({TimeZoneId{.id = "America/New_York"}}) + .set_temperature_unit(TemperatureUnit::FAHRENHEIT); + const std::string expected = + R"({"method":"setLocale","args":["en","US","",""]})"; + const auto actual = MakeLocalizationPlatformMessageData(profile); + ASSERT_EQ(expected, std::string(actual.begin(), actual.end())); +} + +TEST_F(FuchsiaIntlTest, MakeLocalizationPlatformMessageData_MultipleLocales) { + Profile profile{}; + profile + .set_locales({LocaleId{.id = "en-US-u-ca-gregory-fw-sun-hc-h12-ms-" + "ussystem-nu-latn-tz-usnyc"}, + LocaleId{.id = "sl-Latn-IT-nedis"}, + LocaleId{.id = "zh-Hans"}, LocaleId{.id = "sr-Cyrl-CS"}}) + .set_calendars({CalendarId{.id = "und-u-gregory"}}) + .set_time_zones({TimeZoneId{.id = "America/New_York"}}) + .set_temperature_unit(TemperatureUnit::FAHRENHEIT); + const std::string expected = + R"({"method":"setLocale","args":["en","US","","","sl","IT","Latn","nedis",)" + R"("zh","","Hans","","sr","CS","Cyrl",""]})"; + const auto actual = MakeLocalizationPlatformMessageData(profile); + ASSERT_EQ(expected, std::string(actual.begin(), actual.end())); +} + +} // namespace +} // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx index d4fd774fe4dcc..bdfec3cd2e4d0 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx @@ -11,10 +11,11 @@ "services": [ "fuchsia.accessibility.SettingsManager", "fuchsia.accessibility.semantics.SemanticsManager", - "fuchsia.device.NameProvider", "fuchsia.deprecatedtimezone.Timezone", + "fuchsia.device.NameProvider", "fuchsia.feedback.CrashReporter", "fuchsia.fonts.Provider", + "fuchsia.intl.PropertyProvider", "fuchsia.net.NameLookup", "fuchsia.netstack.Netstack", "fuchsia.posix.socket.Provider", diff --git a/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx index 039fc303fded8..912b534df93a2 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx @@ -11,10 +11,11 @@ "services": [ "fuchsia.accessibility.SettingsManager", "fuchsia.accessibility.semantics.SemanticsManager", - "fuchsia.device.NameProvider", "fuchsia.deprecatedtimezone.Timezone", + "fuchsia.device.NameProvider", "fuchsia.feedback.CrashReporter", "fuchsia.fonts.Provider", + "fuchsia.intl.PropertyProvider", "fuchsia.net.NameLookup", "fuchsia.netstack.Netstack", "fuchsia.posix.socket.Provider",