• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#define FML_USED_ON_EMBEDDER
6
7#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
8
9#include <memory>
10
11#include "flutter/fml/message_loop.h"
12#include "flutter/fml/platform/darwin/platform_version.h"
13#include "flutter/fml/trace_event.h"
14#include "flutter/shell/common/engine.h"
15#include "flutter/shell/common/platform_view.h"
16#include "flutter/shell/common/shell.h"
17#include "flutter/shell/common/switches.h"
18#include "flutter/shell/common/thread_host.h"
19#include "flutter/shell/platform/darwin/common/command_line.h"
20#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h"
21#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
22#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
23#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
24#import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h"
25#import "flutter/shell/platform/darwin/ios/ios_surface.h"
26#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
27
28@interface FlutterEngine () <FlutterTextInputDelegate, FlutterBinaryMessenger>
29// Maintains a dictionary of plugin names that have registered with the engine.  Used by
30// FlutterEngineRegistrar to implement a FlutterPluginRegistrar.
31@property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
32
33@property(nonatomic, readwrite, copy) NSString* isolateId;
34@end
35
36@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
37- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine;
38@end
39
40@implementation FlutterEngine {
41  flutter::ThreadHost _threadHost;
42  std::unique_ptr<flutter::Shell> _shell;
43  NSString* _labelPrefix;
44  std::unique_ptr<fml::WeakPtrFactory<FlutterEngine>> _weakFactory;
45
46  fml::WeakPtr<FlutterViewController> _viewController;
47
48  std::unique_ptr<flutter::FlutterPlatformViewsController> _platformViewsController;
49
50  // Channels
51  fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
52  fml::scoped_nsobject<FlutterTextInputPlugin> _textInputPlugin;
53  fml::scoped_nsobject<FlutterMethodChannel> _localizationChannel;
54  fml::scoped_nsobject<FlutterMethodChannel> _navigationChannel;
55  fml::scoped_nsobject<FlutterMethodChannel> _platformChannel;
56  fml::scoped_nsobject<FlutterMethodChannel> _platformViewsChannel;
57  fml::scoped_nsobject<FlutterMethodChannel> _textInputChannel;
58  fml::scoped_nsobject<FlutterBasicMessageChannel> _lifecycleChannel;
59  fml::scoped_nsobject<FlutterBasicMessageChannel> _systemChannel;
60  fml::scoped_nsobject<FlutterBasicMessageChannel> _settingsChannel;
61
62  int64_t _nextTextureId;
63
64  BOOL _allowHeadlessExecution;
65  FlutterBinaryMessengerRelay* _binaryMessenger;
66}
67
68- (instancetype)initWithName:(NSString*)labelPrefix {
69  return [self initWithName:labelPrefix allowHeadlessExecution:YES];
70}
71
72- (instancetype)initWithName:(NSString*)labelPrefix
73      allowHeadlessExecution:(BOOL)allowHeadlessExecution {
74  self = [super init];
75  NSAssert(self, @"Super init cannot be nil");
76  NSAssert(labelPrefix, @"labelPrefix is required");
77
78  _allowHeadlessExecution = allowHeadlessExecution;
79  _labelPrefix = [labelPrefix copy];
80
81  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self);
82
83  _pluginPublications = [NSMutableDictionary new];
84  _platformViewsController.reset(new flutter::FlutterPlatformViewsController());
85
86  [self setupChannels];
87  _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
88
89  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
90  [center addObserver:self
91             selector:@selector(onMemoryWarning:)
92                 name:UIApplicationDidReceiveMemoryWarningNotification
93               object:nil];
94
95  return self;
96}
97
98- (void)dealloc {
99  [_pluginPublications release];
100  _binaryMessenger.parent = nil;
101  [_binaryMessenger release];
102
103  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
104  [center removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
105
106  [super dealloc];
107}
108
109- (flutter::Shell&)shell {
110  FML_DCHECK(_shell);
111  return *_shell;
112}
113
114- (fml::WeakPtr<FlutterEngine>)getWeakPtr {
115  return _weakFactory->GetWeakPtr();
116}
117
118- (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics {
119  if (!self.platformView) {
120    return;
121  }
122  self.platformView->SetViewportMetrics(std::move(viewportMetrics));
123}
124
125- (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet {
126  if (!self.platformView) {
127    return;
128  }
129  self.platformView->DispatchPointerDataPacket(std::move(packet));
130}
131
132- (fml::WeakPtr<flutter::PlatformView>)platformView {
133  FML_DCHECK(_shell);
134  return _shell->GetPlatformView();
135}
136
137- (flutter::PlatformViewIOS*)iosPlatformView {
138  FML_DCHECK(_shell);
139  return static_cast<flutter::PlatformViewIOS*>(_shell->GetPlatformView().get());
140}
141
142- (fml::RefPtr<fml::TaskRunner>)platformTaskRunner {
143  FML_DCHECK(_shell);
144  return _shell->GetTaskRunners().GetPlatformTaskRunner();
145}
146
147- (fml::RefPtr<fml::TaskRunner>)GPUTaskRunner {
148  FML_DCHECK(_shell);
149  return _shell->GetTaskRunners().GetGPUTaskRunner();
150}
151
152- (void)setViewController:(FlutterViewController*)viewController {
153  FML_DCHECK(self.iosPlatformView);
154  _viewController = [viewController getWeakPtr];
155  self.iosPlatformView->SetOwnerViewController(_viewController);
156  [self maybeSetupPlatformViewChannels];
157}
158
159- (void)notifyViewControllerDeallocated {
160  if (!_allowHeadlessExecution) {
161    [self destroyContext];
162  }
163}
164
165- (void)destroyContext {
166  [self resetChannels];
167  self.isolateId = nil;
168  _shell.reset();
169  _threadHost.Reset();
170  _platformViewsController.reset();
171}
172
173- (FlutterViewController*)viewController {
174  if (!_viewController) {
175    return nil;
176  }
177  return _viewController.get();
178}
179
180- (FlutterPlatformPlugin*)platformPlugin {
181  return _platformPlugin.get();
182}
183- (flutter::FlutterPlatformViewsController*)platformViewsController {
184  return _platformViewsController.get();
185}
186- (FlutterTextInputPlugin*)textInputPlugin {
187  return _textInputPlugin.get();
188}
189- (FlutterMethodChannel*)localizationChannel {
190  return _localizationChannel.get();
191}
192- (FlutterMethodChannel*)navigationChannel {
193  return _navigationChannel.get();
194}
195- (FlutterMethodChannel*)platformChannel {
196  return _platformChannel.get();
197}
198- (FlutterMethodChannel*)textInputChannel {
199  return _textInputChannel.get();
200}
201- (FlutterBasicMessageChannel*)lifecycleChannel {
202  return _lifecycleChannel.get();
203}
204- (FlutterBasicMessageChannel*)systemChannel {
205  return _systemChannel.get();
206}
207- (FlutterBasicMessageChannel*)settingsChannel {
208  return _settingsChannel.get();
209}
210
211- (NSURL*)observatoryUrl {
212  return nil;
213}
214
215- (void)resetChannels {
216  _localizationChannel.reset();
217  _navigationChannel.reset();
218  _platformChannel.reset();
219  _platformViewsChannel.reset();
220  _textInputChannel.reset();
221  _lifecycleChannel.reset();
222  _systemChannel.reset();
223  _settingsChannel.reset();
224}
225
226// If you add a channel, be sure to also update `resetChannels`.
227// Channels get a reference to the engine, and therefore need manual
228// cleanup for proper collection.
229 - (void)setupChannels {
230
231 }
232
233- (void)maybeSetupPlatformViewChannels {
234  if (_shell && self.shell.IsSetup()) {
235    [_platformChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
236      [_platformPlugin.get() handleMethodCall:call result:result];
237    }];
238
239    [_platformViewsChannel.get()
240        setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
241          _platformViewsController->OnMethodCall(call, result);
242        }];
243
244    [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
245      [_textInputPlugin.get() handleMethodCall:call result:result];
246    }];
247  }
248}
249
250- (flutter::Rasterizer::Screenshot)screenshot:(flutter::Rasterizer::ScreenshotType)type
251                                 base64Encode:(bool)base64Encode {
252  return self.shell.Screenshot(type, base64Encode);
253}
254
255- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
256
257}
258
259- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
260  if (_shell != nullptr) {
261    FML_LOG(WARNING) << "This FlutterEngine was already invoked.";
262    return NO;
263  }
264
265  static size_t shellCount = 1;
266
267  flutter::Settings settings = {};;// = [_dartProject.get() settings];
268
269  if (libraryURI) {
270    FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library";
271    settings.assets_path = entrypoint.UTF8String;
272  } else if (entrypoint) {
273    settings.assets_path = entrypoint.UTF8String;
274  } else {
275    settings.assets_path = std::string("main");
276  }
277
278  const auto threadLabel = [NSString stringWithFormat:@"%@.%zu", _labelPrefix, shellCount++];
279  FML_DLOG(INFO) << "Creating threadHost for " << threadLabel.UTF8String;
280  // The current thread will be used as the platform thread. Ensure that the message loop is
281  // initialized.
282  fml::MessageLoop::EnsureInitializedForCurrentThread();
283
284  _threadHost = {threadLabel.UTF8String,  // label
285                 flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::GPU |
286                     flutter::ThreadHost::Type::IO};
287
288  // Lambda captures by pointers to ObjC objects are fine here because the
289  // create call is
290  // synchronous.
291  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
292      [](flutter::Shell& shell) {
293        return std::make_unique<flutter::PlatformViewIOS>(shell, shell.GetTaskRunners());
294      };
295
296  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
297      [](flutter::Shell& shell) {
298        return std::make_unique<flutter::Rasterizer>(shell, shell.GetTaskRunners());
299      };
300
301  if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
302    // Embedded views requires the gpu and the platform views to be the same.
303    // The plan is to eventually dynamically merge the threads when there's a
304    // platform view in the layer tree.
305    // For now we use a fixed thread configuration with the same thread used as the
306    // gpu and platform task runner.
307    // TODO(amirh/chinmaygarde): remove this, and dynamically change the thread configuration.
308    // https://github.com/flutter/flutter/issues/23975
309
310    flutter::TaskRunners task_runners(threadLabel.UTF8String,                          // label
311                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
312                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // gpu
313                                      _threadHost.ui_thread->GetTaskRunner(),          // ui
314                                      _threadHost.io_thread->GetTaskRunner()           // io
315    );
316    // Create the shell. This is a blocking operation.
317    _shell = flutter::Shell::Create(std::move(task_runners),  // task runners
318                                    std::move(settings),      // settings
319                                    on_create_platform_view,  // platform view creation
320                                    on_create_rasterizer      // rasterzier creation
321    );
322  } else {
323    flutter::TaskRunners task_runners(threadLabel.UTF8String,                          // label
324                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
325                                      _threadHost.gpu_thread->GetTaskRunner(),         // gpu
326                                      _threadHost.ui_thread->GetTaskRunner(),          // ui
327                                      _threadHost.io_thread->GetTaskRunner()           // io
328    );
329    // Create the shell. This is a blocking operation.
330    _shell = flutter::Shell::Create(std::move(task_runners),  // task runners
331                                    std::move(settings),      // settings
332                                    on_create_platform_view,  // platform view creation
333                                    on_create_rasterizer      // rasterzier creation
334    );
335  }
336
337  if (_shell == nullptr) {
338    FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: "
339                   << entrypoint.UTF8String;
340  } else {
341    [self setupChannels];
342    if (!_platformViewsController) {
343      _platformViewsController.reset(new flutter::FlutterPlatformViewsController());
344    }
345
346    [self maybeSetupPlatformViewChannels];
347  }
348
349  return _shell != nullptr;
350}
351
352- (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
353  if ([self createShell:entrypoint libraryURI:libraryURI]) {
354    [self launchEngine:entrypoint libraryURI:libraryURI];
355  }
356
357  return _shell != nullptr;
358}
359
360- (BOOL)runWithEntrypoint:(NSString*)entrypoint {
361  return [self runWithEntrypoint:entrypoint libraryURI:nil];
362}
363
364#pragma mark - Text input delegate
365
366- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
367  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
368                              arguments:@[ @(client), state ]];
369}
370
371- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state
372                  withClient:(int)client
373                withPosition:(NSDictionary*)position {
374  NSString* stateString;
375  switch (state) {
376    case FlutterFloatingCursorDragStateStart:
377      stateString = @"FloatingCursorDragState.start";
378      break;
379    case FlutterFloatingCursorDragStateUpdate:
380      stateString = @"FloatingCursorDragState.update";
381      break;
382    case FlutterFloatingCursorDragStateEnd:
383      stateString = @"FloatingCursorDragState.end";
384      break;
385  }
386  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateFloatingCursor"
387                              arguments:@[ @(client), stateString, position ]];
388}
389
390- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
391  NSString* actionString;
392  switch (action) {
393    case FlutterTextInputActionUnspecified:
394      // Where did the term "unspecified" come from? iOS has a "default" and Android
395      // has "unspecified." These 2 terms seem to mean the same thing but we need
396      // to pick just one. "unspecified" was chosen because "default" is often a
397      // reserved word in languages with switch statements (dart, java, etc).
398      actionString = @"TextInputAction.unspecified";
399      break;
400    case FlutterTextInputActionDone:
401      actionString = @"TextInputAction.done";
402      break;
403    case FlutterTextInputActionGo:
404      actionString = @"TextInputAction.go";
405      break;
406    case FlutterTextInputActionSend:
407      actionString = @"TextInputAction.send";
408      break;
409    case FlutterTextInputActionSearch:
410      actionString = @"TextInputAction.search";
411      break;
412    case FlutterTextInputActionNext:
413      actionString = @"TextInputAction.next";
414      break;
415    case FlutterTextInputActionContinue:
416      actionString = @"TextInputAction.continue";
417      break;
418    case FlutterTextInputActionJoin:
419      actionString = @"TextInputAction.join";
420      break;
421    case FlutterTextInputActionRoute:
422      actionString = @"TextInputAction.route";
423      break;
424    case FlutterTextInputActionEmergencyCall:
425      actionString = @"TextInputAction.emergencyCall";
426      break;
427    case FlutterTextInputActionNewline:
428      actionString = @"TextInputAction.newline";
429      break;
430  }
431  [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction"
432                              arguments:@[ @(client), actionString ]];
433}
434
435#pragma mark - Screenshot Delegate
436
437- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
438                                  asBase64Encoded:(BOOL)base64Encode {
439  FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";
440  return _shell->Screenshot(type, base64Encode);
441}
442
443- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
444  return _binaryMessenger;
445}
446
447#pragma mark - FlutterBinaryMessenger
448
449- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
450  [self sendOnChannel:channel message:message binaryReply:nil];
451}
452
453- (void)sendOnChannel:(NSString*)channel
454              message:(NSData*)message
455          binaryReply:(FlutterBinaryReply)callback {
456  NSAssert(channel, @"The channel must not be null");
457  fml::RefPtr<flutter::PlatformMessageResponseDarwin> response =
458      (callback == nil) ? nullptr
459                        : fml::MakeRefCounted<flutter::PlatformMessageResponseDarwin>(
460                              ^(NSData* reply) {
461                                callback(reply);
462                              },
463                              _shell->GetTaskRunners().GetPlatformTaskRunner());
464  fml::RefPtr<flutter::PlatformMessage> platformMessage =
465      (message == nil) ? fml::MakeRefCounted<flutter::PlatformMessage>(channel.UTF8String, response)
466                       : fml::MakeRefCounted<flutter::PlatformMessage>(
467                             channel.UTF8String, flutter::GetVectorFromNSData(message), response);
468
469  _shell->GetPlatformView()->DispatchPlatformMessage(platformMessage);
470}
471
472- (void)setMessageHandlerOnChannel:(NSString*)channel
473              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
474  NSAssert(channel, @"The channel must not be null");
475  FML_DCHECK(_shell && _shell->IsSetup());
476  self.iosPlatformView->GetPlatformMessageRouter().SetMessageHandler(channel.UTF8String, handler);
477}
478
479#pragma mark - FlutterTextureRegistry
480
481- (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
482  int64_t textureId = _nextTextureId++;
483  self.iosPlatformView->RegisterExternalTexture(textureId, texture);
484  return textureId;
485}
486
487- (void)unregisterTexture:(int64_t)textureId {
488  _shell->GetPlatformView()->UnregisterTexture(textureId);
489}
490
491- (void)textureFrameAvailable:(int64_t)textureId {
492  _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId);
493}
494
495- (NSString*)lookupKeyForAsset:(NSString*)asset {
496    return nil;
497}
498
499- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
500    return nil;
501}
502
503- (id<FlutterPluginRegistry>)pluginRegistry {
504  return self;
505}
506
507#pragma mark - FlutterPluginRegistry
508
509- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
510  NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);
511  self.pluginPublications[pluginKey] = [NSNull null];
512  return [[[FlutterEngineRegistrar alloc] initWithPlugin:pluginKey flutterEngine:self] autorelease];
513}
514
515- (BOOL)hasPlugin:(NSString*)pluginKey {
516  return _pluginPublications[pluginKey] != nil;
517}
518
519- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
520  return _pluginPublications[pluginKey];
521}
522
523#pragma mark - Memory Notifications
524
525- (void)onMemoryWarning:(NSNotification*)notification {
526  if (_shell) {
527    _shell->NotifyLowMemoryWarning();
528  }
529  [_systemChannel sendMessage:@{@"type" : @"memoryPressure"}];
530}
531
532@end
533
534@implementation FlutterEngineRegistrar {
535  NSString* _pluginKey;
536  FlutterEngine* _flutterEngine;
537}
538
539- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {
540  self = [super init];
541  NSAssert(self, @"Super init cannot be nil");
542  _pluginKey = [pluginKey retain];
543  _flutterEngine = [flutterEngine retain];
544  return self;
545}
546
547- (void)dealloc {
548  [_pluginKey release];
549  [_flutterEngine release];
550  [super dealloc];
551}
552
553- (NSObject<FlutterBinaryMessenger>*)messenger {
554  return _flutterEngine.binaryMessenger;
555}
556
557- (NSObject<FlutterTextureRegistry>*)textures {
558  return _flutterEngine;
559}
560
561- (void)publish:(NSObject*)value {
562  _flutterEngine.pluginPublications[_pluginKey] = value;
563}
564
565- (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
566                      channel:(FlutterMethodChannel*)channel {
567  [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
568    [delegate handleMethodCall:call result:result];
569  }];
570}
571
572- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
573  id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
574  if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
575    id<FlutterAppLifeCycleProvider> lifeCycleProvider =
576        (id<FlutterAppLifeCycleProvider>)appDelegate;
577    [lifeCycleProvider addApplicationLifeCycleDelegate:delegate];
578  }
579}
580
581- (NSString*)lookupKeyForAsset:(NSString*)asset {
582  return [_flutterEngine lookupKeyForAsset:asset];
583}
584
585- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
586  return [_flutterEngine lookupKeyForAsset:asset fromPackage:package];
587}
588
589- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
590                     withId:(NSString*)factoryId {
591  [_flutterEngine platformViewsController] -> RegisterViewFactory(factory, factoryId);
592}
593
594@end
595