mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Platform resolved locale and Android localization refactor (flutter/engine#18645)
This commit is contained in:
parent
082179c37b
commit
c9fa545cf3
@ -762,6 +762,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StringCod
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/FlutterTextUtils.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/AccessibilityEventsDelegate.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java
|
||||
|
||||
@ -809,6 +809,34 @@ class Window {
|
||||
Locale? get platformResolvedLocale => _platformResolvedLocale;
|
||||
Locale? _platformResolvedLocale;
|
||||
|
||||
/// Performs the platform-native locale resolution.
|
||||
///
|
||||
/// Each platform may return different results.
|
||||
///
|
||||
/// If the platform fails to resolve a locale, then this will return null.
|
||||
///
|
||||
/// This method returns synchronously and is a direct call to
|
||||
/// platform specific APIs without invoking method channels.
|
||||
Locale? computePlatformResolvedLocale(List<Locale> supportedLocales) {
|
||||
final List<String?> supportedLocalesData = <String?>[];
|
||||
for (Locale locale in supportedLocales) {
|
||||
supportedLocalesData.add(locale.languageCode);
|
||||
supportedLocalesData.add(locale.countryCode);
|
||||
supportedLocalesData.add(locale.scriptCode);
|
||||
}
|
||||
|
||||
final List<String> result = _computePlatformResolvedLocale(supportedLocalesData);
|
||||
|
||||
if (result.isNotEmpty) {
|
||||
return Locale.fromSubtags(
|
||||
languageCode: result[0],
|
||||
countryCode: result[1] == '' ? null : result[1],
|
||||
scriptCode: result[2] == '' ? null : result[2]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
List<String> _computePlatformResolvedLocale(List<String?> supportedLocalesData) native 'Window_computePlatformResolvedLocale';
|
||||
|
||||
/// A callback that is invoked whenever [locale] changes value.
|
||||
///
|
||||
/// The framework invokes this callback in the same zone in which the
|
||||
|
||||
@ -425,6 +425,27 @@ void Window::CompletePlatformMessageResponse(int response_id,
|
||||
response->Complete(std::make_unique<fml::DataMapping>(std::move(data)));
|
||||
}
|
||||
|
||||
Dart_Handle ComputePlatformResolvedLocale(Dart_Handle supportedLocalesHandle) {
|
||||
std::vector<std::string> supportedLocales =
|
||||
tonic::DartConverter<std::vector<std::string>>::FromDart(
|
||||
supportedLocalesHandle);
|
||||
|
||||
std::vector<std::string> results =
|
||||
*UIDartState::Current()
|
||||
->window()
|
||||
->client()
|
||||
->ComputePlatformResolvedLocale(supportedLocales);
|
||||
|
||||
return tonic::DartConverter<std::vector<std::string>>::ToDart(results);
|
||||
}
|
||||
|
||||
static void _ComputePlatformResolvedLocale(Dart_NativeArguments args) {
|
||||
UIDartState::ThrowIfUIOperationsProhibited();
|
||||
Dart_Handle result =
|
||||
ComputePlatformResolvedLocale(Dart_GetNativeArgument(args, 1));
|
||||
Dart_SetReturnValue(args, result);
|
||||
}
|
||||
|
||||
void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
|
||||
natives->Register({
|
||||
{"Window_defaultRouteName", DefaultRouteName, 1, true},
|
||||
@ -437,6 +458,8 @@ void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
|
||||
{"Window_reportUnhandledException", ReportUnhandledException, 2, true},
|
||||
{"Window_setNeedsReportTimings", SetNeedsReportTimings, 2, true},
|
||||
{"Window_getPersistentIsolateData", GetPersistentIsolateData, 1, true},
|
||||
{"Window_computePlatformResolvedLocale", _ComputePlatformResolvedLocale,
|
||||
2, true},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,9 @@ class WindowClient {
|
||||
int64_t isolate_port) = 0;
|
||||
virtual void SetNeedsReportTimings(bool value) = 0;
|
||||
virtual std::shared_ptr<const fml::Mapping> GetPersistentIsolateData() = 0;
|
||||
virtual std::unique_ptr<std::vector<std::string>>
|
||||
ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~WindowClient();
|
||||
|
||||
@ -616,6 +616,19 @@ abstract class Window {
|
||||
/// See [locales], which is the list of locales the user/device prefers.
|
||||
Locale/*?*/ get platformResolvedLocale;
|
||||
|
||||
/// Performs the platform-native locale resolution.
|
||||
///
|
||||
/// Each platform may return different results.
|
||||
///
|
||||
/// If the platform fails to resolve a locale, then this will return null.
|
||||
///
|
||||
/// This method returns synchronously and is a direct call to
|
||||
/// platform specific APIs without invoking method channels.
|
||||
Locale computePlatformResolvedLocale(List<Locale> supportedLocales) {
|
||||
// TODO(garyq): Implement on web.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// A callback that is invoked whenever [locale] changes value.
|
||||
///
|
||||
/// The framework invokes this callback in the same zone in which the
|
||||
|
||||
@ -340,6 +340,13 @@ RuntimeController::GetPersistentIsolateData() {
|
||||
return persistent_isolate_data_;
|
||||
}
|
||||
|
||||
// |WindowClient|
|
||||
std::unique_ptr<std::vector<std::string>>
|
||||
RuntimeController::ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
return client_.ComputePlatformResolvedLocale(supported_locale_data);
|
||||
}
|
||||
|
||||
Dart_Port RuntimeController::GetMainPort() {
|
||||
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
|
||||
return root_isolate ? root_isolate->main_port() : ILLEGAL_PORT;
|
||||
|
||||
@ -522,6 +522,10 @@ class RuntimeController final : public WindowClient {
|
||||
// |WindowClient|
|
||||
std::shared_ptr<const fml::Mapping> GetPersistentIsolateData() override;
|
||||
|
||||
// |WindowClient|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController);
|
||||
};
|
||||
|
||||
|
||||
@ -37,6 +37,10 @@ class RuntimeDelegate {
|
||||
|
||||
virtual void SetNeedsReportTimings(bool value) = 0;
|
||||
|
||||
virtual std::unique_ptr<std::vector<std::string>>
|
||||
ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~RuntimeDelegate();
|
||||
};
|
||||
|
||||
@ -492,6 +492,11 @@ void Engine::UpdateIsolateDescription(const std::string isolate_name,
|
||||
delegate_.UpdateIsolateDescription(isolate_name, isolate_port);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> Engine::ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
return delegate_.ComputePlatformResolvedLocale(supported_locale_data);
|
||||
}
|
||||
|
||||
void Engine::SetNeedsReportTimings(bool needs_reporting) {
|
||||
delegate_.SetNeedsReportTimings(needs_reporting);
|
||||
}
|
||||
|
||||
@ -227,6 +227,25 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
/// collected and send back to Dart.
|
||||
///
|
||||
virtual void SetNeedsReportTimings(bool needs_reporting) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Directly invokes platform-specific APIs to compute the
|
||||
/// locale the platform would have natively resolved to.
|
||||
///
|
||||
/// @param[in] supported_locale_data The vector of strings that represents
|
||||
/// the locales supported by the app.
|
||||
/// Each locale consists of three
|
||||
/// strings: languageCode, countryCode,
|
||||
/// and scriptCode in that order.
|
||||
///
|
||||
/// @return A vector of 3 strings languageCode, countryCode, and
|
||||
/// scriptCode that represents the locale selected by the
|
||||
/// platform. Empty strings mean the value was unassigned. Empty
|
||||
/// vector represents a null locale.
|
||||
///
|
||||
virtual std::unique_ptr<std::vector<std::string>>
|
||||
ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) = 0;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@ -765,6 +784,10 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
void UpdateIsolateDescription(const std::string isolate_name,
|
||||
int64_t isolate_port) override;
|
||||
|
||||
// |RuntimeDelegate|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) override;
|
||||
|
||||
void SetNeedsReportTimings(bool value) override;
|
||||
|
||||
void StopAnimator();
|
||||
|
||||
@ -137,4 +137,12 @@ void PlatformView::SetNextFrameCallback(const fml::closure& closure) {
|
||||
delegate_.OnPlatformViewSetNextFrameCallback(closure);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>>
|
||||
PlatformView::ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
std::unique_ptr<std::vector<std::string>> out =
|
||||
std::make_unique<std::vector<std::string>>();
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -209,6 +209,25 @@ class PlatformView {
|
||||
///
|
||||
virtual void OnPlatformViewMarkTextureFrameAvailable(
|
||||
int64_t texture_id) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Directly invokes platform-specific APIs to compute the
|
||||
/// locale the platform would have natively resolved to.
|
||||
///
|
||||
/// @param[in] supported_locale_data The vector of strings that represents
|
||||
/// the locales supported by the app.
|
||||
/// Each locale consists of three
|
||||
/// strings: languageCode, countryCode,
|
||||
/// and scriptCode in that order.
|
||||
///
|
||||
/// @return A vector of 3 strings languageCode, countryCode, and
|
||||
/// scriptCode that represents the locale selected by the
|
||||
/// platform. Empty strings mean the value was unassigned. Empty
|
||||
/// vector represents a null locale.
|
||||
///
|
||||
virtual std::unique_ptr<std::vector<std::string>>
|
||||
ComputePlatformViewResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) = 0;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@ -543,6 +562,25 @@ class PlatformView {
|
||||
///
|
||||
void MarkTextureFrameAvailable(int64_t texture_id);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Directly invokes platform-specific APIs to compute the
|
||||
/// locale the platform would have natively resolved to.
|
||||
///
|
||||
/// @param[in] supported_locale_data The vector of strings that represents
|
||||
/// the locales supported by the app.
|
||||
/// Each locale consists of three
|
||||
/// strings: languageCode, countryCode,
|
||||
/// and scriptCode in that order.
|
||||
///
|
||||
/// @return A vector of 3 strings languageCode, countryCode, and
|
||||
/// scriptCode that represents the locale selected by the
|
||||
/// platform. Empty strings mean the value was unassigned. Empty
|
||||
/// vector represents a null locale.
|
||||
///
|
||||
virtual std::unique_ptr<std::vector<std::string>>
|
||||
ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data);
|
||||
|
||||
protected:
|
||||
PlatformView::Delegate& delegate_;
|
||||
const TaskRunners task_runners_;
|
||||
|
||||
@ -1097,6 +1097,19 @@ void Shell::SetNeedsReportTimings(bool value) {
|
||||
needs_report_timings_ = value;
|
||||
}
|
||||
|
||||
// |Engine::Delegate|
|
||||
std::unique_ptr<std::vector<std::string>> Shell::ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
return ComputePlatformViewResolvedLocale(supported_locale_data);
|
||||
}
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
std::unique_ptr<std::vector<std::string>>
|
||||
Shell::ComputePlatformViewResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
return platform_view_->ComputePlatformResolvedLocales(supported_locale_data);
|
||||
}
|
||||
|
||||
void Shell::ReportTimings() {
|
||||
FML_DCHECK(is_setup_);
|
||||
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
|
||||
|
||||
@ -483,6 +483,10 @@ class Shell final : public PlatformView::Delegate,
|
||||
// |PlatformView::Delegate|
|
||||
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override;
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformViewResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) override;
|
||||
|
||||
// |Animator::Delegate|
|
||||
void OnAnimatorBeginFrame(fml::TimePoint frame_target_time) override;
|
||||
|
||||
@ -517,6 +521,10 @@ class Shell final : public PlatformView::Delegate,
|
||||
// |Engine::Delegate|
|
||||
void SetNeedsReportTimings(bool value) override;
|
||||
|
||||
// |Engine::Delegate|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) override;
|
||||
|
||||
// |Rasterizer::Delegate|
|
||||
void OnFrameRasterized(const FrameTiming&) override;
|
||||
|
||||
|
||||
@ -210,6 +210,7 @@ android_java_sources = [
|
||||
"io/flutter/plugin/editing/FlutterTextUtils.java",
|
||||
"io/flutter/plugin/editing/InputConnectionAdaptor.java",
|
||||
"io/flutter/plugin/editing/TextInputPlugin.java",
|
||||
"io/flutter/plugin/localization/LocalizationPlugin.java",
|
||||
"io/flutter/plugin/mouse/MouseCursorPlugin.java",
|
||||
"io/flutter/plugin/platform/AccessibilityEventsDelegate.java",
|
||||
"io/flutter/plugin/platform/PlatformPlugin.java",
|
||||
|
||||
@ -11,7 +11,6 @@ import android.content.res.Configuration;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseArray;
|
||||
@ -40,14 +39,11 @@ import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
|
||||
import io.flutter.embedding.engine.renderer.RenderSurface;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.plugin.editing.TextInputPlugin;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.mouse.MouseCursorPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.view.AccessibilityBridge;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -101,6 +97,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
// existing, stateless system channels, e.g., KeyEventChannel, TextInputChannel, etc.
|
||||
@Nullable private MouseCursorPlugin mouseCursorPlugin;
|
||||
@Nullable private TextInputPlugin textInputPlugin;
|
||||
@Nullable private LocalizationPlugin localizationPlugin;
|
||||
@Nullable private AndroidKeyProcessor androidKeyProcessor;
|
||||
@Nullable private AndroidTouchProcessor androidTouchProcessor;
|
||||
@Nullable private AccessibilityBridge accessibilityBridge;
|
||||
@ -354,7 +351,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
// again (e.g. in onStart).
|
||||
if (flutterEngine != null) {
|
||||
Log.v(TAG, "Configuration changed. Sending locales and user settings to Flutter.");
|
||||
sendLocalesToFlutter(newConfig);
|
||||
localizationPlugin.sendLocalesToFlutter(newConfig);
|
||||
sendUserSettingsToFlutter();
|
||||
}
|
||||
}
|
||||
@ -815,6 +812,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
this,
|
||||
this.flutterEngine.getTextInputChannel(),
|
||||
this.flutterEngine.getPlatformViewsController());
|
||||
localizationPlugin = this.flutterEngine.getLocalizationPlugin();
|
||||
androidKeyProcessor =
|
||||
new AndroidKeyProcessor(this.flutterEngine.getKeyEventChannel(), textInputPlugin);
|
||||
androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());
|
||||
@ -841,7 +839,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
|
||||
// Push View and Context related information from Android to Flutter.
|
||||
sendUserSettingsToFlutter();
|
||||
sendLocalesToFlutter(getResources().getConfiguration());
|
||||
localizationPlugin.sendLocalesToFlutter(getResources().getConfiguration());
|
||||
sendViewportMetricsToFlutter();
|
||||
|
||||
flutterEngine.getPlatformViewsController().attachToView(this);
|
||||
@ -944,42 +942,6 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
flutterEngineAttachmentListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the current {@link Locale} configuration to Flutter.
|
||||
*
|
||||
* <p>FlutterEngine must be non-null when this method is invoked.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void sendLocalesToFlutter(@NonNull Configuration config) {
|
||||
List<Locale> locales = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
LocaleList localeList = config.getLocales();
|
||||
int localeCount = localeList.size();
|
||||
for (int index = 0; index < localeCount; ++index) {
|
||||
Locale locale = localeList.get(index);
|
||||
locales.add(locale);
|
||||
}
|
||||
} else {
|
||||
locales.add(config.locale);
|
||||
}
|
||||
|
||||
Locale platformResolvedLocale = null;
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
List<Locale.LanguageRange> languageRanges = new ArrayList<>();
|
||||
LocaleList localeList = config.getLocales();
|
||||
int localeCount = localeList.size();
|
||||
for (int index = 0; index < localeCount; ++index) {
|
||||
Locale locale = localeList.get(index);
|
||||
languageRanges.add(new Locale.LanguageRange(locale.toLanguageTag()));
|
||||
}
|
||||
// TODO(garyq) implement a real locale resolution.
|
||||
platformResolvedLocale =
|
||||
Locale.lookup(languageRanges, Arrays.asList(Locale.getAvailableLocales()));
|
||||
}
|
||||
|
||||
flutterEngine.getLocalizationChannel().sendLocales(locales, platformResolvedLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send various user preferences of this Android device to Flutter.
|
||||
*
|
||||
|
||||
@ -28,6 +28,7 @@ import io.flutter.embedding.engine.systemchannels.RestorationChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
@ -73,6 +74,7 @@ public class FlutterEngine {
|
||||
@NonNull private final FlutterRenderer renderer;
|
||||
@NonNull private final DartExecutor dartExecutor;
|
||||
@NonNull private final FlutterEnginePluginRegistry pluginRegistry;
|
||||
@NonNull private final LocalizationPlugin localizationPlugin;
|
||||
|
||||
// System channels.
|
||||
@NonNull private final AccessibilityChannel accessibilityChannel;
|
||||
@ -260,21 +262,9 @@ public class FlutterEngine {
|
||||
@Nullable String[] dartVmArgs,
|
||||
boolean automaticallyRegisterPlugins,
|
||||
boolean waitForRestorationData) {
|
||||
this.flutterJNI = flutterJNI;
|
||||
flutterLoader.startInitialization(context.getApplicationContext());
|
||||
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
|
||||
|
||||
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
|
||||
flutterJNI.setPlatformViewsController(platformViewsController);
|
||||
attachToJni();
|
||||
|
||||
this.dartExecutor = new DartExecutor(flutterJNI, context.getAssets());
|
||||
this.dartExecutor.onAttachedToJNI();
|
||||
|
||||
// TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if
|
||||
// possible.
|
||||
this.renderer = new FlutterRenderer(flutterJNI);
|
||||
|
||||
accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
|
||||
keyEventChannel = new KeyEventChannel(dartExecutor);
|
||||
lifecycleChannel = new LifecycleChannel(dartExecutor);
|
||||
@ -287,6 +277,21 @@ public class FlutterEngine {
|
||||
systemChannel = new SystemChannel(dartExecutor);
|
||||
textInputChannel = new TextInputChannel(dartExecutor);
|
||||
|
||||
this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);
|
||||
|
||||
this.flutterJNI = flutterJNI;
|
||||
flutterLoader.startInitialization(context.getApplicationContext());
|
||||
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
|
||||
|
||||
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
|
||||
flutterJNI.setPlatformViewsController(platformViewsController);
|
||||
flutterJNI.setLocalizationPlugin(localizationPlugin);
|
||||
attachToJni();
|
||||
|
||||
// TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if
|
||||
// possible.
|
||||
this.renderer = new FlutterRenderer(flutterJNI);
|
||||
|
||||
this.platformViewsController = platformViewsController;
|
||||
this.platformViewsController.onAttachedToJNI();
|
||||
|
||||
@ -486,6 +491,12 @@ public class FlutterEngine {
|
||||
return pluginRegistry;
|
||||
}
|
||||
|
||||
/** The LocalizationPlugin this FlutterEngine created. */
|
||||
@NonNull
|
||||
public LocalizationPlugin getLocalizationPlugin() {
|
||||
return localizationPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code PlatformViewsController}, which controls all platform views running within this {@code
|
||||
* FlutterEngine}.
|
||||
|
||||
@ -8,6 +8,7 @@ import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
@ -22,10 +23,14 @@ import io.flutter.embedding.engine.dart.PlatformMessageHandler;
|
||||
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
|
||||
import io.flutter.embedding.engine.renderer.RenderSurface;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.view.AccessibilityBridge;
|
||||
import io.flutter.view.FlutterCallbackInformation;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
@ -169,6 +174,7 @@ public class FlutterJNI {
|
||||
@Nullable private Long nativePlatformViewId;
|
||||
@Nullable private AccessibilityDelegate accessibilityDelegate;
|
||||
@Nullable private PlatformMessageHandler platformMessageHandler;
|
||||
@Nullable private LocalizationPlugin localizationPlugin;
|
||||
@Nullable private PlatformViewsController platformViewsController;
|
||||
|
||||
@NonNull
|
||||
@ -834,6 +840,65 @@ public class FlutterJNI {
|
||||
}
|
||||
// ----- End Engine Lifecycle Support ----
|
||||
|
||||
// ----- Start Localization Support ----
|
||||
|
||||
/** Sets the localization plugin that is used in various localization methods. */
|
||||
@UiThread
|
||||
public void setLocalizationPlugin(@Nullable LocalizationPlugin localizationPlugin) {
|
||||
ensureRunningOnMainThread();
|
||||
this.localizationPlugin = localizationPlugin;
|
||||
}
|
||||
|
||||
/** Invoked by native to obtain the results of Android's locale resolution algorithm. */
|
||||
@SuppressWarnings("unused")
|
||||
@VisibleForTesting
|
||||
String[] computePlatformResolvedLocale(@NonNull String[] strings) {
|
||||
if (localizationPlugin == null) {
|
||||
return new String[0];
|
||||
}
|
||||
List<Locale> supportedLocales = new ArrayList<Locale>();
|
||||
final int localeDataLength = 3;
|
||||
for (int i = 0; i < strings.length; i += localeDataLength) {
|
||||
String languageCode = strings[i + 0];
|
||||
String countryCode = strings[i + 1];
|
||||
String scriptCode = strings[i + 2];
|
||||
// Convert to Locales via LocaleBuilder if available (API 24+) to include scriptCode.
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
Locale.Builder localeBuilder = new Locale.Builder();
|
||||
if (!languageCode.isEmpty()) {
|
||||
localeBuilder.setLanguage(languageCode);
|
||||
}
|
||||
if (!countryCode.isEmpty()) {
|
||||
localeBuilder.setRegion(countryCode);
|
||||
}
|
||||
if (!scriptCode.isEmpty()) {
|
||||
localeBuilder.setScript(scriptCode);
|
||||
}
|
||||
supportedLocales.add(localeBuilder.build());
|
||||
} else {
|
||||
// Pre-API 24, we fall back on scriptCode-less locales.
|
||||
supportedLocales.add(new Locale(languageCode, countryCode));
|
||||
}
|
||||
}
|
||||
|
||||
Locale result = localizationPlugin.resolveNativeLocale(supportedLocales);
|
||||
|
||||
if (result == null) {
|
||||
return new String[0];
|
||||
}
|
||||
String[] output = new String[localeDataLength];
|
||||
output[0] = result.getLanguage();
|
||||
output[1] = result.getCountry();
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
output[2] = result.getScript();
|
||||
} else {
|
||||
output[2] = "";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// ----- End Localization Support ----
|
||||
|
||||
// @SuppressWarnings("unused")
|
||||
@UiThread
|
||||
public void onDisplayPlatformView(int viewId, int x, int y, int width, int height) {
|
||||
|
||||
@ -26,20 +26,8 @@ public class LocalizationChannel {
|
||||
}
|
||||
|
||||
/** Send the given {@code locales} to Dart. */
|
||||
public void sendLocales(@NonNull List<Locale> locales, Locale platformResolvedLocale) {
|
||||
public void sendLocales(@NonNull List<Locale> locales) {
|
||||
Log.v(TAG, "Sending Locales to Flutter.");
|
||||
// Send platformResolvedLocale first as it may be used in the callback
|
||||
// triggered by the user supported locales being updated/set.
|
||||
if (platformResolvedLocale != null) {
|
||||
List<String> platformResolvedLocaleData = new ArrayList<>();
|
||||
platformResolvedLocaleData.add(platformResolvedLocale.getLanguage());
|
||||
platformResolvedLocaleData.add(platformResolvedLocale.getCountry());
|
||||
platformResolvedLocaleData.add(
|
||||
Build.VERSION.SDK_INT >= 21 ? platformResolvedLocale.getScript() : "");
|
||||
platformResolvedLocaleData.add(platformResolvedLocale.getVariant());
|
||||
channel.invokeMethod("setPlatformResolvedLocale", platformResolvedLocaleData);
|
||||
}
|
||||
|
||||
// Send the user's preferred locales.
|
||||
List<String> data = new ArrayList<>();
|
||||
for (Locale locale : locales) {
|
||||
@ -60,4 +48,20 @@ public class LocalizationChannel {
|
||||
}
|
||||
channel.invokeMethod("setLocale", data);
|
||||
}
|
||||
|
||||
/** Send the given {@code platformResolvedLocale} to Dart. */
|
||||
public void sendPlatformResolvedLocales(Locale platformResolvedLocale) {
|
||||
Log.v(TAG, "Sending Locales to Flutter.");
|
||||
// Send platformResolvedLocale first as it may be used in the callback
|
||||
// triggered by the user supported locales being updated/set.
|
||||
if (platformResolvedLocale != null) {
|
||||
List<String> platformResolvedLocaleData = new ArrayList<>();
|
||||
platformResolvedLocaleData.add(platformResolvedLocale.getLanguage());
|
||||
platformResolvedLocaleData.add(platformResolvedLocale.getCountry());
|
||||
platformResolvedLocaleData.add(
|
||||
Build.VERSION.SDK_INT >= 21 ? platformResolvedLocale.getScript() : "");
|
||||
platformResolvedLocaleData.add(platformResolvedLocale.getVariant());
|
||||
channel.invokeMethod("setPlatformResolvedLocale", platformResolvedLocaleData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
|
||||
package io.flutter.plugin.localization;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Android implementation of the localization plugin. */
|
||||
public class LocalizationPlugin {
|
||||
@NonNull private final LocalizationChannel localizationChannel;
|
||||
@NonNull private final Context context;
|
||||
|
||||
public LocalizationPlugin(
|
||||
@NonNull Context context, @NonNull LocalizationChannel localizationChannel) {
|
||||
|
||||
this.context = context;
|
||||
this.localizationChannel = localizationChannel;
|
||||
}
|
||||
|
||||
public Locale resolveNativeLocale(List<Locale> supportedLocales) {
|
||||
Locale platformResolvedLocale = null;
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
List<Locale.LanguageRange> languageRanges = new ArrayList<>();
|
||||
LocaleList localeList = context.getResources().getConfiguration().getLocales();
|
||||
int localeCount = localeList.size();
|
||||
for (int index = 0; index < localeCount; ++index) {
|
||||
Locale locale = localeList.get(index);
|
||||
String localeString = locale.toString();
|
||||
// This string replacement converts the locale string into the ranges format.
|
||||
languageRanges.add(new Locale.LanguageRange(localeString.replace("_", "-")));
|
||||
}
|
||||
|
||||
// TODO(garyq): This should be modified to achieve Android's full
|
||||
// locale resolution:
|
||||
// https://developer.android.com/guide/topics/resources/multilingual-support
|
||||
platformResolvedLocale = Locale.lookup(languageRanges, supportedLocales);
|
||||
}
|
||||
return platformResolvedLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the current {@link Locale} configuration to Flutter.
|
||||
*
|
||||
* <p>FlutterEngine must be non-null when this method is invoked.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void sendLocalesToFlutter(@NonNull Configuration config) {
|
||||
List<Locale> locales = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
LocaleList localeList = config.getLocales();
|
||||
int localeCount = localeList.size();
|
||||
for (int index = 0; index < localeCount; ++index) {
|
||||
Locale locale = localeList.get(index);
|
||||
locales.add(locale);
|
||||
}
|
||||
} else {
|
||||
locales.add(config.locale);
|
||||
}
|
||||
|
||||
localizationChannel.sendLocales(locales);
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,6 @@ import android.graphics.Rect;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.LocaleList;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
@ -59,14 +58,13 @@ import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.common.ActivityLifecycleListener;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.editing.TextInputPlugin;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.mouse.MouseCursorPlugin;
|
||||
import io.flutter.plugin.platform.PlatformPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
@ -124,6 +122,7 @@ public class FlutterView extends SurfaceView
|
||||
private final SystemChannel systemChannel;
|
||||
private final InputMethodManager mImm;
|
||||
private final TextInputPlugin mTextInputPlugin;
|
||||
private final LocalizationPlugin mLocalizationPlugin;
|
||||
private final MouseCursorPlugin mMouseCursorPlugin;
|
||||
private final AndroidKeyProcessor androidKeyProcessor;
|
||||
private final AndroidTouchProcessor androidTouchProcessor;
|
||||
@ -231,15 +230,17 @@ public class FlutterView extends SurfaceView
|
||||
} else {
|
||||
mMouseCursorPlugin = null;
|
||||
}
|
||||
mLocalizationPlugin = new LocalizationPlugin(context, localizationChannel);
|
||||
androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin);
|
||||
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer);
|
||||
mNativeView
|
||||
.getPluginRegistry()
|
||||
.getPlatformViewsController()
|
||||
.attachTextInputPlugin(mTextInputPlugin);
|
||||
mNativeView.getFlutterJNI().setLocalizationPlugin(mLocalizationPlugin);
|
||||
|
||||
// Send initial platform information to Dart
|
||||
sendLocalesToDart(getResources().getConfiguration());
|
||||
mLocalizationPlugin.sendLocalesToFlutter(getResources().getConfiguration());
|
||||
sendUserPlatformSettingsToDart();
|
||||
}
|
||||
|
||||
@ -404,41 +405,10 @@ public class FlutterView extends SurfaceView
|
||||
.send();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void sendLocalesToDart(Configuration config) {
|
||||
List<Locale> locales = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
LocaleList localeList = config.getLocales();
|
||||
int localeCount = localeList.size();
|
||||
for (int index = 0; index < localeCount; ++index) {
|
||||
Locale locale = localeList.get(index);
|
||||
locales.add(locale);
|
||||
}
|
||||
} else {
|
||||
locales.add(config.locale);
|
||||
}
|
||||
|
||||
Locale platformResolvedLocale = null;
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
List<Locale.LanguageRange> languageRanges = new ArrayList<>();
|
||||
LocaleList localeList = config.getLocales();
|
||||
int localeCount = localeList.size();
|
||||
for (int index = 0; index < localeCount; ++index) {
|
||||
Locale locale = localeList.get(index);
|
||||
languageRanges.add(new Locale.LanguageRange(locale.toLanguageTag()));
|
||||
}
|
||||
// TODO(garyq) implement a real locale resolution.
|
||||
platformResolvedLocale =
|
||||
Locale.lookup(languageRanges, Arrays.asList(Locale.getAvailableLocales()));
|
||||
}
|
||||
|
||||
localizationChannel.sendLocales(locales, platformResolvedLocale);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
sendLocalesToDart(newConfig);
|
||||
mLocalizationPlugin.sendLocalesToFlutter(newConfig);
|
||||
sendUserPlatformSettingsToDart();
|
||||
}
|
||||
|
||||
|
||||
@ -153,6 +153,13 @@ class PlatformViewAndroidJNI {
|
||||
/// @note Must be called from the platform thread.
|
||||
///
|
||||
virtual void FlutterViewCreateOverlaySurface() = 0;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Computes the locale Android would select.
|
||||
///
|
||||
virtual std::unique_ptr<std::vector<std::string>>
|
||||
FlutterViewComputePlatformResolvedLocale(
|
||||
std::vector<std::string> supported_locales_data) = 0;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -395,6 +395,14 @@ void PlatformViewAndroid::ReleaseResourceContext() const {
|
||||
}
|
||||
}
|
||||
|
||||
// |PlatformView|
|
||||
std::unique_ptr<std::vector<std::string>>
|
||||
PlatformViewAndroid::ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
return jni_facade_->FlutterViewComputePlatformResolvedLocale(
|
||||
supported_locale_data);
|
||||
}
|
||||
|
||||
void PlatformViewAndroid::InstallFirstFrameCallback() {
|
||||
// On Platform Task Runner.
|
||||
SetNextFrameCallback(
|
||||
|
||||
@ -107,6 +107,10 @@ class PlatformViewAndroid final : public PlatformView {
|
||||
// |PlatformView|
|
||||
void ReleaseResourceContext() const override;
|
||||
|
||||
// |PlatformView|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data) override;
|
||||
|
||||
void InstallFirstFrameCallback();
|
||||
|
||||
void FireFirstFrameCallback();
|
||||
|
||||
@ -95,6 +95,9 @@ static jmethodID g_get_transform_matrix_method = nullptr;
|
||||
|
||||
static jmethodID g_detach_from_gl_context_method = nullptr;
|
||||
|
||||
static jmethodID g_compute_platform_resolved_locale_method = nullptr;
|
||||
|
||||
// Called By Java
|
||||
static jmethodID g_on_display_platform_view_method = nullptr;
|
||||
|
||||
static jmethodID g_on_display_overlay_surface_method = nullptr;
|
||||
@ -804,6 +807,15 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_compute_platform_resolved_locale_method = env->GetMethodID(
|
||||
g_flutter_jni_class->obj(), "computePlatformResolvedLocale",
|
||||
"([Ljava/lang/String;)[Ljava/lang/String;");
|
||||
|
||||
if (g_compute_platform_resolved_locale_method == nullptr) {
|
||||
FML_LOG(ERROR) << "Could not locate computePlatformResolvedLocale method";
|
||||
return false;
|
||||
}
|
||||
|
||||
return RegisterApi(env);
|
||||
}
|
||||
|
||||
@ -1112,4 +1124,32 @@ void PlatformViewAndroidJNIImpl::FlutterViewCreateOverlaySurface() {
|
||||
FML_CHECK(CheckException(env));
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>>
|
||||
PlatformViewAndroidJNIImpl::FlutterViewComputePlatformResolvedLocale(
|
||||
std::vector<std::string> supported_locales_data) {
|
||||
JNIEnv* env = fml::jni::AttachCurrentThread();
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> out =
|
||||
std::make_unique<std::vector<std::string>>();
|
||||
|
||||
auto java_object = java_object_.get(env);
|
||||
if (java_object.is_null()) {
|
||||
return out;
|
||||
}
|
||||
fml::jni::ScopedJavaLocalRef<jobjectArray> j_locales_data =
|
||||
fml::jni::VectorToStringArray(env, supported_locales_data);
|
||||
jobjectArray result = (jobjectArray)env->CallObjectMethod(
|
||||
java_object.obj(), g_compute_platform_resolved_locale_method,
|
||||
j_locales_data.obj());
|
||||
|
||||
FML_CHECK(CheckException(env));
|
||||
|
||||
int length = env->GetArrayLength(result);
|
||||
for (int i = 0; i < length; i++) {
|
||||
out->emplace_back(fml::jni::JavaStringToString(
|
||||
env, (jstring)env->GetObjectArrayElement(result, i)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -68,6 +68,10 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI {
|
||||
|
||||
void FlutterViewCreateOverlaySurface() override;
|
||||
|
||||
std::unique_ptr<std::vector<std::string>>
|
||||
FlutterViewComputePlatformResolvedLocale(
|
||||
std::vector<std::string> supported_locales_data) override;
|
||||
|
||||
private:
|
||||
// Reference to FlutterJNI object.
|
||||
const fml::jni::JavaObjectWeakGlobalRef java_object_;
|
||||
|
||||
@ -32,6 +32,7 @@ import io.flutter.embedding.engine.systemchannels.NavigationChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -626,6 +627,7 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class));
|
||||
when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
|
||||
when(engine.getActivityControlSurface()).thenReturn(mock(ActivityControlSurface.class));
|
||||
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
@ -102,14 +102,13 @@ public class FlutterViewTest {
|
||||
Configuration configuration = RuntimeEnvironment.application.getResources().getConfiguration();
|
||||
// 1 invocation of channels.
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
// 2 invocations of channels.
|
||||
flutterView.onConfigurationChanged(configuration);
|
||||
flutterView.detachFromFlutterEngine();
|
||||
|
||||
// Should fizzle.
|
||||
flutterView.onConfigurationChanged(configuration);
|
||||
|
||||
verify(flutterEngine, times(2)).getLocalizationChannel();
|
||||
verify(flutterEngine, times(1)).getLocalizationPlugin();
|
||||
verify(flutterEngine, times(2)).getSettingsChannel();
|
||||
}
|
||||
|
||||
|
||||
@ -4,9 +4,19 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.LocaleList;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
|
||||
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -15,6 +25,7 @@ import org.robolectric.annotation.Config;
|
||||
|
||||
@Config(manifest = Config.NONE)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@TargetApi(24) // LocaleList and scriptCode are API 24+.
|
||||
public class FlutterJNITest {
|
||||
@Test
|
||||
public void itAllowsFirstFrameListenersToRemoveThemselvesInline() {
|
||||
@ -50,6 +61,85 @@ public class FlutterJNITest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void computePlatformResolvedLocaleCallsLocalizationPluginProperly() {
|
||||
// --- Test Setup ---
|
||||
FlutterJNI flutterJNI = new FlutterJNI();
|
||||
|
||||
Context context = mock(Context.class);
|
||||
Resources resources = mock(Resources.class);
|
||||
Configuration config = mock(Configuration.class);
|
||||
DartExecutor dartExecutor = mock(DartExecutor.class);
|
||||
LocaleList localeList =
|
||||
new LocaleList(new Locale("es", "MX"), new Locale("zh", "CN"), new Locale("en", "US"));
|
||||
when(context.getResources()).thenReturn(resources);
|
||||
when(resources.getConfiguration()).thenReturn(config);
|
||||
when(config.getLocales()).thenReturn(localeList);
|
||||
|
||||
flutterJNI.setLocalizationPlugin(
|
||||
new LocalizationPlugin(context, new LocalizationChannel(dartExecutor)));
|
||||
String[] supportedLocales =
|
||||
new String[] {
|
||||
"fr", "FR", "",
|
||||
"zh", "", "",
|
||||
"en", "CA", ""
|
||||
};
|
||||
String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
|
||||
assertEquals(result.length, 3);
|
||||
assertEquals(result[0], "zh");
|
||||
assertEquals(result[1], "");
|
||||
assertEquals(result[2], "");
|
||||
|
||||
supportedLocales =
|
||||
new String[] {
|
||||
"fr", "FR", "",
|
||||
"ar", "", "",
|
||||
"en", "CA", ""
|
||||
};
|
||||
result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
|
||||
assertEquals(result.length, 0); // This should change when full algo is implemented.
|
||||
|
||||
supportedLocales =
|
||||
new String[] {
|
||||
"fr", "FR", "",
|
||||
"ar", "", "",
|
||||
"en", "US", ""
|
||||
};
|
||||
result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
|
||||
assertEquals(result.length, 3);
|
||||
assertEquals(result[0], "en");
|
||||
assertEquals(result[1], "US");
|
||||
assertEquals(result[2], "");
|
||||
|
||||
supportedLocales =
|
||||
new String[] {
|
||||
"ar", "", "",
|
||||
"es", "MX", "",
|
||||
"en", "US", ""
|
||||
};
|
||||
result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
|
||||
assertEquals(result.length, 3);
|
||||
assertEquals(result[0], "es");
|
||||
assertEquals(result[1], "MX");
|
||||
assertEquals(result[2], "");
|
||||
|
||||
// Empty supportedLocales.
|
||||
supportedLocales = new String[] {};
|
||||
result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
|
||||
assertEquals(result.length, 0);
|
||||
|
||||
// Empty preferredLocales.
|
||||
supportedLocales =
|
||||
new String[] {
|
||||
"fr", "FR", "",
|
||||
"zh", "", "",
|
||||
"en", "CA", ""
|
||||
};
|
||||
localeList = new LocaleList();
|
||||
when(config.getLocales()).thenReturn(localeList);
|
||||
result = flutterJNI.computePlatformResolvedLocale(supportedLocales);
|
||||
assertEquals(result.length, 0);
|
||||
}
|
||||
|
||||
public void onDisplayPlatformView__callsPlatformViewsController() {
|
||||
PlatformViewsController platformViewsController = mock(PlatformViewsController.class);
|
||||
|
||||
|
||||
@ -85,6 +85,12 @@ class MockDelegate : public PlatformView::Delegate {
|
||||
void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
|
||||
void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
|
||||
void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformViewResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) override {
|
||||
std::unique_ptr<std::vector<std::string>> out = std::make_unique<std::vector<std::string>>();
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
class MockIosDelegate : public AccessibilityBridge::IosDelegate {
|
||||
|
||||
@ -129,6 +129,7 @@ class PlatformViewIOS final : public PlatformView {
|
||||
fml::scoped_nsprotocol<FlutterTextInputPlugin*> text_input_plugin_;
|
||||
fml::closure firstFrameCallback_;
|
||||
ScopedObserver dealloc_view_controller_observer_;
|
||||
std::vector<std::string> platform_resolved_locale_;
|
||||
|
||||
// |PlatformView|
|
||||
void HandlePlatformMessage(fml::RefPtr<flutter::PlatformMessage> message) override;
|
||||
@ -152,6 +153,10 @@ class PlatformViewIOS final : public PlatformView {
|
||||
// |PlatformView|
|
||||
void OnPreEngineRestart() const override;
|
||||
|
||||
// |PlatformView|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data) override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewIOS);
|
||||
};
|
||||
|
||||
|
||||
@ -191,6 +191,39 @@ void PlatformViewIOS::OnPreEngineRestart() const {
|
||||
[owner_controller_.get() platformViewsController]->Reset();
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<std::string>> PlatformViewIOS::ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
size_t localeDataLength = 3;
|
||||
NSMutableArray<NSString*>* supported_locale_identifiers =
|
||||
[NSMutableArray arrayWithCapacity:supported_locale_data.size() / localeDataLength];
|
||||
for (size_t i = 0; i < supported_locale_data.size(); i += localeDataLength) {
|
||||
NSDictionary<NSString*, NSString*>* dict = @{
|
||||
NSLocaleLanguageCode : [NSString stringWithUTF8String:supported_locale_data[i].c_str()],
|
||||
NSLocaleCountryCode : [NSString stringWithUTF8String:supported_locale_data[i + 1].c_str()],
|
||||
NSLocaleScriptCode : [NSString stringWithUTF8String:supported_locale_data[i + 2].c_str()]
|
||||
};
|
||||
[supported_locale_identifiers addObject:[NSLocale localeIdentifierFromComponents:dict]];
|
||||
}
|
||||
NSArray<NSString*>* result =
|
||||
[NSBundle preferredLocalizationsFromArray:supported_locale_identifiers];
|
||||
|
||||
// Output format should be either empty or 3 strings for language, country, and script.
|
||||
std::unique_ptr<std::vector<std::string>> out = std::make_unique<std::vector<std::string>>();
|
||||
|
||||
if (result != nullptr && [result count] > 0) {
|
||||
if (@available(ios 10.0, *)) {
|
||||
NSLocale* locale = [NSLocale localeWithLocaleIdentifier:[result firstObject]];
|
||||
NSString* languageCode = [locale languageCode];
|
||||
out->emplace_back(languageCode == nullptr ? "" : languageCode.UTF8String);
|
||||
NSString* countryCode = [locale countryCode];
|
||||
out->emplace_back(countryCode == nullptr ? "" : countryCode.UTF8String);
|
||||
NSString* scriptCode = [locale scriptCode];
|
||||
out->emplace_back(scriptCode == nullptr ? "" : scriptCode.UTF8String);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
PlatformViewIOS::ScopedObserver::ScopedObserver() : observer_(nil) {}
|
||||
|
||||
PlatformViewIOS::ScopedObserver::~ScopedObserver() {
|
||||
|
||||
@ -93,4 +93,13 @@ std::unique_ptr<VsyncWaiter> PlatformViewEmbedder::CreateVSyncWaiter() {
|
||||
platform_dispatch_table_.vsync_callback, task_runners_);
|
||||
}
|
||||
|
||||
// |PlatformView|
|
||||
std::unique_ptr<std::vector<std::string>>
|
||||
PlatformViewEmbedder::ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
std::unique_ptr<std::vector<std::string>> out =
|
||||
std::make_unique<std::vector<std::string>>();
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -76,6 +76,10 @@ class PlatformViewEmbedder final : public PlatformView {
|
||||
// |PlatformView|
|
||||
std::unique_ptr<VsyncWaiter> CreateVSyncWaiter() override;
|
||||
|
||||
// |PlatformView|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocales(
|
||||
const std::vector<std::string>& supported_locale_data) override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewEmbedder);
|
||||
};
|
||||
|
||||
|
||||
@ -74,6 +74,13 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
|
||||
void OnPlatformViewUnregisterTexture(int64_t texture_id) {}
|
||||
// |flutter::PlatformView::Delegate|
|
||||
void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) {}
|
||||
// |flutter::PlatformView::Delegate|
|
||||
std::unique_ptr<std::vector<std::string>> ComputePlatformViewResolvedLocale(
|
||||
const std::vector<std::string>& supported_locale_data) {
|
||||
std::unique_ptr<std::vector<std::string>> out =
|
||||
std::make_unique<std::vector<std::string>>();
|
||||
return out;
|
||||
}
|
||||
|
||||
bool SemanticsEnabled() const { return semantics_enabled_; }
|
||||
int32_t SemanticsFeatures() const { return semantics_features_; }
|
||||
|
||||
@ -25,4 +25,16 @@ void main() {
|
||||
final FrameTiming timing = FrameTiming(<int>[1000, 8000, 9000, 19500]);
|
||||
expect(timing.toString(), 'FrameTiming(buildDuration: 7.0ms, rasterDuration: 10.5ms, totalSpan: 18.5ms)');
|
||||
});
|
||||
|
||||
test('computePlatformResolvedLocale basic', () {
|
||||
final List<Locale> supportedLocales = <Locale>[
|
||||
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),
|
||||
const Locale.fromSubtags(languageCode: 'fr', countryCode: 'FR'),
|
||||
const Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
const Locale.fromSubtags(languageCode: 'en'),
|
||||
];
|
||||
// The default implementation returns null due to lack of a real platform.
|
||||
final Locale result = window.computePlatformResolvedLocale(supportedLocales);
|
||||
expect(result, null);
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user