diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index c9b3589182e1f9625eceeb13e1bcd96f92b60b71..0ccbd28e2a9df0a5c7367f514dc3ae92d0284056 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -6,6 +6,9 @@ #define RAPIDJSON_HAS_STDSTRING 1 #include +#include +#include +#include #include "flutter/fml/build_config.h" #include "flutter/fml/closure.h" @@ -910,6 +913,54 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, }; } + flutter::PlatformViewEmbedder::ComputePlatformResolvedLocaleCallback + compute_platform_resolved_locale_callback = nullptr; + if (SAFE_ACCESS(args, compute_platform_resolved_locale_callback, nullptr) != + nullptr) { + compute_platform_resolved_locale_callback = + [ptr = args->compute_platform_resolved_locale_callback]( + const std::vector& supported_locales_data) { + const size_t number_of_strings_per_locale = 3; + size_t locale_count = + supported_locales_data.size() / number_of_strings_per_locale; + std::vector supported_locales; + std::vector supported_locales_ptr; + for (size_t i = 0; i < locale_count; ++i) { + supported_locales.push_back( + {.struct_size = sizeof(FlutterLocale), + .language_code = + supported_locales_data[i * number_of_strings_per_locale + + 0] + .c_str(), + .country_code = + supported_locales_data[i * number_of_strings_per_locale + + 1] + .c_str(), + .script_code = + supported_locales_data[i * number_of_strings_per_locale + + 2] + .c_str(), + .variant_code = nullptr}); + supported_locales_ptr.push_back(&supported_locales[i]); + } + + const FlutterLocale* result = + ptr(supported_locales_ptr.data(), locale_count); + + std::unique_ptr> out = + std::make_unique>(); + if (result) { + std::string language_code(SAFE_ACCESS(result, language_code, "")); + if (language_code != "") { + out->push_back(language_code); + out->emplace_back(SAFE_ACCESS(result, country_code, "")); + out->emplace_back(SAFE_ACCESS(result, script_code, "")); + } + } + return out; + }; + } + auto external_view_embedder_result = InferExternalViewEmbedderFromArgs(SAFE_ACCESS(args, compositor, nullptr)); if (external_view_embedder_result.second) { @@ -919,10 +970,11 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, flutter::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table = { - update_semantics_nodes_callback, // - update_semantics_custom_actions_callback, // - platform_message_response_callback, // - vsync_callback, // + update_semantics_nodes_callback, // + update_semantics_custom_actions_callback, // + platform_message_response_callback, // + vsync_callback, // + compute_platform_resolved_locale_callback, // }; auto on_create_platform_view = InferPlatformViewCreationCallback( @@ -1058,17 +1110,17 @@ FlutterEngineResult FlutterEngineRunInitialized( auto embedder_engine = reinterpret_cast(engine); - // The engine must not already be running. Initialize may only be called once - // on an engine instance. + // The engine must not already be running. Initialize may only be called + // once on an engine instance. if (embedder_engine->IsValid()) { return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); } // Step 1: Launch the shell. if (!embedder_engine->LaunchShell()) { - return LOG_EMBEDDER_ERROR( - kInvalidArguments, - "Could not launch the engine using supplied initialization arguments."); + return LOG_EMBEDDER_ERROR(kInvalidArguments, + "Could not launch the engine using supplied " + "initialization arguments."); } // Step 2: Tell the platform view to initialize itself. @@ -1193,8 +1245,8 @@ inline int64_t PointerDataButtonsForLegacyEvent( switch (change) { case flutter::PointerData::Change::kDown: case flutter::PointerData::Change::kMove: - // These kinds of change must have a non-zero `buttons`, otherwise gesture - // recognizers will ignore these events. + // These kinds of change must have a non-zero `buttons`, otherwise + // gesture recognizers will ignore these events. return flutter::kPointerButtonMousePrimary; case flutter::PointerData::Change::kCancel: case flutter::PointerData::Change::kAdd: @@ -1236,16 +1288,17 @@ FlutterEngineResult FlutterEngineSendPointerEvent( pointer_data.physical_delta_x = 0.0; pointer_data.physical_delta_y = 0.0; pointer_data.device = SAFE_ACCESS(current, device, 0); - // Pointer identifier will be generated in pointer_data_packet_converter.cc. + // Pointer identifier will be generated in + // pointer_data_packet_converter.cc. pointer_data.pointer_identifier = 0; pointer_data.signal_kind = ToPointerDataSignalKind( SAFE_ACCESS(current, signal_kind, kFlutterPointerSignalKindNone)); pointer_data.scroll_delta_x = SAFE_ACCESS(current, scroll_delta_x, 0.0); pointer_data.scroll_delta_y = SAFE_ACCESS(current, scroll_delta_y, 0.0); FlutterPointerDeviceKind device_kind = SAFE_ACCESS(current, device_kind, 0); - // For backwards compatibility with embedders written before the device kind - // and buttons were exposed, if the device kind is not set treat it as a - // mouse, with a synthesized primary button state based on the phase. + // For backwards compatibility with embedders written before the device + // kind and buttons were exposed, if the device kind is not set treat it + // as a mouse, with a synthesized primary button state based on the phase. if (device_kind == 0) { pointer_data.kind = flutter::PointerData::DeviceKind::kMouse; pointer_data.buttons = @@ -1356,9 +1409,9 @@ FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( auto handle = new FlutterPlatformMessageResponseHandle(); handle->message = fml::MakeRefCounted( - "", // The channel is empty and unused as the response handle is going to - // referenced directly in the |FlutterEngineSendPlatformMessage| with - // the container message discarded. + "", // The channel is empty and unused as the response handle is going + // to referenced directly in the |FlutterEngineSendPlatformMessage| + // with the container message discarded. fml::MakeRefCounted( std::move(platform_task_runner), response_callback)); *response_out = handle; @@ -1791,14 +1844,14 @@ FlutterEngineResult FlutterEnginePostDartObject( peer->trampoline = callback; // This finalizer is set so that in case of failure of the // Dart_PostCObject below, we collect the peer. The embedder is still - // responsible for collecting the buffer in case of non-kSuccess returns - // from this method. This finalizer must be released in case of kSuccess - // returns from this method. + // responsible for collecting the buffer in case of non-kSuccess + // returns from this method. This finalizer must be released in case + // of kSuccess returns from this method. typed_data_finalizer.SetClosure([peer]() { - // This is the tiny object we use as the peer to the Dart call so that - // we can attach the a trampoline to the embedder supplied callback. - // In case of error, we need to collect this object lest we introduce - // a tiny leak. + // This is the tiny object we use as the peer to the Dart call so + // that we can attach the a trampoline to the embedder supplied + // callback. In case of error, we need to collect this object lest + // we introduce a tiny leak. delete peer; }); dart_object.type = Dart_CObject_kExternalTypedData; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 84b027273b755906a05082d05d3305819012f613..6277ccd4e80c8e94421a9cfa1dad12e497c497d2 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -861,6 +861,10 @@ typedef struct { const char* variant_code; } FlutterLocale; +typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)( + const FlutterLocale** /* supported_locales*/, + size_t /* Number of locales*/); + typedef int64_t FlutterEngineDartPort; typedef enum { @@ -1205,6 +1209,17 @@ typedef struct { /// /// Embedders can provide either snapshot buffers or aot_data, but not both. FlutterEngineAOTData aot_data; + + /// A callback that computes the locale the platform would natively resolve + /// to. + /// + /// The input parameter is an array of FlutterLocales which represent the + /// locales supported by the app. One of the input supported locales should + /// be selected and returned to best match with the user/device's preferred + /// locale. The implementation should produce a result that as closely + /// matches what the platform would natively resolve to as possible. + FlutterComputePlatformResolvedLocaleCallback + compute_platform_resolved_locale_callback; } FlutterProjectArgs; //------------------------------------------------------------------------------ diff --git a/shell/platform/embedder/platform_view_embedder.cc b/shell/platform/embedder/platform_view_embedder.cc index b1a2cac536e544dc8e92ba3d2e3ee3faf510b479..65e5d56b00714f54d2ca706e3acdd3b8c27aedf7 100644 --- a/shell/platform/embedder/platform_view_embedder.cc +++ b/shell/platform/embedder/platform_view_embedder.cc @@ -97,6 +97,11 @@ std::unique_ptr PlatformViewEmbedder::CreateVSyncWaiter() { std::unique_ptr> PlatformViewEmbedder::ComputePlatformResolvedLocales( const std::vector& supported_locale_data) { + if (platform_dispatch_table_.compute_platform_resolved_locale_callback != + nullptr) { + return platform_dispatch_table_.compute_platform_resolved_locale_callback( + supported_locale_data); + } std::unique_ptr> out = std::make_unique>(); return out; diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index 9815d94936918557336e4eb35db5250289d6aaab..c7959bc29372c8de88d861278b7c54b48bf2711f 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -25,6 +25,9 @@ class PlatformViewEmbedder final : public PlatformView { std::function; using PlatformMessageResponseCallback = std::function)>; + using ComputePlatformResolvedLocaleCallback = + std::function>( + const std::vector& supported_locale_data)>; struct PlatformDispatchTable { UpdateSemanticsNodesCallback update_semantics_nodes_callback; // optional @@ -33,6 +36,8 @@ class PlatformViewEmbedder final : public PlatformView { PlatformMessageResponseCallback platform_message_response_callback; // optional VsyncWaiterEmbedder::VsyncCallback vsync_callback; // optional + ComputePlatformResolvedLocaleCallback + compute_platform_resolved_locale_callback; }; // Creates a platform view that sets up an OpenGL rasterizer. diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 72cc3d400dc46af3fda5850b13d5d9cb655c1734..1c08b98fde1f9f6f26bc17a10a82a5b7e15c60c1 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -80,6 +80,7 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( SetAssetsPath(); SetIsolateCreateCallbackHook(); SetSemanticsCallbackHooks(); + SetLocalizationCallbackHooks(); AddCommandLineArgument("--disable-observatory"); if (preference == InitializationPreference::kSnapshotsInitialize || @@ -157,6 +158,11 @@ void EmbedderConfigBuilder::SetSemanticsCallbackHooks() { EmbedderTestContext::GetUpdateSemanticsCustomActionCallbackHook(); } +void EmbedderConfigBuilder::SetLocalizationCallbackHooks() { + project_args_.compute_platform_resolved_locale_callback = + EmbedderTestContext::GetComputePlatformResolvedLocaleCallbackHook(); +} + void EmbedderConfigBuilder::SetDartEntrypoint(std::string entrypoint) { if (entrypoint.size() == 0) { return; diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 6512f15242f9cef5d398713f4e61edd7f74cc2d2..a386cd91d0c6d1811dc057c21d4ee4a3efeb253c 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -59,6 +59,8 @@ class EmbedderConfigBuilder { void SetSemanticsCallbackHooks(); + void SetLocalizationCallbackHooks(); + void SetDartEntrypoint(std::string entrypoint); void AddCommandLineArgument(std::string arg); diff --git a/shell/platform/embedder/tests/embedder_test_context.cc b/shell/platform/embedder/tests/embedder_test_context.cc index bb2c71788b332853a1000e7e658018e2ca655051..be7f7641cc58693e024158a280fe603827f1b733 100644 --- a/shell/platform/embedder/tests/embedder_test_context.cc +++ b/shell/platform/embedder/tests/embedder_test_context.cc @@ -160,6 +160,14 @@ EmbedderTestContext::GetUpdateSemanticsCustomActionCallbackHook() { }; } +FlutterComputePlatformResolvedLocaleCallback +EmbedderTestContext::GetComputePlatformResolvedLocaleCallbackHook() { + return [](const FlutterLocale** supported_locales, + size_t length) -> const FlutterLocale* { + return supported_locales[0]; + }; +} + void EmbedderTestContext::SetupOpenGLSurface(SkISize surface_size) { FML_CHECK(!gl_surface_); gl_surface_ = std::make_unique(surface_size); diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index 182edcd31c700bf2f9f443ed1e965a60b6afc583..56a44f6b5efe63a18fff7ea8c71cdab61c2a739b 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -112,6 +112,9 @@ class EmbedderTestContext { static FlutterUpdateSemanticsCustomActionCallback GetUpdateSemanticsCustomActionCallbackHook(); + static FlutterComputePlatformResolvedLocaleCallback + GetComputePlatformResolvedLocaleCallbackHook(); + void SetupAOTMappingsIfNecessary(); void SetupAOTDataIfNecessary(); diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index a3ece90e032cc959feb427727881946c88dfec6e..498f602f5b2bf5d367110526d9f2f87a34673c04 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -5,6 +5,7 @@ #define FML_USED_ON_EMBEDDER #include +#include #include "embedder.h" #include "embedder_engine.h" @@ -2889,6 +2890,33 @@ TEST_F(EmbedderTest, CanUpdateLocales) { check_latch.Wait(); } +TEST_F(EmbedderTest, LocalizationCallbacksCalled) { + auto& context = GetEmbedderContext(); + fml::AutoResetWaitableEvent latch; + context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + // Wait for the root isolate to launch. + latch.Wait(); + + flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); + std::vector supported_locales; + supported_locales.push_back("es"); + supported_locales.push_back("MX"); + supported_locales.push_back(""); + auto result = shell.GetPlatformView()->ComputePlatformResolvedLocales( + supported_locales); + + ASSERT_EQ((*result).size(), supported_locales.size()); // 3 + ASSERT_EQ((*result)[0], supported_locales[0]); + ASSERT_EQ((*result)[1], supported_locales[1]); + ASSERT_EQ((*result)[2], supported_locales[2]); + + engine.reset(); +} + TEST_F(EmbedderTest, CanQueryDartAOTMode) { ASSERT_EQ(FlutterEngineRunsAOTCompiledDartCode(), flutter::DartVM::IsRunningPrecompiledCode());