From 35340ceaf037ee8652005a7380fb0567fd9232bc Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Wed, 17 Oct 2018 10:53:01 -0700 Subject: [PATCH] Pass full locale list with script and variant codes to framework (#6557) * Locale Passing * Pass full locale list and script and variant codes to framework * Working Android locale list passing and fallback --- lib/ui/hooks.dart | 12 ++++-- lib/ui/window.dart | 29 +++++++++++++-- lib/ui/window/window.cc | 13 ++----- lib/ui/window/window.h | 7 ++-- runtime/runtime_controller.cc | 20 +++------- runtime/runtime_controller.h | 25 +++++++++++-- shell/common/engine.cc | 24 +++++++----- .../android/io/flutter/view/FlutterView.java | 37 +++++++++++++++++-- .../framework/Source/FlutterViewController.mm | 33 ++++++++++------- .../dart/window_hooks_integration_test.dart | 2 +- 10 files changed, 138 insertions(+), 64 deletions(-) diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 20980a0a1c4..d3d81b7f3cc 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -42,14 +42,20 @@ void _updateWindowMetrics(double devicePixelRatio, typedef _LocaleClosure = String Function(); -String _localeClosure() => window._locale.toString(); +String _localeClosure() => window.locale.toString(); @pragma('vm:entry-point') _LocaleClosure _getLocaleClosure() => _localeClosure; @pragma('vm:entry-point') -void _updateLocale(String languageCode, String countryCode, String scriptCode, String variantCode) { - window._locale = new Locale(languageCode, countryCode); +void _updateLocales(List locales) { + const int stringsPerLocale = 4; + final int numLocales = locales.length ~/ stringsPerLocale; + window._locales = new List(numLocales); + for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) { + window._locales[localeIndex] = new Locale(locales[localeIndex * stringsPerLocale], + locales[localeIndex * stringsPerLocale + 1]); + } _invoke(window.onLocaleChanged, window._onLocaleChangedZone); } diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 0b1318c997a..fa0431b3113 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -152,6 +152,9 @@ class Locale { /// the region subtag should be uppercase. const Locale(this._languageCode, [ this._countryCode ]) : assert(_languageCode != null); + /// Empty locale constant. This is an invalid locale. + static const Locale none = const Locale('', ''); + /// The primary language subtag for the locale. /// /// This must not be null. @@ -426,19 +429,39 @@ class Window { _onMetricsChangedZone = Zone.current; } - /// The system-reported locale. + /// The system-reported default locale of the device. /// /// This establishes the language and formatting conventions that application /// should, if possible, use to render their user interface. /// + /// This is the first locale selected by the user and is the user's + /// primary locale (the locale the device UI is displayed in) + /// + /// This is equivalent to `locales.first` and will provide an empty non-null locale + /// if the [locales] list has not been set or is empty. + Locale get locale { + if (_locales != null && _locales.isNotEmpty) { + return _locales.first; + } + return Locale.none; + } + + /// The full system-reported supported locales of the device. + /// + /// This establishes the language and formatting conventions that application + /// should, if possible, use to render their user interface. + /// + /// The list is ordered in order of priority, with lower-indexed locales being + /// preferred over higher-indexed ones. The first element is the primary [locale]. + /// /// The [onLocaleChanged] callback is called whenever this value changes. /// /// See also: /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - Locale get locale => _locale; - Locale _locale; + List get locales => _locales; + List _locales; /// A callback that is invoked whenever [locale] changes value. /// diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc index 95be0076198..ce4626afa74 100644 --- a/lib/ui/window/window.cc +++ b/lib/ui/window/window.cc @@ -162,21 +162,14 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { }); } -void Window::UpdateLocale(const std::string& language_code, - const std::string& country_code, - const std::string& script_code, - const std::string& variant_code) { +void Window::UpdateLocales(const std::vector& locales) { std::shared_ptr dart_state = library_.dart_state().lock(); if (!dart_state) return; tonic::DartState::Scope scope(dart_state); - - DartInvokeField(library_.value(), "_updateLocale", + DartInvokeField(library_.value(), "_updateLocales", { - StdStringToDart(language_code), - StdStringToDart(country_code), - StdStringToDart(script_code), - StdStringToDart(variant_code), + tonic::ToDart>(locales), }); } diff --git a/lib/ui/window/window.h b/lib/ui/window/window.h index eb2a8c6613d..bc1e3ba267f 100644 --- a/lib/ui/window/window.h +++ b/lib/ui/window/window.h @@ -5,7 +5,9 @@ #ifndef FLUTTER_LIB_UI_WINDOW_WINDOW_H_ #define FLUTTER_LIB_UI_WINDOW_WINDOW_H_ +#include #include +#include #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/semantics/semantics_update.h" @@ -59,10 +61,7 @@ class Window final { void DidCreateIsolate(); void UpdateWindowMetrics(const ViewportMetrics& metrics); - void UpdateLocale(const std::string& language_code, - const std::string& country_code, - const std::string& script_code, - const std::string& variant_code); + void UpdateLocales(const std::vector& locales); void UpdateUserSettingsData(const std::string& data); void UpdateSemanticsEnabled(bool enabled); void UpdateAccessibilityFeatures(int32_t flags); diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 7e0bd024c66..92fb9624379 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -124,8 +124,7 @@ std::unique_ptr RuntimeController::Clone() const { bool RuntimeController::FlushRuntimeStateToIsolate() { return SetViewportMetrics(window_data_.viewport_metrics) && - SetLocale(window_data_.language_code, window_data_.country_code, - window_data_.script_code, window_data_.variant_code) && + SetLocales(window_data_.locale_data) && SetSemanticsEnabled(window_data_.semantics_enabled) && SetAccessibilityFeatures(window_data_.accessibility_feature_flags_); } @@ -140,22 +139,15 @@ bool RuntimeController::SetViewportMetrics(const ViewportMetrics& metrics) { return false; } -bool RuntimeController::SetLocale(const std::string& language_code, - const std::string& country_code, - const std::string& script_code, - const std::string& variant_code) { - window_data_.language_code = language_code; - window_data_.country_code = country_code; - window_data_.script_code = script_code; - window_data_.variant_code = variant_code; +bool RuntimeController::SetLocales( + const std::vector& locale_data) { + window_data_.locale_data = locale_data; if (auto window = GetWindowIfAvailable()) { - window->UpdateLocale(window_data_.language_code, window_data_.country_code, - window_data_.script_code, window_data_.variant_code); + window->UpdateLocales(locale_data); return true; } - - return false; + return true; } bool RuntimeController::SetUserSettingsData(const std::string& data) { diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 67c7f3e44cb..532499cb51e 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -6,6 +6,7 @@ #define FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_ #include +#include #include "flutter/common/task_runners.h" #include "flutter/flow/layers/layer_tree.h" @@ -15,6 +16,8 @@ #include "flutter/lib/ui/window/pointer_data_packet.h" #include "flutter/lib/ui/window/window.h" #include "flutter/runtime/dart_vm.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" namespace blink { class Scene; @@ -40,10 +43,7 @@ class RuntimeController final : public WindowClient { bool SetViewportMetrics(const ViewportMetrics& metrics); - bool SetLocale(const std::string& language_code, - const std::string& country_code, - const std::string& script_code, - const std::string& variant_code); + bool SetLocales(const std::vector& locale_data); bool SetUserSettingsData(const std::string& data); @@ -78,12 +78,29 @@ class RuntimeController final : public WindowClient { std::pair GetRootIsolateReturnCode(); private: + struct Locale { + Locale(std::string language_code_, + std::string country_code_, + std::string script_code_, + std::string variant_code_) + : language_code(language_code_), + country_code(country_code_), + script_code(script_code_), + variant_code(variant_code_) {} + + std::string language_code; + std::string country_code; + std::string script_code; + std::string variant_code; + }; + struct WindowData { ViewportMetrics viewport_metrics; std::string language_code; std::string country_code; std::string script_code; std::string variant_code; + std::vector locale_data; std::string user_settings_data = "{}"; bool semantics_enabled = false; bool assistive_technology_enabled = false; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 5e1f53a5591..3ab484c46f3 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include "flutter/common/settings.h" #include "flutter/fml/eintr_wrapper.h" @@ -325,17 +326,22 @@ bool Engine::HandleLocalizationPlatformMessage( if (args == root.MemberEnd() || !args->value.IsArray()) return false; - const auto& language = args->value[0]; - const auto& country = args->value[1]; - const auto& script = args->value[2]; - const auto& variant = args->value[3]; - - if (!language.IsString() || !country.IsString()) + const size_t strings_per_locale = 4; + if (args->value.Size() % strings_per_locale != 0) return false; + std::vector locale_data; + for (size_t locale_index = 0; locale_index < args->value.Size(); + locale_index += strings_per_locale) { + if (!args->value[locale_index].IsString() || + !args->value[locale_index + 1].IsString()) + return false; + locale_data.push_back(args->value[locale_index].GetString()); + locale_data.push_back(args->value[locale_index + 1].GetString()); + locale_data.push_back(args->value[locale_index + 2].GetString()); + locale_data.push_back(args->value[locale_index + 3].GetString()); + } - return runtime_controller_->SetLocale(language.GetString(), - country.GetString(), script.GetString(), - variant.GetString()); + return runtime_controller_->SetLocales(locale_data); } void Engine::HandleSettingsPlatformMessage(blink::PlatformMessage* message) { diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 87788774868..4f1f151d747 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -37,6 +37,7 @@ import io.flutter.plugin.platform.PlatformPlugin; import org.json.JSONException; import org.json.JSONObject; +import java.lang.reflect.Method; import java.net.URI; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -170,7 +171,8 @@ public class FlutterView extends SurfaceView mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); mTextInputPlugin = new TextInputPlugin(this); - setLocale(getResources().getConfiguration().locale); + + setLocales(getResources().getConfiguration()); setUserSettings(); } @@ -316,14 +318,43 @@ public class FlutterView extends SurfaceView mFlutterSettingsChannel.send(message); } - private void setLocale(Locale locale) { + private void setLocales(Configuration config) { + if (Build.VERSION.SDK_INT >= 24) { + try { + // Passes the full list of locales for android API >= 24 with reflection. + Object localeList = config.getClass().getDeclaredMethod("getLocales").invoke(config); + Method localeListGet = localeList.getClass().getDeclaredMethod("get", int.class); + Method localeListSize = localeList.getClass().getDeclaredMethod("size"); + int localeCount = (int)localeListSize.invoke(localeList); + List data = new ArrayList(); + for (int index = 0; index < localeCount; ++index) { + Locale locale = (Locale)localeListGet.invoke(localeList, index); + data.add(locale.getLanguage()); + data.add(locale.getCountry()); + data.add(locale.getScript()); + data.add(locale.getVariant()); + } + mFlutterLocalizationChannel.invokeMethod("setLocale", data); + return; + } catch (Exception exception) { + // Any exception is a failure. Resort to fallback of sending only one locale. + } + } + // Fallback single locale passing for android API < 24. Should work always. + Locale locale = config.locale; + List data = new ArrayList(); + data.add(locale.getLanguage()); + data.add(locale.getCountry()); + data.add(locale.getScript()); + data.add(locale.getVariant()); mFlutterLocalizationChannel.invokeMethod("setLocale", Arrays.asList(locale.getLanguage(), locale.getCountry(), locale.getScript(), locale.getVariant())); + } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - setLocale(newConfig.locale); + setLocales(newConfig); setUserSettings(); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 2e812139804..9327e8fbbb5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -905,19 +905,26 @@ static blink::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) { #pragma mark - Locale updates - (void)onLocaleUpdated:(NSNotification*)notification { - NSLocale* currentLocale = [NSLocale currentLocale]; - NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode]; - NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode]; - NSString* scriptCode = [currentLocale objectForKey:NSLocaleScriptCode]; - NSString* variantCode = [currentLocale objectForKey:NSLocaleVariantCode]; - if (languageCode && countryCode) - // We pass empty strings for undefined scripts and variants to ensure the JSON encoder/decoder - // functions properly. - [_localizationChannel.get() invokeMethod:@"setLocale" - arguments:@[ - languageCode, countryCode, scriptCode ? scriptCode : @"", - variantCode ? variantCode : @"" - ]]; + NSArray* preferredLocales = [NSLocale preferredLanguages]; + NSMutableArray* data = [NSMutableArray new]; + for (NSString* localeID in preferredLocales) { + NSLocale* currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:localeID]; + NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode]; + NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode]; + NSString* scriptCode = [currentLocale objectForKey:NSLocaleScriptCode]; + NSString* variantCode = [currentLocale objectForKey:NSLocaleVariantCode]; + if (!languageCode || !countryCode) { + continue; + } + [data addObject:languageCode]; + [data addObject:countryCode]; + [data addObject:(scriptCode ? scriptCode : @"")]; + [data addObject:(variantCode ? variantCode : @"")]; + } + if (data.count == 0) { + return; + } + [_localizationChannel.get() invokeMethod:@"setLocale" arguments:data]; } #pragma mark - Set user settings diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index b56aaeee4d1..1e362f403ce 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -100,7 +100,7 @@ void main() { }; }); - _updateLocale('en', 'US', '', ''); + _updateLocales(['en', 'US', '', '']); expect(runZone, isNotNull); expect(runZone, same(innerZone)); expect(locale, equals(const Locale('en', 'US')));