| // 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. |
| |
| #define FML_USED_ON_EMBEDDER |
| |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" |
| |
| #include <memory> |
| |
| #include "flutter/fml/message_loop.h" |
| #include "flutter/fml/platform/darwin/platform_version.h" |
| #include "flutter/fml/trace_event.h" |
| #include "flutter/shell/common/engine.h" |
| #include "flutter/shell/common/platform_view.h" |
| #include "flutter/shell/common/shell.h" |
| #include "flutter/shell/common/switches.h" |
| #include "flutter/shell/common/thread_host.h" |
| #include "flutter/shell/platform/darwin/common/command_line.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h" |
| #import "flutter/shell/platform/darwin/ios/ios_surface.h" |
| #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" |
| #include "flutter/shell/platform/darwin/ios/rendering_api_selection.h" |
| #include "flutter/shell/profiling/sampling_profiler.h" |
| |
| NSString* const FlutterDefaultDartEntrypoint = nil; |
| static constexpr int kNumProfilerSamplesPerSec = 5; |
| |
| @interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar> |
| @property(nonatomic, assign) FlutterEngine* flutterEngine; |
| - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine; |
| @end |
| |
| @interface FlutterEngine () <FlutterTextInputDelegate, FlutterBinaryMessenger> |
| // Maintains a dictionary of plugin names that have registered with the engine. Used by |
| // FlutterEngineRegistrar to implement a FlutterPluginRegistrar. |
| @property(nonatomic, readonly) NSMutableDictionary* pluginPublications; |
| @property(nonatomic, readonly) NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* registrars; |
| |
| @property(nonatomic, readwrite, copy) NSString* isolateId; |
| @property(nonatomic, retain) id<NSObject> flutterViewControllerWillDeallocObserver; |
| @end |
| |
| @implementation FlutterEngine { |
| fml::scoped_nsobject<FlutterDartProject> _dartProject; |
| flutter::ThreadHost _threadHost; |
| std::unique_ptr<flutter::Shell> _shell; |
| NSString* _labelPrefix; |
| std::unique_ptr<fml::WeakPtrFactory<FlutterEngine>> _weakFactory; |
| |
| fml::WeakPtr<FlutterViewController> _viewController; |
| fml::scoped_nsobject<FlutterObservatoryPublisher> _publisher; |
| |
| std::unique_ptr<flutter::FlutterPlatformViewsController> _platformViewsController; |
| std::unique_ptr<flutter::ProfilerMetricsIOS> _profiler_metrics; |
| std::unique_ptr<flutter::SamplingProfiler> _profiler; |
| |
| // Channels |
| fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin; |
| fml::scoped_nsobject<FlutterTextInputPlugin> _textInputPlugin; |
| fml::scoped_nsobject<FlutterMethodChannel> _localizationChannel; |
| fml::scoped_nsobject<FlutterMethodChannel> _navigationChannel; |
| fml::scoped_nsobject<FlutterMethodChannel> _platformChannel; |
| fml::scoped_nsobject<FlutterMethodChannel> _platformViewsChannel; |
| fml::scoped_nsobject<FlutterMethodChannel> _textInputChannel; |
| fml::scoped_nsobject<FlutterBasicMessageChannel> _lifecycleChannel; |
| fml::scoped_nsobject<FlutterBasicMessageChannel> _systemChannel; |
| fml::scoped_nsobject<FlutterBasicMessageChannel> _settingsChannel; |
| |
| int64_t _nextTextureId; |
| |
| BOOL _allowHeadlessExecution; |
| FlutterBinaryMessengerRelay* _binaryMessenger; |
| } |
| |
| - (instancetype)initWithName:(NSString*)labelPrefix { |
| return [self initWithName:labelPrefix project:nil allowHeadlessExecution:YES]; |
| } |
| |
| - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { |
| return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES]; |
| } |
| |
| - (instancetype)initWithName:(NSString*)labelPrefix |
| project:(FlutterDartProject*)project |
| allowHeadlessExecution:(BOOL)allowHeadlessExecution { |
| self = [super init]; |
| NSAssert(self, @"Super init cannot be nil"); |
| NSAssert(labelPrefix, @"labelPrefix is required"); |
| |
| _allowHeadlessExecution = allowHeadlessExecution; |
| _labelPrefix = [labelPrefix copy]; |
| |
| _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self); |
| |
| if (project == nil) |
| _dartProject.reset([[FlutterDartProject alloc] init]); |
| else |
| _dartProject.reset([project retain]); |
| |
| _pluginPublications = [NSMutableDictionary new]; |
| _registrars = [[NSMutableDictionary alloc] init]; |
| _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); |
| |
| _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self]; |
| |
| NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| [center addObserver:self |
| selector:@selector(onMemoryWarning:) |
| name:UIApplicationDidReceiveMemoryWarningNotification |
| object:nil]; |
| |
| [center addObserver:self |
| selector:@selector(applicationBecameActive:) |
| name:UIApplicationDidBecomeActiveNotification |
| object:nil]; |
| |
| [center addObserver:self |
| selector:@selector(applicationWillResignActive:) |
| name:UIApplicationWillResignActiveNotification |
| object:nil]; |
| |
| [center addObserver:self |
| selector:@selector(onLocaleUpdated:) |
| name:NSCurrentLocaleDidChangeNotification |
| object:nil]; |
| |
| return self; |
| } |
| |
| - (void)dealloc { |
| /// Notify plugins of dealloc. This should happen first in dealloc since the |
| /// plugins may be talking to things like the binaryMessenger. |
| [_pluginPublications enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL* stop) { |
| if ([object respondsToSelector:@selector(detachFromEngineForRegistrar:)]) { |
| NSObject<FlutterPluginRegistrar>* registrar = self.registrars[key]; |
| [object detachFromEngineForRegistrar:registrar]; |
| } |
| }]; |
| |
| /// nil out weak references. |
| [_registrars |
| enumerateKeysAndObjectsUsingBlock:^(id key, FlutterEngineRegistrar* registrar, BOOL* stop) { |
| registrar.flutterEngine = nil; |
| }]; |
| |
| [_labelPrefix release]; |
| [_pluginPublications release]; |
| [_registrars release]; |
| _binaryMessenger.parent = nil; |
| [_binaryMessenger release]; |
| |
| NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| if (_flutterViewControllerWillDeallocObserver) { |
| [center removeObserver:_flutterViewControllerWillDeallocObserver]; |
| [_flutterViewControllerWillDeallocObserver release]; |
| } |
| [center removeObserver:self]; |
| |
| [super dealloc]; |
| } |
| |
| - (flutter::Shell&)shell { |
| FML_DCHECK(_shell); |
| return *_shell; |
| } |
| |
| - (fml::WeakPtr<FlutterEngine>)getWeakPtr { |
| return _weakFactory->GetWeakPtr(); |
| } |
| |
| - (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics { |
| if (!self.platformView) { |
| return; |
| } |
| self.platformView->SetViewportMetrics(std::move(viewportMetrics)); |
| } |
| |
| - (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet { |
| if (!self.platformView) { |
| return; |
| } |
| self.platformView->DispatchPointerDataPacket(std::move(packet)); |
| } |
| |
| - (fml::WeakPtr<flutter::PlatformView>)platformView { |
| FML_DCHECK(_shell); |
| return _shell->GetPlatformView(); |
| } |
| |
| - (flutter::PlatformViewIOS*)iosPlatformView { |
| FML_DCHECK(_shell); |
| return static_cast<flutter::PlatformViewIOS*>(_shell->GetPlatformView().get()); |
| } |
| |
| - (fml::RefPtr<fml::TaskRunner>)platformTaskRunner { |
| FML_DCHECK(_shell); |
| return _shell->GetTaskRunners().GetPlatformTaskRunner(); |
| } |
| |
| - (fml::RefPtr<fml::TaskRunner>)RasterTaskRunner { |
| FML_DCHECK(_shell); |
| return _shell->GetTaskRunners().GetRasterTaskRunner(); |
| } |
| |
| - (void)ensureSemanticsEnabled { |
| self.iosPlatformView->SetSemanticsEnabled(true); |
| } |
| |
| - (void)setViewController:(FlutterViewController*)viewController { |
| FML_DCHECK(self.iosPlatformView); |
| _viewController = |
| viewController ? [viewController getWeakPtr] : fml::WeakPtr<FlutterViewController>(); |
| self.iosPlatformView->SetOwnerViewController(_viewController); |
| [self maybeSetupPlatformViewChannels]; |
| |
| if (viewController) { |
| __block FlutterEngine* blockSelf = self; |
| self.flutterViewControllerWillDeallocObserver = |
| [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc |
| object:viewController |
| queue:[NSOperationQueue mainQueue] |
| usingBlock:^(NSNotification* note) { |
| [blockSelf notifyViewControllerDeallocated]; |
| }]; |
| } else { |
| self.flutterViewControllerWillDeallocObserver = nil; |
| } |
| } |
| |
| - (void)attachView { |
| self.iosPlatformView->attachView(); |
| } |
| |
| - (void)setFlutterViewControllerWillDeallocObserver:(id<NSObject>)observer { |
| if (observer != _flutterViewControllerWillDeallocObserver) { |
| if (_flutterViewControllerWillDeallocObserver) { |
| [[NSNotificationCenter defaultCenter] |
| removeObserver:_flutterViewControllerWillDeallocObserver]; |
| [_flutterViewControllerWillDeallocObserver release]; |
| } |
| _flutterViewControllerWillDeallocObserver = [observer retain]; |
| } |
| } |
| |
| - (void)notifyViewControllerDeallocated { |
| [[self lifecycleChannel] sendMessage:@"AppLifecycleState.detached"]; |
| if (!_allowHeadlessExecution) { |
| [self destroyContext]; |
| } else { |
| flutter::PlatformViewIOS* platform_view = [self iosPlatformView]; |
| if (platform_view) { |
| platform_view->SetOwnerViewController({}); |
| } |
| } |
| _viewController.reset(); |
| } |
| |
| - (void)destroyContext { |
| [self resetChannels]; |
| self.isolateId = nil; |
| _shell.reset(); |
| _profiler.reset(); |
| _threadHost.Reset(); |
| _platformViewsController.reset(); |
| } |
| |
| - (FlutterViewController*)viewController { |
| if (!_viewController) { |
| return nil; |
| } |
| return _viewController.get(); |
| } |
| |
| - (FlutterPlatformPlugin*)platformPlugin { |
| return _platformPlugin.get(); |
| } |
| - (flutter::FlutterPlatformViewsController*)platformViewsController { |
| return _platformViewsController.get(); |
| } |
| - (FlutterTextInputPlugin*)textInputPlugin { |
| return _textInputPlugin.get(); |
| } |
| - (FlutterMethodChannel*)localizationChannel { |
| return _localizationChannel.get(); |
| } |
| - (FlutterMethodChannel*)navigationChannel { |
| return _navigationChannel.get(); |
| } |
| - (FlutterMethodChannel*)platformChannel { |
| return _platformChannel.get(); |
| } |
| - (FlutterMethodChannel*)textInputChannel { |
| return _textInputChannel.get(); |
| } |
| - (FlutterBasicMessageChannel*)lifecycleChannel { |
| return _lifecycleChannel.get(); |
| } |
| - (FlutterBasicMessageChannel*)systemChannel { |
| return _systemChannel.get(); |
| } |
| - (FlutterBasicMessageChannel*)settingsChannel { |
| return _settingsChannel.get(); |
| } |
| |
| - (NSURL*)observatoryUrl { |
| return [_publisher.get() url]; |
| } |
| |
| - (void)resetChannels { |
| _localizationChannel.reset(); |
| _navigationChannel.reset(); |
| _platformChannel.reset(); |
| _platformViewsChannel.reset(); |
| _textInputChannel.reset(); |
| _lifecycleChannel.reset(); |
| _systemChannel.reset(); |
| _settingsChannel.reset(); |
| } |
| |
| - (void)startProfiler:(NSString*)threadLabel { |
| _profiler_metrics = std::make_unique<flutter::ProfilerMetricsIOS>(); |
| _profiler = std::make_unique<flutter::SamplingProfiler>( |
| threadLabel.UTF8String, _threadHost.profiler_thread->GetTaskRunner(), |
| [self]() { return self->_profiler_metrics->GenerateSample(); }, kNumProfilerSamplesPerSec); |
| _profiler->Start(); |
| } |
| |
| // If you add a channel, be sure to also update `resetChannels`. |
| // Channels get a reference to the engine, and therefore need manual |
| // cleanup for proper collection. |
| - (void)setupChannels { |
| // This will be invoked once the shell is done setting up and the isolate ID |
| // for the UI isolate is available. |
| fml::WeakPtr<FlutterEngine> weakSelf = [self getWeakPtr]; |
| [_binaryMessenger setMessageHandlerOnChannel:@"flutter/isolate" |
| binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) { |
| if (weakSelf) { |
| weakSelf.get().isolateId = |
| [[FlutterStringCodec sharedInstance] decode:message]; |
| } |
| }]; |
| |
| _localizationChannel.reset([[FlutterMethodChannel alloc] |
| initWithName:@"flutter/localization" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterJSONMethodCodec sharedInstance]]); |
| |
| _navigationChannel.reset([[FlutterMethodChannel alloc] |
| initWithName:@"flutter/navigation" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterJSONMethodCodec sharedInstance]]); |
| |
| _platformChannel.reset([[FlutterMethodChannel alloc] |
| initWithName:@"flutter/platform" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterJSONMethodCodec sharedInstance]]); |
| |
| _platformViewsChannel.reset([[FlutterMethodChannel alloc] |
| initWithName:@"flutter/platform_views" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterStandardMethodCodec sharedInstance]]); |
| |
| _textInputChannel.reset([[FlutterMethodChannel alloc] |
| initWithName:@"flutter/textinput" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterJSONMethodCodec sharedInstance]]); |
| |
| _lifecycleChannel.reset([[FlutterBasicMessageChannel alloc] |
| initWithName:@"flutter/lifecycle" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterStringCodec sharedInstance]]); |
| |
| _systemChannel.reset([[FlutterBasicMessageChannel alloc] |
| initWithName:@"flutter/system" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterJSONMessageCodec sharedInstance]]); |
| |
| _settingsChannel.reset([[FlutterBasicMessageChannel alloc] |
| initWithName:@"flutter/settings" |
| binaryMessenger:self.binaryMessenger |
| codec:[FlutterJSONMessageCodec sharedInstance]]); |
| |
| _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]); |
| _textInputPlugin.get().textInputDelegate = self; |
| |
| _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]); |
| } |
| |
| - (void)maybeSetupPlatformViewChannels { |
| if (_shell && self.shell.IsSetup()) { |
| [_platformChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { |
| [_platformPlugin.get() handleMethodCall:call result:result]; |
| }]; |
| |
| [_platformViewsChannel.get() |
| setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { |
| _platformViewsController->OnMethodCall(call, result); |
| }]; |
| |
| [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { |
| [_textInputPlugin.get() handleMethodCall:call result:result]; |
| }]; |
| } |
| } |
| |
| - (flutter::Rasterizer::Screenshot)screenshot:(flutter::Rasterizer::ScreenshotType)type |
| base64Encode:(bool)base64Encode { |
| return self.shell.Screenshot(type, base64Encode); |
| } |
| |
| - (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil { |
| // Launch the Dart application with the inferred run configuration. |
| self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint |
| libraryOrNil:libraryOrNil]); |
| } |
| |
| - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { |
| if (_shell != nullptr) { |
| FML_LOG(WARNING) << "This FlutterEngine was already invoked."; |
| return NO; |
| } |
| |
| static size_t shellCount = 1; |
| |
| auto settings = [_dartProject.get() settings]; |
| auto windowData = [_dartProject.get() defaultWindowData]; |
| |
| if (libraryURI) { |
| FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; |
| settings.advisory_script_entrypoint = entrypoint.UTF8String; |
| settings.advisory_script_uri = libraryURI.UTF8String; |
| } else if (entrypoint) { |
| settings.advisory_script_entrypoint = entrypoint.UTF8String; |
| settings.advisory_script_uri = std::string("main.dart"); |
| } else { |
| settings.advisory_script_entrypoint = std::string("main"); |
| settings.advisory_script_uri = std::string("main.dart"); |
| } |
| |
| const auto threadLabel = [NSString stringWithFormat:@"%@.%zu", _labelPrefix, shellCount++]; |
| |
| // The current thread will be used as the platform thread. Ensure that the message loop is |
| // initialized. |
| fml::MessageLoop::EnsureInitializedForCurrentThread(); |
| |
| uint32_t threadHostType = flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::GPU | |
| flutter::ThreadHost::Type::IO; |
| bool profilerEnabled = false; |
| #if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) || \ |
| (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE) |
| profilerEnabled = true; |
| #endif |
| if (profilerEnabled) { |
| threadHostType = threadHostType | flutter::ThreadHost::Type::Profiler; |
| } |
| _threadHost = {threadLabel.UTF8String, // label |
| threadHostType}; |
| |
| // Lambda captures by pointers to ObjC objects are fine here because the |
| // create call is |
| // synchronous. |
| flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view = |
| [](flutter::Shell& shell) { |
| return std::make_unique<flutter::PlatformViewIOS>( |
| shell, flutter::GetRenderingAPIForProcess(), shell.GetTaskRunners()); |
| }; |
| |
| flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer = |
| [](flutter::Shell& shell) { |
| return std::make_unique<flutter::Rasterizer>(shell, shell.GetTaskRunners()); |
| }; |
| |
| if (flutter::IsIosEmbeddedViewsPreviewEnabled()) { |
| // Embedded views requires the gpu and the platform views to be the same. |
| // The plan is to eventually dynamically merge the threads when there's a |
| // platform view in the layer tree. |
| // For now we use a fixed thread configuration with the same thread used as the |
| // gpu and platform task runner. |
| // TODO(amirh/chinmaygarde): remove this, and dynamically change the thread configuration. |
| // https://github.com/flutter/flutter/issues/23975 |
| |
| flutter::TaskRunners task_runners(threadLabel.UTF8String, // label |
| fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform |
| fml::MessageLoop::GetCurrent().GetTaskRunner(), // raster |
| _threadHost.ui_thread->GetTaskRunner(), // ui |
| _threadHost.io_thread->GetTaskRunner() // io |
| ); |
| // Create the shell. This is a blocking operation. |
| _shell = flutter::Shell::Create(std::move(task_runners), // task runners |
| std::move(windowData), // window data |
| std::move(settings), // settings |
| on_create_platform_view, // platform view creation |
| on_create_rasterizer // rasterzier creation |
| ); |
| } else { |
| flutter::TaskRunners task_runners(threadLabel.UTF8String, // label |
| fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform |
| _threadHost.raster_thread->GetTaskRunner(), // raster |
| _threadHost.ui_thread->GetTaskRunner(), // ui |
| _threadHost.io_thread->GetTaskRunner() // io |
| ); |
| // Create the shell. This is a blocking operation. |
| _shell = flutter::Shell::Create(std::move(task_runners), // task runners |
| std::move(windowData), // window data |
| std::move(settings), // settings |
| on_create_platform_view, // platform view creation |
| on_create_rasterizer // rasterzier creation |
| ); |
| } |
| |
| if (_shell == nullptr) { |
| FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: " |
| << entrypoint.UTF8String; |
| } else { |
| [self setupChannels]; |
| [self onLocaleUpdated:nil]; |
| if (!_platformViewsController) { |
| _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); |
| } |
| _publisher.reset([[FlutterObservatoryPublisher alloc] init]); |
| [self maybeSetupPlatformViewChannels]; |
| _shell->GetIsGpuDisabledSyncSwitch()->SetSwitch(_isGpuDisabled ? true : false); |
| if (profilerEnabled) { |
| [self startProfiler:threadLabel]; |
| } |
| } |
| |
| return _shell != nullptr; |
| } |
| |
| - (BOOL)run { |
| return [self runWithEntrypoint:FlutterDefaultDartEntrypoint libraryURI:nil]; |
| } |
| |
| - (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { |
| if ([self createShell:entrypoint libraryURI:libraryURI]) { |
| [self launchEngine:entrypoint libraryURI:libraryURI]; |
| } |
| |
| return _shell != nullptr; |
| } |
| |
| - (BOOL)runWithEntrypoint:(NSString*)entrypoint { |
| return [self runWithEntrypoint:entrypoint libraryURI:nil]; |
| } |
| |
| #pragma mark - Text input delegate |
| |
| - (void)updateEditingClient:(int)client withState:(NSDictionary*)state { |
| [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState" |
| arguments:@[ @(client), state ]]; |
| } |
| |
| - (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag { |
| [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingStateWithTag" |
| arguments:@[ @(client), @{tag : state} ]]; |
| } |
| |
| - (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state |
| withClient:(int)client |
| withPosition:(NSDictionary*)position { |
| NSString* stateString; |
| switch (state) { |
| case FlutterFloatingCursorDragStateStart: |
| stateString = @"FloatingCursorDragState.start"; |
| break; |
| case FlutterFloatingCursorDragStateUpdate: |
| stateString = @"FloatingCursorDragState.update"; |
| break; |
| case FlutterFloatingCursorDragStateEnd: |
| stateString = @"FloatingCursorDragState.end"; |
| break; |
| } |
| [_textInputChannel.get() invokeMethod:@"TextInputClient.updateFloatingCursor" |
| arguments:@[ @(client), stateString, position ]]; |
| } |
| |
| - (void)performAction:(FlutterTextInputAction)action withClient:(int)client { |
| NSString* actionString; |
| switch (action) { |
| case FlutterTextInputActionUnspecified: |
| // Where did the term "unspecified" come from? iOS has a "default" and Android |
| // has "unspecified." These 2 terms seem to mean the same thing but we need |
| // to pick just one. "unspecified" was chosen because "default" is often a |
| // reserved word in languages with switch statements (dart, java, etc). |
| actionString = @"TextInputAction.unspecified"; |
| break; |
| case FlutterTextInputActionDone: |
| actionString = @"TextInputAction.done"; |
| break; |
| case FlutterTextInputActionGo: |
| actionString = @"TextInputAction.go"; |
| break; |
| case FlutterTextInputActionSend: |
| actionString = @"TextInputAction.send"; |
| break; |
| case FlutterTextInputActionSearch: |
| actionString = @"TextInputAction.search"; |
| break; |
| case FlutterTextInputActionNext: |
| actionString = @"TextInputAction.next"; |
| break; |
| case FlutterTextInputActionContinue: |
| actionString = @"TextInputAction.continue"; |
| break; |
| case FlutterTextInputActionJoin: |
| actionString = @"TextInputAction.join"; |
| break; |
| case FlutterTextInputActionRoute: |
| actionString = @"TextInputAction.route"; |
| break; |
| case FlutterTextInputActionEmergencyCall: |
| actionString = @"TextInputAction.emergencyCall"; |
| break; |
| case FlutterTextInputActionNewline: |
| actionString = @"TextInputAction.newline"; |
| break; |
| } |
| [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction" |
| arguments:@[ @(client), actionString ]]; |
| } |
| |
| - (void)showAutocorrectionPromptRectForStart:(NSUInteger)start |
| end:(NSUInteger)end |
| withClient:(int)client { |
| [_textInputChannel.get() invokeMethod:@"TextInputClient.showAutocorrectionPromptRect" |
| arguments:@[ @(client), @(start), @(end) ]]; |
| } |
| |
| #pragma mark - Screenshot Delegate |
| |
| - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type |
| asBase64Encoded:(BOOL)base64Encode { |
| FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell"; |
| return _shell->Screenshot(type, base64Encode); |
| } |
| |
| - (NSObject<FlutterBinaryMessenger>*)binaryMessenger { |
| return _binaryMessenger; |
| } |
| |
| #pragma mark - FlutterBinaryMessenger |
| |
| - (void)sendOnChannel:(NSString*)channel message:(NSData*)message { |
| [self sendOnChannel:channel message:message binaryReply:nil]; |
| } |
| |
| - (void)sendOnChannel:(NSString*)channel |
| message:(NSData*)message |
| binaryReply:(FlutterBinaryReply)callback { |
| NSParameterAssert(channel); |
| NSAssert(_shell && _shell->IsSetup(), |
| @"Sending a message before the FlutterEngine has been run."); |
| fml::RefPtr<flutter::PlatformMessageResponseDarwin> response = |
| (callback == nil) ? nullptr |
| : fml::MakeRefCounted<flutter::PlatformMessageResponseDarwin>( |
| ^(NSData* reply) { |
| callback(reply); |
| }, |
| _shell->GetTaskRunners().GetPlatformTaskRunner()); |
| fml::RefPtr<flutter::PlatformMessage> platformMessage = |
| (message == nil) ? fml::MakeRefCounted<flutter::PlatformMessage>(channel.UTF8String, response) |
| : fml::MakeRefCounted<flutter::PlatformMessage>( |
| channel.UTF8String, flutter::GetVectorFromNSData(message), response); |
| |
| _shell->GetPlatformView()->DispatchPlatformMessage(platformMessage); |
| } |
| |
| - (void)setMessageHandlerOnChannel:(NSString*)channel |
| binaryMessageHandler:(FlutterBinaryMessageHandler)handler { |
| NSParameterAssert(channel); |
| if (_shell && _shell->IsSetup()) { |
| self.iosPlatformView->GetPlatformMessageRouter().SetMessageHandler(channel.UTF8String, handler); |
| } else { |
| NSAssert(!handler, @"Setting a message handler before the FlutterEngine has been run."); |
| // Setting a handler to nil for a not setup channel is a noop. |
| } |
| } |
| |
| #pragma mark - FlutterTextureRegistry |
| |
| - (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture { |
| int64_t textureId = _nextTextureId++; |
| self.iosPlatformView->RegisterExternalTexture(textureId, texture); |
| return textureId; |
| } |
| |
| - (void)unregisterTexture:(int64_t)textureId { |
| _shell->GetPlatformView()->UnregisterTexture(textureId); |
| } |
| |
| - (void)textureFrameAvailable:(int64_t)textureId { |
| _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId); |
| } |
| |
| - (NSString*)lookupKeyForAsset:(NSString*)asset { |
| return [FlutterDartProject lookupKeyForAsset:asset]; |
| } |
| |
| - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { |
| return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package]; |
| } |
| |
| - (id<FlutterPluginRegistry>)pluginRegistry { |
| return self; |
| } |
| |
| #pragma mark - FlutterPluginRegistry |
| |
| - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey { |
| NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey); |
| self.pluginPublications[pluginKey] = [NSNull null]; |
| FlutterEngineRegistrar* result = [[FlutterEngineRegistrar alloc] initWithPlugin:pluginKey |
| flutterEngine:self]; |
| self.registrars[pluginKey] = result; |
| return [result autorelease]; |
| } |
| |
| - (BOOL)hasPlugin:(NSString*)pluginKey { |
| return _pluginPublications[pluginKey] != nil; |
| } |
| |
| - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { |
| return _pluginPublications[pluginKey]; |
| } |
| |
| #pragma mark - Notifications |
| |
| - (void)applicationBecameActive:(NSNotification*)notification { |
| [self setIsGpuDisabled:NO]; |
| } |
| |
| - (void)applicationWillResignActive:(NSNotification*)notification { |
| [self setIsGpuDisabled:YES]; |
| } |
| |
| - (void)onMemoryWarning:(NSNotification*)notification { |
| if (_shell) { |
| _shell->NotifyLowMemoryWarning(); |
| } |
| [_systemChannel sendMessage:@{@"type" : @"memoryPressure"}]; |
| } |
| |
| - (void)setIsGpuDisabled:(BOOL)value { |
| if (_shell) { |
| _shell->GetIsGpuDisabledSyncSwitch()->SetSwitch(value ? true : false); |
| } |
| _isGpuDisabled = value; |
| } |
| |
| #pragma mark - Locale updates |
| |
| - (void)onLocaleUpdated:(NSNotification*)notification { |
| NSArray<NSString*>* preferredLocales = [NSLocale preferredLanguages]; |
| NSMutableArray<NSString*>* data = [[NSMutableArray new] autorelease]; |
| |
| // Force prepend the [NSLocale currentLocale] to the front of the list |
| // to ensure we are including the full default locale. preferredLocales |
| // is not guaranteed to include anything beyond the languageCode. |
| 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) { |
| [data addObject:languageCode]; |
| [data addObject:(countryCode ? countryCode : @"")]; |
| [data addObject:(scriptCode ? scriptCode : @"")]; |
| [data addObject:(variantCode ? variantCode : @"")]; |
| } |
| |
| // Add any secondary locales/languages to the list. |
| for (NSString* localeID in preferredLocales) { |
| NSLocale* currentLocale = [[[NSLocale alloc] initWithLocaleIdentifier:localeID] autorelease]; |
| NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode]; |
| NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode]; |
| NSString* scriptCode = [currentLocale objectForKey:NSLocaleScriptCode]; |
| NSString* variantCode = [currentLocale objectForKey:NSLocaleVariantCode]; |
| if (!languageCode) { |
| continue; |
| } |
| [data addObject:languageCode]; |
| [data addObject:(countryCode ? countryCode : @"")]; |
| [data addObject:(scriptCode ? scriptCode : @"")]; |
| [data addObject:(variantCode ? variantCode : @"")]; |
| } |
| if (data.count == 0) { |
| return; |
| } |
| [self.localizationChannel invokeMethod:@"setLocale" arguments:data]; |
| } |
| |
| @end |
| |
| @implementation FlutterEngineRegistrar { |
| NSString* _pluginKey; |
| } |
| |
| - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine { |
| self = [super init]; |
| NSAssert(self, @"Super init cannot be nil"); |
| _pluginKey = [pluginKey copy]; |
| _flutterEngine = flutterEngine; |
| return self; |
| } |
| |
| - (void)dealloc { |
| [_pluginKey release]; |
| [super dealloc]; |
| } |
| |
| - (NSObject<FlutterBinaryMessenger>*)messenger { |
| return _flutterEngine.binaryMessenger; |
| } |
| |
| - (NSObject<FlutterTextureRegistry>*)textures { |
| return _flutterEngine; |
| } |
| |
| - (void)publish:(NSObject*)value { |
| _flutterEngine.pluginPublications[_pluginKey] = value; |
| } |
| |
| - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate |
| channel:(FlutterMethodChannel*)channel { |
| [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { |
| [delegate handleMethodCall:call result:result]; |
| }]; |
| } |
| |
| - (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate { |
| id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate]; |
| if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) { |
| id<FlutterAppLifeCycleProvider> lifeCycleProvider = |
| (id<FlutterAppLifeCycleProvider>)appDelegate; |
| [lifeCycleProvider addApplicationLifeCycleDelegate:delegate]; |
| } |
| } |
| |
| - (NSString*)lookupKeyForAsset:(NSString*)asset { |
| return [_flutterEngine lookupKeyForAsset:asset]; |
| } |
| |
| - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { |
| return [_flutterEngine lookupKeyForAsset:asset fromPackage:package]; |
| } |
| |
| - (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory |
| withId:(NSString*)factoryId { |
| [self registerViewFactory:factory |
| withId:factoryId |
| gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; |
| } |
| |
| - (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory |
| withId:(NSString*)factoryId |
| gestureRecognizersBlockingPolicy: |
| (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy { |
| [_flutterEngine platformViewsController]->RegisterViewFactory(factory, factoryId, |
| gestureRecognizersBlockingPolicy); |
| } |
| |
| @end |