Flutter iOS Embedder
FlutterEngine.mm
Go to the documentation of this file.
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 #include "common/settings.h"
6 #define FML_USED_ON_EMBEDDER
7 
9 
10 #include <memory>
11 
12 #include "flutter/common/constants.h"
13 #include "flutter/fml/message_loop.h"
14 #include "flutter/fml/platform/darwin/platform_version.h"
15 #include "flutter/fml/trace_event.h"
16 #include "flutter/runtime/ptrace_check.h"
17 #include "flutter/shell/common/engine.h"
18 #include "flutter/shell/common/platform_view.h"
19 #include "flutter/shell/common/shell.h"
20 #include "flutter/shell/common/switches.h"
21 #include "flutter/shell/common/thread_host.h"
22 #include "flutter/shell/common/variable_refresh_rate_display.h"
23 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
26 #import "flutter/shell/platform/darwin/ios/InternalFlutterSwift/InternalFlutterSwift.h"
44 #include "flutter/shell/profiling/sampling_profiler.h"
45 
47 
48 /// Inheriting ThreadConfigurer and use iOS platform thread API to configure the thread priorities
49 /// Using iOS platform thread API to configure thread priority
50 static void IOSPlatformThreadConfigSetter(const fml::Thread::ThreadConfig& config) {
51  // set thread name
52  fml::Thread::SetCurrentThreadName(config);
53 
54  // set thread priority
55  switch (config.priority) {
56  case fml::Thread::ThreadPriority::kBackground: {
57  pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0);
58  [[NSThread currentThread] setThreadPriority:0];
59  break;
60  }
61  case fml::Thread::ThreadPriority::kNormal: {
62  pthread_set_qos_class_self_np(QOS_CLASS_DEFAULT, 0);
63  [[NSThread currentThread] setThreadPriority:0.5];
64  break;
65  }
66  case fml::Thread::ThreadPriority::kRaster:
67  case fml::Thread::ThreadPriority::kDisplay: {
68  pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
69  [[NSThread currentThread] setThreadPriority:1.0];
70  sched_param param;
71  int policy;
72  pthread_t thread = pthread_self();
73  if (!pthread_getschedparam(thread, &policy, &param)) {
74  param.sched_priority = 50;
75  pthread_setschedparam(thread, policy, &param);
76  }
77  break;
78  }
79  }
80 }
81 
82 #pragma mark - Public exported constants
83 
84 NSString* const FlutterDefaultDartEntrypoint = nil;
85 NSString* const FlutterDefaultInitialRoute = nil;
86 
87 #pragma mark - Internal constants
88 
89 NSString* const kFlutterKeyDataChannel = @"flutter/keydata";
90 static constexpr int kNumProfilerSamplesPerSec = 5;
91 
93 @property(nonatomic, weak) FlutterEngine* flutterEngine;
94 - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine;
95 @end
96 
102 
103 #pragma mark - Properties
104 
105 @property(nonatomic, readonly) FlutterDartProject* dartProject;
106 @property(nonatomic, readonly, copy) NSString* labelPrefix;
107 @property(nonatomic, readonly, assign) BOOL allowHeadlessExecution;
108 @property(nonatomic, readonly, assign) BOOL restorationEnabled;
109 
110 @property(nonatomic, strong) FlutterPlatformViewsController* platformViewsController;
111 
112 // Maintains a dictionary of plugin names that have registered with the engine. Used by
113 // FlutterEngineRegistrar to implement a FlutterPluginRegistrar.
114 @property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
115 @property(nonatomic, readonly) NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* registrars;
116 
117 @property(nonatomic, readwrite, copy) NSString* isolateId;
118 @property(nonatomic, copy) NSString* initialRoute;
119 @property(nonatomic, strong) id<NSObject> flutterViewControllerWillDeallocObserver;
120 @property(nonatomic, strong) FlutterDartVMServicePublisher* publisher;
121 @property(nonatomic, strong) FlutterConnectionCollection* connections;
122 @property(nonatomic, assign) int64_t nextTextureId;
123 
124 #pragma mark - Channel properties
125 
126 @property(nonatomic, strong) FlutterPlatformPlugin* platformPlugin;
127 @property(nonatomic, strong) FlutterTextInputPlugin* textInputPlugin;
128 @property(nonatomic, strong) FlutterUndoManagerPlugin* undoManagerPlugin;
129 @property(nonatomic, strong) FlutterSpellCheckPlugin* spellCheckPlugin;
130 @property(nonatomic, strong) FlutterRestorationPlugin* restorationPlugin;
131 @property(nonatomic, strong) FlutterMethodChannel* localizationChannel;
132 @property(nonatomic, strong) FlutterMethodChannel* navigationChannel;
133 @property(nonatomic, strong) FlutterMethodChannel* restorationChannel;
134 @property(nonatomic, strong) FlutterMethodChannel* platformChannel;
135 @property(nonatomic, strong) FlutterMethodChannel* platformViewsChannel;
136 @property(nonatomic, strong) FlutterMethodChannel* textInputChannel;
137 @property(nonatomic, strong) FlutterMethodChannel* undoManagerChannel;
138 @property(nonatomic, strong) FlutterMethodChannel* scribbleChannel;
139 @property(nonatomic, strong) FlutterMethodChannel* spellCheckChannel;
140 @property(nonatomic, strong) FlutterBasicMessageChannel* lifecycleChannel;
141 @property(nonatomic, strong) FlutterBasicMessageChannel* systemChannel;
142 @property(nonatomic, strong) FlutterBasicMessageChannel* settingsChannel;
143 @property(nonatomic, strong) FlutterBasicMessageChannel* keyEventChannel;
144 @property(nonatomic, strong) FlutterMethodChannel* screenshotChannel;
145 
146 #pragma mark - Embedder API properties
147 
148 @property(nonatomic, assign) BOOL enableEmbedderAPI;
149 // Function pointers for interacting with the embedder.h API.
150 @property(nonatomic) FlutterEngineProcTable& embedderAPI;
151 
152 @end
153 
154 @implementation FlutterEngine {
155  std::shared_ptr<flutter::ThreadHost> _threadHost;
156  std::unique_ptr<flutter::Shell> _shell;
157 
159  std::shared_ptr<flutter::SamplingProfiler> _profiler;
160 
163 }
164 
165 - (int64_t)engineIdentifier {
166  return reinterpret_cast<int64_t>((__bridge void*)self);
167 }
168 
169 - (instancetype)init {
170  return [self initWithName:@"FlutterEngine" project:nil allowHeadlessExecution:YES];
171 }
172 
173 - (instancetype)initWithName:(NSString*)labelPrefix {
174  return [self initWithName:labelPrefix project:nil allowHeadlessExecution:YES];
175 }
176 
177 - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
178  return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES];
179 }
180 
181 - (instancetype)initWithName:(NSString*)labelPrefix
182  project:(FlutterDartProject*)project
183  allowHeadlessExecution:(BOOL)allowHeadlessExecution {
184  return [self initWithName:labelPrefix
185  project:project
186  allowHeadlessExecution:allowHeadlessExecution
187  restorationEnabled:NO];
188 }
189 
190 - (instancetype)initWithName:(NSString*)labelPrefix
191  project:(FlutterDartProject*)project
192  allowHeadlessExecution:(BOOL)allowHeadlessExecution
193  restorationEnabled:(BOOL)restorationEnabled {
194  self = [super init];
195  NSAssert(self, @"Super init cannot be nil");
196  NSAssert(labelPrefix, @"labelPrefix is required");
197 
198  _restorationEnabled = restorationEnabled;
199  _allowHeadlessExecution = allowHeadlessExecution;
200  _labelPrefix = [labelPrefix copy];
201  _dartProject = project ?: [[FlutterDartProject alloc] init];
202 
203  _enableEmbedderAPI = _dartProject.settings.enable_embedder_api;
204  if (_enableEmbedderAPI) {
205  NSLog(@"============== iOS: enable_embedder_api is on ==============");
206  _embedderAPI.struct_size = sizeof(FlutterEngineProcTable);
207  FlutterEngineGetProcAddresses(&_embedderAPI);
208  }
209 
210  if (!EnableTracingIfNecessary(_dartProject.settings)) {
211  NSLog(
212  @"Cannot create a FlutterEngine instance in debug mode without Flutter tooling or "
213  @"Xcode.\n\nTo launch in debug mode in iOS 14+, run flutter run from Flutter tools, run "
214  @"from an IDE with a Flutter IDE plugin or run the iOS project from Xcode.\nAlternatively "
215  @"profile and release mode apps can be launched from the home screen.");
216  return nil;
217  }
218 
219  _pluginPublications = [[NSMutableDictionary alloc] init];
220  _registrars = [[NSMutableDictionary alloc] init];
221  [self recreatePlatformViewsController];
222  _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
223  _textureRegistry = [[FlutterTextureRegistryRelay alloc] initWithParent:self];
224  _connections = [[FlutterConnectionCollection alloc] init];
225 
226  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
227  [center addObserver:self
228  selector:@selector(onMemoryWarning:)
229  name:UIApplicationDidReceiveMemoryWarningNotification
230  object:nil];
231 
232  [self setUpLifecycleNotifications:center];
233 
234  [center addObserver:self
235  selector:@selector(onLocaleUpdated:)
236  name:NSCurrentLocaleDidChangeNotification
237  object:nil];
238 
239  return self;
240 }
241 
242 + (FlutterEngine*)engineForIdentifier:(int64_t)identifier {
243  NSAssert([[NSThread currentThread] isMainThread], @"Must be called on the main thread.");
244  return (__bridge FlutterEngine*)reinterpret_cast<void*>(identifier);
245 }
246 
247 - (void)setUpLifecycleNotifications:(NSNotificationCenter*)center {
248  // If the application is not available, use the scene for lifecycle notifications if available.
250  [center addObserver:self
251  selector:@selector(sceneWillEnterForeground:)
252  name:UISceneWillEnterForegroundNotification
253  object:nil];
254  [center addObserver:self
255  selector:@selector(sceneDidEnterBackground:)
256  name:UISceneDidEnterBackgroundNotification
257  object:nil];
258  return;
259  }
260  [center addObserver:self
261  selector:@selector(applicationWillEnterForeground:)
262  name:UIApplicationWillEnterForegroundNotification
263  object:nil];
264  [center addObserver:self
265  selector:@selector(applicationDidEnterBackground:)
266  name:UIApplicationDidEnterBackgroundNotification
267  object:nil];
268 }
269 
270 - (void)recreatePlatformViewsController {
271  _renderingApi = flutter::GetRenderingAPIForProcess(/*force_software=*/false);
272  _platformViewsController = [[FlutterPlatformViewsController alloc] init];
273 }
274 
275 - (flutter::IOSRenderingAPI)platformViewsRenderingAPI {
276  return _renderingApi;
277 }
278 
279 - (void)dealloc {
280  /// Notify plugins of dealloc. This should happen first in dealloc since the
281  /// plugins may be talking to things like the binaryMessenger.
282  [_pluginPublications enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL* stop) {
283  if ([object respondsToSelector:@selector(detachFromEngineForRegistrar:)]) {
284  NSObject<FlutterPluginRegistrar>* registrar = self.registrars[key];
285  [object detachFromEngineForRegistrar:registrar];
286  }
287  }];
288 
289  // nil out weak references.
290  // TODO(cbracken): https://github.com/flutter/flutter/issues/156222
291  // Ensure that FlutterEngineRegistrar is using weak pointers, then eliminate this code.
292  [_registrars
293  enumerateKeysAndObjectsUsingBlock:^(id key, FlutterEngineRegistrar* registrar, BOOL* stop) {
294  registrar.flutterEngine = nil;
295  }];
296 
297  _binaryMessenger.parent = nil;
298  _textureRegistry.parent = nil;
299 
300  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
301  if (_flutterViewControllerWillDeallocObserver) {
302  [center removeObserver:_flutterViewControllerWillDeallocObserver];
303  }
304  [center removeObserver:self];
305 }
306 
307 - (flutter::Shell&)shell {
308  FML_DCHECK(_shell);
309  return *_shell;
310 }
311 
312 - (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics {
313  if (!self.platformView) {
314  return;
315  }
316  self.platformView->SetViewportMetrics(flutter::kFlutterImplicitViewId, viewportMetrics);
317 }
318 
319 - (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet {
320  if (!self.platformView) {
321  return;
322  }
323  self.platformView->DispatchPointerDataPacket(std::move(packet));
324 }
325 
326 - (void)installFirstFrameCallback:(void (^)(void))block {
327  if (!self.platformView) {
328  return;
329  }
330 
331  __weak FlutterEngine* weakSelf = self;
332  self.platformView->SetNextFrameCallback([weakSelf, block] {
333  FlutterEngine* strongSelf = weakSelf;
334  if (!strongSelf) {
335  return;
336  }
337  FML_DCHECK(strongSelf.platformTaskRunner);
338  FML_DCHECK(strongSelf.rasterTaskRunner);
339  FML_DCHECK(strongSelf.rasterTaskRunner->RunsTasksOnCurrentThread());
340  // Get callback on raster thread and jump back to platform thread.
341  strongSelf.platformTaskRunner->PostTask([block]() { block(); });
342  });
343 }
344 
345 - (void)enableSemantics:(BOOL)enabled withFlags:(int64_t)flags {
346  if (!self.platformView) {
347  return;
348  }
349  self.platformView->SetSemanticsEnabled(enabled);
350  self.platformView->SetAccessibilityFeatures(flags);
351 }
352 
353 - (void)notifyViewCreated {
354  if (!self.platformView) {
355  return;
356  }
357  self.platformView->NotifyCreated();
358 }
359 
360 - (void)notifyViewDestroyed {
361  if (!self.platformView) {
362  return;
363  }
364  self.platformView->NotifyDestroyed();
365 }
366 
367 - (flutter::PlatformViewIOS*)platformView {
368  if (!_shell) {
369  return nullptr;
370  }
371  return static_cast<flutter::PlatformViewIOS*>(_shell->GetPlatformView().get());
372 }
373 
374 - (fml::RefPtr<fml::TaskRunner>)platformTaskRunner {
375  if (!_shell) {
376  return {};
377  }
378  return _shell->GetTaskRunners().GetPlatformTaskRunner();
379 }
380 
381 - (fml::RefPtr<fml::TaskRunner>)uiTaskRunner {
382  if (!_shell) {
383  return {};
384  }
385  return _shell->GetTaskRunners().GetUITaskRunner();
386 }
387 
388 - (fml::RefPtr<fml::TaskRunner>)rasterTaskRunner {
389  if (!_shell) {
390  return {};
391  }
392  return _shell->GetTaskRunners().GetRasterTaskRunner();
393 }
394 
395 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
396  callback:(FlutterKeyEventCallback)callback
397  userData:(void*)userData API_AVAILABLE(ios(13.4)) {
398  if (@available(iOS 13.4, *)) {
399  } else {
400  return;
401  }
402  if (!self.platformView) {
403  return;
404  }
405  const char* character = event.character;
406 
407  flutter::KeyData key_data;
408  key_data.Clear();
409  key_data.timestamp = (uint64_t)event.timestamp;
410  switch (event.type) {
411  case kFlutterKeyEventTypeUp:
412  key_data.type = flutter::KeyEventType::kUp;
413  break;
414  case kFlutterKeyEventTypeDown:
415  key_data.type = flutter::KeyEventType::kDown;
416  break;
417  case kFlutterKeyEventTypeRepeat:
418  key_data.type = flutter::KeyEventType::kRepeat;
419  break;
420  }
421  key_data.physical = event.physical;
422  key_data.logical = event.logical;
423  key_data.synthesized = event.synthesized;
424 
425  auto packet = std::make_unique<flutter::KeyDataPacket>(key_data, character);
426  NSData* message = [NSData dataWithBytes:packet->data().data() length:packet->data().size()];
427 
428  auto response = ^(NSData* reply) {
429  if (callback == nullptr) {
430  return;
431  }
432  BOOL handled = FALSE;
433  if (reply.length == 1 && *reinterpret_cast<const uint8_t*>(reply.bytes) == 1) {
434  handled = TRUE;
435  }
436  callback(handled, userData);
437  };
438 
439  [self sendOnChannel:kFlutterKeyDataChannel message:message binaryReply:response];
440 }
441 
442 - (void)ensureSemanticsEnabled {
443  if (!self.platformView) {
444  return;
445  }
446  self.platformView->SetSemanticsEnabled(true);
447 }
448 
449 - (void)setViewController:(FlutterViewController*)viewController {
450  FML_DCHECK(self.platformView);
451  _viewController = viewController;
452  self.platformView->SetOwnerViewController(_viewController);
453  [self maybeSetupPlatformViewChannels];
454  [self updateDisplays];
455  self.textInputPlugin.viewController = viewController;
456 
457  if (viewController) {
458  __weak __block FlutterEngine* weakSelf = self;
459  self.flutterViewControllerWillDeallocObserver =
460  [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc
461  object:viewController
462  queue:[NSOperationQueue mainQueue]
463  usingBlock:^(NSNotification* note) {
464  [weakSelf notifyViewControllerDeallocated];
465  }];
466  } else {
467  self.flutterViewControllerWillDeallocObserver = nil;
468  [self notifyLowMemory];
469  }
470 }
471 
472 - (void)attachView {
473  FML_DCHECK(self.platformView);
474  self.platformView->attachView();
475 }
476 
477 - (void)setFlutterViewControllerWillDeallocObserver:(id<NSObject>)observer {
478  if (observer != _flutterViewControllerWillDeallocObserver) {
479  if (_flutterViewControllerWillDeallocObserver) {
480  [[NSNotificationCenter defaultCenter]
481  removeObserver:_flutterViewControllerWillDeallocObserver];
482  }
483  _flutterViewControllerWillDeallocObserver = observer;
484  }
485 }
486 
487 - (void)notifyViewControllerDeallocated {
488  [self.lifecycleChannel sendMessage:@"AppLifecycleState.detached"];
489  self.textInputPlugin.viewController = nil;
490  if (!self.allowHeadlessExecution) {
491  [self destroyContext];
492  } else if (self.platformView) {
493  self.platformView->SetOwnerViewController({});
494  }
495  [self.textInputPlugin resetViewResponder];
496  _viewController = nil;
497 }
498 
499 - (void)destroyContext {
500  [self resetChannels];
501  self.isolateId = nil;
502  _shell.reset();
503  _profiler.reset();
504  _threadHost.reset();
505  _platformViewsController = nil;
506 }
507 
508 - (NSURL*)vmServiceUrl {
509  return self.publisher.url;
510 }
511 
512 - (void)resetChannels {
513  self.localizationChannel = nil;
514  self.navigationChannel = nil;
515  self.restorationChannel = nil;
516  self.platformChannel = nil;
517  self.platformViewsChannel = nil;
518  self.textInputChannel = nil;
519  self.undoManagerChannel = nil;
520  self.scribbleChannel = nil;
521  self.lifecycleChannel = nil;
522  self.systemChannel = nil;
523  self.settingsChannel = nil;
524  self.keyEventChannel = nil;
525  self.spellCheckChannel = nil;
526 }
527 
528 - (void)startProfiler {
529  FML_DCHECK(!_threadHost->name_prefix.empty());
530  _profiler = std::make_shared<flutter::SamplingProfiler>(
531  _threadHost->name_prefix.c_str(), _threadHost->profiler_thread->GetTaskRunner(),
532  []() {
533  flutter::ProfilerMetricsIOS profiler_metrics;
534  return profiler_metrics.GenerateSample();
535  },
537  _profiler->Start();
538 }
539 
540 // If you add a channel, be sure to also update `resetChannels`.
541 // Channels get a reference to the engine, and therefore need manual
542 // cleanup for proper collection.
543 - (void)setUpChannels {
544  // This will be invoked once the shell is done setting up and the isolate ID
545  // for the UI isolate is available.
546  __weak FlutterEngine* weakSelf = self;
547  [_binaryMessenger setMessageHandlerOnChannel:@"flutter/isolate"
548  binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
549  if (weakSelf) {
550  weakSelf.isolateId =
551  [[FlutterStringCodec sharedInstance] decode:message];
552  }
553  }];
554 
555  self.localizationChannel =
556  [[FlutterMethodChannel alloc] initWithName:@"flutter/localization"
557  binaryMessenger:self.binaryMessenger
558  codec:[FlutterJSONMethodCodec sharedInstance]];
559 
560  self.navigationChannel =
561  [[FlutterMethodChannel alloc] initWithName:@"flutter/navigation"
562  binaryMessenger:self.binaryMessenger
563  codec:[FlutterJSONMethodCodec sharedInstance]];
564 
565  if ([_initialRoute length] > 0) {
566  // Flutter isn't ready to receive this method call yet but the channel buffer will cache this.
567  [self.navigationChannel invokeMethod:@"setInitialRoute" arguments:_initialRoute];
568  _initialRoute = nil;
569  }
570 
571  self.restorationChannel =
572  [[FlutterMethodChannel alloc] initWithName:@"flutter/restoration"
573  binaryMessenger:self.binaryMessenger
574  codec:[FlutterStandardMethodCodec sharedInstance]];
575 
576  self.platformChannel =
577  [[FlutterMethodChannel alloc] initWithName:@"flutter/platform"
578  binaryMessenger:self.binaryMessenger
579  codec:[FlutterJSONMethodCodec sharedInstance]];
580 
581  self.platformViewsChannel =
582  [[FlutterMethodChannel alloc] initWithName:@"flutter/platform_views"
583  binaryMessenger:self.binaryMessenger
584  codec:[FlutterStandardMethodCodec sharedInstance]];
585 
586  self.textInputChannel =
587  [[FlutterMethodChannel alloc] initWithName:@"flutter/textinput"
588  binaryMessenger:self.binaryMessenger
589  codec:[FlutterJSONMethodCodec sharedInstance]];
590 
591  self.undoManagerChannel =
592  [[FlutterMethodChannel alloc] initWithName:@"flutter/undomanager"
593  binaryMessenger:self.binaryMessenger
594  codec:[FlutterJSONMethodCodec sharedInstance]];
595 
596  self.scribbleChannel =
597  [[FlutterMethodChannel alloc] initWithName:@"flutter/scribble"
598  binaryMessenger:self.binaryMessenger
599  codec:[FlutterJSONMethodCodec sharedInstance]];
600 
601  self.spellCheckChannel =
602  [[FlutterMethodChannel alloc] initWithName:@"flutter/spellcheck"
603  binaryMessenger:self.binaryMessenger
604  codec:[FlutterStandardMethodCodec sharedInstance]];
605 
606  self.lifecycleChannel =
607  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/lifecycle"
608  binaryMessenger:self.binaryMessenger
610 
611  self.systemChannel =
612  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/system"
613  binaryMessenger:self.binaryMessenger
615 
616  self.settingsChannel =
617  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/settings"
618  binaryMessenger:self.binaryMessenger
620 
621  self.keyEventChannel =
622  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/keyevent"
623  binaryMessenger:self.binaryMessenger
625 
626  self.textInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:self];
627  self.textInputPlugin.indirectScribbleDelegate = self;
628  [self.textInputPlugin setUpIndirectScribbleInteraction:self.viewController];
629 
630  self.undoManagerPlugin = [[FlutterUndoManagerPlugin alloc] initWithDelegate:self];
631  self.platformPlugin = [[FlutterPlatformPlugin alloc] initWithEngine:self];
632 
633  self.restorationPlugin =
634  [[FlutterRestorationPlugin alloc] initWithChannel:self.restorationChannel
635  restorationEnabled:self.restorationEnabled];
636  self.spellCheckPlugin = [[FlutterSpellCheckPlugin alloc] init];
637 
638  self.screenshotChannel =
639  [[FlutterMethodChannel alloc] initWithName:@"flutter/screenshot"
640  binaryMessenger:self.binaryMessenger
641  codec:[FlutterStandardMethodCodec sharedInstance]];
642 
643  [self.screenshotChannel setMethodCallHandler:^(FlutterMethodCall* _Nonnull call,
644  FlutterResult _Nonnull result) {
645  FlutterEngine* strongSelf = weakSelf;
646  if (!(strongSelf && strongSelf->_shell && strongSelf->_shell->IsSetup())) {
647  return result([FlutterError
648  errorWithCode:@"invalid_state"
649  message:@"Requesting screenshot while engine is not running."
650  details:nil]);
651  }
652  flutter::Rasterizer::Screenshot screenshot =
653  [strongSelf screenshot:flutter::Rasterizer::ScreenshotType::SurfaceData base64Encode:NO];
654  if (!screenshot.data) {
655  return result([FlutterError errorWithCode:@"failure"
656  message:@"Unable to get screenshot."
657  details:nil]);
658  }
659  // TODO(gaaclarke): Find way to eliminate this data copy.
660  NSData* data = [NSData dataWithBytes:screenshot.data->writable_data()
661  length:screenshot.data->size()];
662  NSString* format = [NSString stringWithUTF8String:screenshot.format.c_str()];
663  NSNumber* width = @(screenshot.frame_size.fWidth);
664  NSNumber* height = @(screenshot.frame_size.fHeight);
665  return result(@[ width, height, format ?: [NSNull null], data ]);
666  }];
667 }
668 
669 - (void)maybeSetupPlatformViewChannels {
670  if (_shell && self.shell.IsSetup()) {
671  __weak FlutterEngine* weakSelf = self;
672 
673  [self.platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
674  [weakSelf.platformPlugin handleMethodCall:call result:result];
675  }];
676 
677  [self.platformViewsChannel
678  setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
679  if (weakSelf) {
680  [weakSelf.platformViewsController onMethodCall:call result:result];
681  }
682  }];
683 
684  [self.textInputChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
685  [weakSelf.textInputPlugin handleMethodCall:call result:result];
686  }];
687 
688  [self.undoManagerChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
689  [weakSelf.undoManagerPlugin handleMethodCall:call result:result];
690  }];
691 
692  [self.spellCheckChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
693  [weakSelf.spellCheckPlugin handleMethodCall:call result:result];
694  }];
695  }
696 }
697 
698 - (flutter::Rasterizer::Screenshot)screenshot:(flutter::Rasterizer::ScreenshotType)type
699  base64Encode:(bool)base64Encode {
700  return self.shell.Screenshot(type, base64Encode);
701 }
702 
703 - (void)launchEngine:(NSString*)entrypoint
704  libraryURI:(NSString*)libraryOrNil
705  entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
706  // Launch the Dart application with the inferred run configuration.
707  flutter::RunConfiguration configuration =
708  [self.dartProject runConfigurationForEntrypoint:entrypoint
709  libraryOrNil:libraryOrNil
710  entrypointArgs:entrypointArgs];
711 
712  configuration.SetEngineId(self.engineIdentifier);
713  self.shell.RunEngine(std::move(configuration));
714 }
715 
716 - (void)setUpShell:(std::unique_ptr<flutter::Shell>)shell
717  withVMServicePublication:(BOOL)doesVMServicePublication {
718  _shell = std::move(shell);
719  [self setUpChannels];
720  [self onLocaleUpdated:nil];
721  [self updateDisplays];
722  self.publisher = [[FlutterDartVMServicePublisher alloc]
723  initWithEnableVMServicePublication:doesVMServicePublication];
724  [self maybeSetupPlatformViewChannels];
725  _shell->SetGpuAvailability(_isGpuDisabled ? flutter::GpuAvailability::kUnavailable
726  : flutter::GpuAvailability::kAvailable);
727 }
728 
729 + (BOOL)isProfilerEnabled {
730  bool profilerEnabled = false;
731 #if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) || \
732  (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE)
733  profilerEnabled = true;
734 #endif
735  return profilerEnabled;
736 }
737 
738 + (NSString*)generateThreadLabel:(NSString*)labelPrefix {
739  static size_t s_shellCount = 0;
740  return [NSString stringWithFormat:@"%@.%zu", labelPrefix, ++s_shellCount];
741 }
742 
743 static flutter::ThreadHost MakeThreadHost(NSString* thread_label,
744  const flutter::Settings& settings) {
745  // The current thread will be used as the platform thread. Ensure that the message loop is
746  // initialized.
747  fml::MessageLoop::EnsureInitializedForCurrentThread();
748 
749  uint32_t threadHostType = flutter::ThreadHost::Type::kRaster | flutter::ThreadHost::Type::kIo;
750  if (settings.merged_platform_ui_thread != flutter::Settings::MergedPlatformUIThread::kEnabled) {
751  threadHostType |= flutter::ThreadHost::Type::kUi;
752  }
753 
754  if ([FlutterEngine isProfilerEnabled]) {
755  threadHostType = threadHostType | flutter::ThreadHost::Type::kProfiler;
756  }
757 
758  flutter::ThreadHost::ThreadHostConfig host_config(thread_label.UTF8String, threadHostType,
760 
761  host_config.ui_config =
762  fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName(
763  flutter::ThreadHost::Type::kUi, thread_label.UTF8String),
764  fml::Thread::ThreadPriority::kDisplay);
765  host_config.raster_config =
766  fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName(
767  flutter::ThreadHost::Type::kRaster, thread_label.UTF8String),
768  fml::Thread::ThreadPriority::kRaster);
769 
770  host_config.io_config =
771  fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName(
772  flutter::ThreadHost::Type::kIo, thread_label.UTF8String),
773  fml::Thread::ThreadPriority::kNormal);
774 
775  return (flutter::ThreadHost){host_config};
776 }
777 
778 static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSString* libraryURI) {
779  if (libraryURI) {
780  FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library";
781  settings->advisory_script_entrypoint = entrypoint.UTF8String;
782  settings->advisory_script_uri = libraryURI.UTF8String;
783  } else if (entrypoint) {
784  settings->advisory_script_entrypoint = entrypoint.UTF8String;
785  settings->advisory_script_uri = std::string("main.dart");
786  } else {
787  settings->advisory_script_entrypoint = std::string("main");
788  settings->advisory_script_uri = std::string("main.dart");
789  }
790 }
791 
792 - (BOOL)createShell:(NSString*)entrypoint
793  libraryURI:(NSString*)libraryURI
794  initialRoute:(NSString*)initialRoute {
795  if (_shell != nullptr) {
796  [FlutterLogger logWarning:@"This FlutterEngine was already invoked."];
797  return NO;
798  }
799 
800  self.initialRoute = initialRoute;
801 
802  auto settings = [self.dartProject settings];
803  if (initialRoute != nil) {
804  self.initialRoute = initialRoute;
805  } else if (settings.route.empty() == false) {
806  self.initialRoute = [NSString stringWithUTF8String:settings.route.c_str()];
807  }
808 
809  auto platformData = [self.dartProject defaultPlatformData];
810 
811  SetEntryPoint(&settings, entrypoint, libraryURI);
812 
813  NSString* threadLabel = [FlutterEngine generateThreadLabel:self.labelPrefix];
814  _threadHost = std::make_shared<flutter::ThreadHost>();
815  *_threadHost = MakeThreadHost(threadLabel, settings);
816 
817  __weak FlutterEngine* weakSelf = self;
818  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
819  [weakSelf](flutter::Shell& shell) {
820  FlutterEngine* strongSelf = weakSelf;
821  if (!strongSelf) {
822  return std::unique_ptr<flutter::PlatformViewIOS>();
823  }
824  [strongSelf recreatePlatformViewsController];
825  strongSelf.platformViewsController.taskRunner =
826  shell.GetTaskRunners().GetPlatformTaskRunner();
827  return std::make_unique<flutter::PlatformViewIOS>(
828  shell, strongSelf->_renderingApi, strongSelf.platformViewsController,
829  shell.GetTaskRunners(), shell.GetConcurrentWorkerTaskRunner(),
830  shell.GetIsGpuDisabledSyncSwitch());
831  };
832 
833  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
834  [](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };
835 
836  fml::RefPtr<fml::TaskRunner> ui_runner;
837  if (settings.enable_impeller &&
838  settings.merged_platform_ui_thread == flutter::Settings::MergedPlatformUIThread::kEnabled) {
839  ui_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
840  } else {
841  ui_runner = _threadHost->ui_thread->GetTaskRunner();
842  }
843  flutter::TaskRunners task_runners(threadLabel.UTF8String, // label
844  fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform
845  _threadHost->raster_thread->GetTaskRunner(), // raster
846  ui_runner, // ui
847  _threadHost->io_thread->GetTaskRunner() // io
848  );
849 
850  // Disable GPU if the app or scene is running in the background.
851  self.isGpuDisabled = self.viewController
852  ? self.viewController.stateIsBackground
854  FlutterSharedApplication.application.applicationState ==
855  UIApplicationStateBackground;
856 
857  // Create the shell. This is a blocking operation.
858  std::unique_ptr<flutter::Shell> shell = flutter::Shell::Create(
859  /*platform_data=*/platformData,
860  /*task_runners=*/task_runners,
861  /*settings=*/settings,
862  /*on_create_platform_view=*/on_create_platform_view,
863  /*on_create_rasterizer=*/on_create_rasterizer,
864  /*is_gpu_disabled=*/_isGpuDisabled);
865 
866  if (shell == nullptr) {
867  NSString* errorMessage = [NSString
868  stringWithFormat:@"Could not start a shell FlutterEngine with entrypoint: %@", entrypoint];
869  [FlutterLogger logError:errorMessage];
870  } else {
871  [self setUpShell:std::move(shell)
872  withVMServicePublication:settings.enable_vm_service_publication];
873  if ([FlutterEngine isProfilerEnabled]) {
874  [self startProfiler];
875  }
876  }
877 
878  return _shell != nullptr;
879 }
880 
881 - (void)updateDisplays {
882  if (!_shell) {
883  // Tests may do this.
884  return;
885  }
886  auto vsync_waiter = _shell->GetVsyncWaiter().lock();
887  auto vsync_waiter_ios = std::static_pointer_cast<flutter::VsyncWaiterIOS>(vsync_waiter);
888  std::vector<std::unique_ptr<flutter::Display>> displays;
889  auto screen_size = UIScreen.mainScreen.nativeBounds.size;
890  auto scale = UIScreen.mainScreen.scale;
891  displays.push_back(std::make_unique<flutter::VariableRefreshRateDisplay>(
892  0, vsync_waiter_ios, screen_size.width, screen_size.height, scale));
893  _shell->OnDisplayUpdates(std::move(displays));
894 }
895 
896 - (BOOL)run {
897  return [self runWithEntrypoint:FlutterDefaultDartEntrypoint
898  libraryURI:nil
899  initialRoute:FlutterDefaultInitialRoute];
900 }
901 
902 - (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
903  return [self runWithEntrypoint:entrypoint
904  libraryURI:libraryURI
905  initialRoute:FlutterDefaultInitialRoute];
906 }
907 
908 - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
909  return [self runWithEntrypoint:entrypoint libraryURI:nil initialRoute:FlutterDefaultInitialRoute];
910 }
911 
912 - (BOOL)runWithEntrypoint:(NSString*)entrypoint initialRoute:(NSString*)initialRoute {
913  return [self runWithEntrypoint:entrypoint libraryURI:nil initialRoute:initialRoute];
914 }
915 
916 - (BOOL)runWithEntrypoint:(NSString*)entrypoint
917  libraryURI:(NSString*)libraryURI
918  initialRoute:(NSString*)initialRoute {
919  return [self runWithEntrypoint:entrypoint
920  libraryURI:libraryURI
921  initialRoute:initialRoute
922  entrypointArgs:nil];
923 }
924 
925 - (BOOL)runWithEntrypoint:(NSString*)entrypoint
926  libraryURI:(NSString*)libraryURI
927  initialRoute:(NSString*)initialRoute
928  entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
929  if ([self createShell:entrypoint libraryURI:libraryURI initialRoute:initialRoute]) {
930  [self launchEngine:entrypoint libraryURI:libraryURI entrypointArgs:entrypointArgs];
931  }
932 
933  return _shell != nullptr;
934 }
935 
936 - (void)notifyLowMemory {
937  if (_shell) {
938  _shell->NotifyLowMemoryWarning();
939  }
940  [self.systemChannel sendMessage:@{@"type" : @"memoryPressure"}];
941 }
942 
943 #pragma mark - Text input delegate
944 
945 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
946  updateEditingClient:(int)client
947  withState:(NSDictionary*)state {
948  [self.textInputChannel invokeMethod:@"TextInputClient.updateEditingState"
949  arguments:@[ @(client), state ]];
950 }
951 
952 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
953  updateEditingClient:(int)client
954  withState:(NSDictionary*)state
955  withTag:(NSString*)tag {
956  [self.textInputChannel invokeMethod:@"TextInputClient.updateEditingStateWithTag"
957  arguments:@[ @(client), @{tag : state} ]];
958 }
959 
960 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
961  updateEditingClient:(int)client
962  withDelta:(NSDictionary*)delta {
963  [self.textInputChannel invokeMethod:@"TextInputClient.updateEditingStateWithDeltas"
964  arguments:@[ @(client), delta ]];
965 }
966 
967 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
968  updateFloatingCursor:(FlutterFloatingCursorDragState)state
969  withClient:(int)client
970  withPosition:(NSDictionary*)position {
971  NSString* stateString;
972  switch (state) {
973  case FlutterFloatingCursorDragStateStart:
974  stateString = @"FloatingCursorDragState.start";
975  break;
976  case FlutterFloatingCursorDragStateUpdate:
977  stateString = @"FloatingCursorDragState.update";
978  break;
979  case FlutterFloatingCursorDragStateEnd:
980  stateString = @"FloatingCursorDragState.end";
981  break;
982  }
983  [self.textInputChannel invokeMethod:@"TextInputClient.updateFloatingCursor"
984  arguments:@[ @(client), stateString, position ]];
985 }
986 
987 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
988  performAction:(FlutterTextInputAction)action
989  withClient:(int)client {
990  NSString* actionString;
991  switch (action) {
992  case FlutterTextInputActionUnspecified:
993  // Where did the term "unspecified" come from? iOS has a "default" and Android
994  // has "unspecified." These 2 terms seem to mean the same thing but we need
995  // to pick just one. "unspecified" was chosen because "default" is often a
996  // reserved word in languages with switch statements (dart, java, etc).
997  actionString = @"TextInputAction.unspecified";
998  break;
999  case FlutterTextInputActionDone:
1000  actionString = @"TextInputAction.done";
1001  break;
1002  case FlutterTextInputActionGo:
1003  actionString = @"TextInputAction.go";
1004  break;
1005  case FlutterTextInputActionSend:
1006  actionString = @"TextInputAction.send";
1007  break;
1008  case FlutterTextInputActionSearch:
1009  actionString = @"TextInputAction.search";
1010  break;
1011  case FlutterTextInputActionNext:
1012  actionString = @"TextInputAction.next";
1013  break;
1014  case FlutterTextInputActionContinue:
1015  actionString = @"TextInputAction.continueAction";
1016  break;
1017  case FlutterTextInputActionJoin:
1018  actionString = @"TextInputAction.join";
1019  break;
1020  case FlutterTextInputActionRoute:
1021  actionString = @"TextInputAction.route";
1022  break;
1023  case FlutterTextInputActionEmergencyCall:
1024  actionString = @"TextInputAction.emergencyCall";
1025  break;
1026  case FlutterTextInputActionNewline:
1027  actionString = @"TextInputAction.newline";
1028  break;
1029  }
1030  [self.textInputChannel invokeMethod:@"TextInputClient.performAction"
1031  arguments:@[ @(client), actionString ]];
1032 }
1033 
1034 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1035  showAutocorrectionPromptRectForStart:(NSUInteger)start
1036  end:(NSUInteger)end
1037  withClient:(int)client {
1038  [self.textInputChannel invokeMethod:@"TextInputClient.showAutocorrectionPromptRect"
1039  arguments:@[ @(client), @(start), @(end) ]];
1040 }
1041 
1042 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1043  willDismissEditMenuWithTextInputClient:(int)client {
1044  [self.platformChannel invokeMethod:@"ContextMenu.onDismissSystemContextMenu"
1045  arguments:@[ @(client) ]];
1046 }
1047 
1048 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1049  shareSelectedText:(NSString*)selectedText {
1050  [self.platformPlugin showShareViewController:selectedText];
1051 }
1052 
1053 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1054  searchWebWithSelectedText:(NSString*)selectedText {
1055  [self.platformPlugin searchWeb:selectedText];
1056 }
1057 
1058 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1059  lookUpSelectedText:(NSString*)selectedText {
1060  [self.platformPlugin showLookUpViewController:selectedText];
1061 }
1062 
1063 #pragma mark - FlutterViewEngineDelegate
1064 
1065 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client {
1066  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1067  // the framework has finished transitioning to the Scribble channel.
1068  // https://github.com/flutter/flutter/pull/115296
1069  [self.textInputChannel invokeMethod:@"TextInputClient.showToolbar" arguments:@[ @(client) ]];
1070 }
1071 
1072 - (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
1073  focusElement:(UIScribbleElementIdentifier)elementIdentifier
1074  atPoint:(CGPoint)referencePoint
1075  result:(FlutterResult)callback {
1076  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1077  // the framework has finished transitioning to the Scribble channel.
1078  // https://github.com/flutter/flutter/pull/115296
1079  [self.textInputChannel
1080  invokeMethod:@"TextInputClient.focusElement"
1081  arguments:@[ elementIdentifier, @(referencePoint.x), @(referencePoint.y) ]
1082  result:callback];
1083 }
1084 
1085 - (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
1086  requestElementsInRect:(CGRect)rect
1087  result:(FlutterResult)callback {
1088  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1089  // the framework has finished transitioning to the Scribble channel.
1090  // https://github.com/flutter/flutter/pull/115296
1091  [self.textInputChannel
1092  invokeMethod:@"TextInputClient.requestElementsInRect"
1093  arguments:@[ @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height) ]
1094  result:callback];
1095 }
1096 
1097 - (void)flutterTextInputViewScribbleInteractionBegan:(FlutterTextInputView*)textInputView {
1098  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1099  // the framework has finished transitioning to the Scribble channel.
1100  // https://github.com/flutter/flutter/pull/115296
1101  [self.textInputChannel invokeMethod:@"TextInputClient.scribbleInteractionBegan" arguments:nil];
1102 }
1103 
1104 - (void)flutterTextInputViewScribbleInteractionFinished:(FlutterTextInputView*)textInputView {
1105  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1106  // the framework has finished transitioning to the Scribble channel.
1107  // https://github.com/flutter/flutter/pull/115296
1108  [self.textInputChannel invokeMethod:@"TextInputClient.scribbleInteractionFinished" arguments:nil];
1109 }
1110 
1111 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1112  insertTextPlaceholderWithSize:(CGSize)size
1113  withClient:(int)client {
1114  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1115  // the framework has finished transitioning to the Scribble channel.
1116  // https://github.com/flutter/flutter/pull/115296
1117  [self.textInputChannel invokeMethod:@"TextInputClient.insertTextPlaceholder"
1118  arguments:@[ @(client), @(size.width), @(size.height) ]];
1119 }
1120 
1121 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1122  removeTextPlaceholder:(int)client {
1123  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1124  // the framework has finished transitioning to the Scribble channel.
1125  // https://github.com/flutter/flutter/pull/115296
1126  [self.textInputChannel invokeMethod:@"TextInputClient.removeTextPlaceholder"
1127  arguments:@[ @(client) ]];
1128 }
1129 
1130 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1131  didResignFirstResponderWithTextInputClient:(int)client {
1132  // When flutter text input view resign first responder, send a message to
1133  // framework to ensure the focus state is correct. This is useful when close
1134  // keyboard from platform side.
1135  [self.textInputChannel invokeMethod:@"TextInputClient.onConnectionClosed"
1136  arguments:@[ @(client) ]];
1137 
1138  // Platform view's first responder detection logic:
1139  //
1140  // All text input widgets (e.g. EditableText) are backed by a dummy UITextInput view
1141  // in the TextInputPlugin. When this dummy UITextInput view resigns first responder,
1142  // check if any platform view becomes first responder. If any platform view becomes
1143  // first responder, send a "viewFocused" channel message to inform the framework to un-focus
1144  // the previously focused text input.
1145  //
1146  // Caveat:
1147  // 1. This detection logic does not cover the scenario when a platform view becomes
1148  // first responder without any flutter text input resigning its first responder status
1149  // (e.g. user tapping on platform view first). For now it works fine because the TextInputPlugin
1150  // does not track the focused platform view id (which is different from Android implementation).
1151  //
1152  // 2. This detection logic assumes that all text input widgets are backed by a dummy
1153  // UITextInput view in the TextInputPlugin, which may not hold true in the future.
1154 
1155  // Have to check in the next run loop, because iOS requests the previous first responder to
1156  // resign before requesting the next view to become first responder.
1157  dispatch_async(dispatch_get_main_queue(), ^(void) {
1158  long platform_view_id = [self.platformViewsController firstResponderPlatformViewId];
1159  if (platform_view_id == -1) {
1160  return;
1161  }
1162 
1163  [self.platformViewsChannel invokeMethod:@"viewFocused" arguments:@(platform_view_id)];
1164  });
1165 }
1166 
1167 #pragma mark - Undo Manager Delegate
1168 
1169 - (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction {
1170  NSString* action = (direction == FlutterUndoRedoDirectionUndo) ? @"undo" : @"redo";
1171  [self.undoManagerChannel invokeMethod:@"UndoManagerClient.handleUndo" arguments:@[ action ]];
1172 }
1173 
1174 - (UIView<UITextInput>*)activeTextInputView {
1175  return [[self textInputPlugin] textInputView];
1176 }
1177 
1178 - (NSUndoManager*)undoManager {
1179  return self.viewController.undoManager;
1180 }
1181 
1182 #pragma mark - Screenshot Delegate
1183 
1184 - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
1185  asBase64Encoded:(BOOL)base64Encode {
1186  FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";
1187  return _shell->Screenshot(type, base64Encode);
1188 }
1189 
1190 - (void)flutterViewAccessibilityDidCall {
1191  if (self.viewController.view.accessibilityElements == nil) {
1192  [self ensureSemanticsEnabled];
1193  }
1194 }
1195 
1196 - (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
1197  return _binaryMessenger;
1198 }
1199 
1200 - (NSObject<FlutterTextureRegistry>*)textureRegistry {
1201  return _textureRegistry;
1202 }
1203 
1204 // For test only. Ideally we should create a dependency injector for all dependencies and
1205 // remove this.
1206 - (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger {
1207  // Discard the previous messenger and keep the new one.
1208  if (binaryMessenger != _binaryMessenger) {
1209  _binaryMessenger.parent = nil;
1210  _binaryMessenger = binaryMessenger;
1211  }
1212 }
1213 
1214 #pragma mark - FlutterBinaryMessenger
1215 
1216 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
1217  [self sendOnChannel:channel message:message binaryReply:nil];
1218 }
1219 
1220 - (void)sendOnChannel:(NSString*)channel
1221  message:(NSData*)message
1222  binaryReply:(FlutterBinaryReply)callback {
1223  NSParameterAssert(channel);
1224  NSAssert(_shell && _shell->IsSetup(),
1225  @"Sending a message before the FlutterEngine has been run.");
1226  fml::RefPtr<flutter::PlatformMessageResponseDarwin> response =
1227  (callback == nil) ? nullptr
1228  : fml::MakeRefCounted<flutter::PlatformMessageResponseDarwin>(
1229  ^(NSData* reply) {
1230  callback(reply);
1231  },
1232  _shell->GetTaskRunners().GetPlatformTaskRunner());
1233  std::unique_ptr<flutter::PlatformMessage> platformMessage =
1234  (message == nil) ? std::make_unique<flutter::PlatformMessage>(channel.UTF8String, response)
1235  : std::make_unique<flutter::PlatformMessage>(
1236  channel.UTF8String, flutter::CopyNSDataToMapping(message), response);
1237 
1238  _shell->GetPlatformView()->DispatchPlatformMessage(std::move(platformMessage));
1239  // platformMessage takes ownership of response.
1240  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
1241 }
1242 
1243 - (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
1245 }
1246 
1247 - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel
1248  binaryMessageHandler:
1249  (FlutterBinaryMessageHandler)handler {
1250  return [self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
1251 }
1252 
1254  setMessageHandlerOnChannel:(NSString*)channel
1255  binaryMessageHandler:(FlutterBinaryMessageHandler)handler
1256  taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
1257  NSParameterAssert(channel);
1258  if (_shell && _shell->IsSetup()) {
1259  self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String,
1260  handler, taskQueue);
1261  return [self.connections acquireConnectionForChannel:channel];
1262  } else {
1263  NSAssert(!handler, @"Setting a message handler before the FlutterEngine has been run.");
1264  // Setting a handler to nil for a channel that has not yet been set up is a no-op.
1265  return [FlutterConnectionCollection makeErrorConnectionWithErrorCode:-1L];
1266  }
1267 }
1268 
1269 - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection {
1270  if (_shell && _shell->IsSetup()) {
1271  NSString* channel = [self.connections cleanupConnectionWithID:connection];
1272  if (channel.length > 0) {
1273  self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String, nil,
1274  nil);
1275  }
1276  }
1277 }
1278 
1279 #pragma mark - FlutterTextureRegistry
1280 
1281 - (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
1282  FML_DCHECK(self.platformView);
1283  int64_t textureId = self.nextTextureId++;
1284  self.platformView->RegisterExternalTexture(textureId, texture);
1285  return textureId;
1286 }
1287 
1288 - (void)unregisterTexture:(int64_t)textureId {
1289  _shell->GetPlatformView()->UnregisterTexture(textureId);
1290 }
1291 
1292 - (void)textureFrameAvailable:(int64_t)textureId {
1293  _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId);
1294 }
1295 
1296 - (NSString*)lookupKeyForAsset:(NSString*)asset {
1297  return [FlutterDartProject lookupKeyForAsset:asset];
1298 }
1299 
1300 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
1301  return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
1302 }
1303 
1304 - (id<FlutterPluginRegistry>)pluginRegistry {
1305  return self;
1306 }
1307 
1308 #pragma mark - FlutterPluginRegistry
1309 
1310 - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
1311  NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);
1312  self.pluginPublications[pluginKey] = [NSNull null];
1313  FlutterEngineRegistrar* result = [[FlutterEngineRegistrar alloc] initWithPlugin:pluginKey
1314  flutterEngine:self];
1315  self.registrars[pluginKey] = result;
1316  return result;
1317 }
1318 
1319 - (BOOL)hasPlugin:(NSString*)pluginKey {
1320  return _pluginPublications[pluginKey] != nil;
1321 }
1322 
1323 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
1324  return _pluginPublications[pluginKey];
1325 }
1326 
1327 #pragma mark - Notifications
1328 
1329 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1330  [self flutterWillEnterForeground:notification];
1331 }
1332 
1333 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1334  [self flutterDidEnterBackground:notification];
1335 }
1336 
1337 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1338  [self flutterWillEnterForeground:notification];
1339 }
1340 
1341 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1342  [self flutterDidEnterBackground:notification];
1343 }
1344 
1345 - (void)flutterWillEnterForeground:(NSNotification*)notification {
1346  [self setIsGpuDisabled:NO];
1347 }
1348 
1349 - (void)flutterDidEnterBackground:(NSNotification*)notification {
1350  [self setIsGpuDisabled:YES];
1351  [self notifyLowMemory];
1352 }
1353 
1354 - (void)onMemoryWarning:(NSNotification*)notification {
1355  [self notifyLowMemory];
1356 }
1357 
1358 - (void)setIsGpuDisabled:(BOOL)value {
1359  if (_shell) {
1360  _shell->SetGpuAvailability(value ? flutter::GpuAvailability::kUnavailable
1361  : flutter::GpuAvailability::kAvailable);
1362  }
1363  _isGpuDisabled = value;
1364 }
1365 
1366 #pragma mark - Locale updates
1367 
1368 - (void)onLocaleUpdated:(NSNotification*)notification {
1369  // Get and pass the user's preferred locale list to dart:ui.
1370  NSMutableArray<NSString*>* localeData = [[NSMutableArray alloc] init];
1371  NSArray<NSString*>* preferredLocales = [NSLocale preferredLanguages];
1372  for (NSString* localeID in preferredLocales) {
1373  NSLocale* locale = [[NSLocale alloc] initWithLocaleIdentifier:localeID];
1374  NSString* languageCode = [locale objectForKey:NSLocaleLanguageCode];
1375  NSString* countryCode = [locale objectForKey:NSLocaleCountryCode];
1376  NSString* scriptCode = [locale objectForKey:NSLocaleScriptCode];
1377  NSString* variantCode = [locale objectForKey:NSLocaleVariantCode];
1378  if (!languageCode) {
1379  continue;
1380  }
1381  [localeData addObject:languageCode];
1382  [localeData addObject:(countryCode ? countryCode : @"")];
1383  [localeData addObject:(scriptCode ? scriptCode : @"")];
1384  [localeData addObject:(variantCode ? variantCode : @"")];
1385  }
1386  if (localeData.count == 0) {
1387  return;
1388  }
1389  [self.localizationChannel invokeMethod:@"setLocale" arguments:localeData];
1390 }
1391 
1392 - (void)waitForFirstFrameSync:(NSTimeInterval)timeout
1393  callback:(NS_NOESCAPE void (^_Nonnull)(BOOL didTimeout))callback {
1394  fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000);
1395  fml::Status status = self.shell.WaitForFirstFrame(waitTime);
1396  callback(status.code() == fml::StatusCode::kDeadlineExceeded);
1397 }
1398 
1399 - (void)waitForFirstFrame:(NSTimeInterval)timeout
1400  callback:(void (^_Nonnull)(BOOL didTimeout))callback {
1401  dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
1402  dispatch_group_t group = dispatch_group_create();
1403 
1404  __weak FlutterEngine* weakSelf = self;
1405  __block BOOL didTimeout = NO;
1406  dispatch_group_async(group, queue, ^{
1407  FlutterEngine* strongSelf = weakSelf;
1408  if (!strongSelf) {
1409  return;
1410  }
1411 
1412  fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000);
1413  fml::Status status = strongSelf.shell.WaitForFirstFrame(waitTime);
1414  didTimeout = status.code() == fml::StatusCode::kDeadlineExceeded;
1415  });
1416 
1417  // Only execute the main queue task once the background task has completely finished executing.
1418  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
1419  // Strongly capture self on the task dispatched to the main thread.
1420  //
1421  // When we capture weakSelf strongly in the above block on a background thread, we risk the
1422  // possibility that all other strong references to FlutterEngine go out of scope while the block
1423  // executes and that the engine is dealloc'ed at the end of the above block on a background
1424  // thread. FlutterEngine is not safe to release on any thread other than the main thread.
1425  //
1426  // self is never nil here since it's a strong reference that's verified non-nil above, but we
1427  // use a conditional check to avoid an unused expression compiler warning.
1428  FlutterEngine* strongSelf = self;
1429  if (!strongSelf) {
1430  return;
1431  }
1432  callback(didTimeout);
1433  });
1434 }
1435 
1436 - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint
1437  libraryURI:(/*nullable*/ NSString*)libraryURI
1438  initialRoute:(/*nullable*/ NSString*)initialRoute
1439  entrypointArgs:(/*nullable*/ NSArray<NSString*>*)entrypointArgs {
1440  NSAssert(_shell, @"Spawning from an engine without a shell (possibly not run).");
1441  FlutterEngine* result = [[FlutterEngine alloc] initWithName:self.labelPrefix
1442  project:self.dartProject
1443  allowHeadlessExecution:self.allowHeadlessExecution];
1444  flutter::RunConfiguration configuration =
1445  [self.dartProject runConfigurationForEntrypoint:entrypoint
1446  libraryOrNil:libraryURI
1447  entrypointArgs:entrypointArgs];
1448 
1449  configuration.SetEngineId(result.engineIdentifier);
1450 
1451  fml::WeakPtr<flutter::PlatformView> platform_view = _shell->GetPlatformView();
1452  FML_DCHECK(platform_view);
1453  // Static-cast safe since this class always creates PlatformViewIOS instances.
1454  flutter::PlatformViewIOS* ios_platform_view =
1455  static_cast<flutter::PlatformViewIOS*>(platform_view.get());
1456  std::shared_ptr<flutter::IOSContext> context = ios_platform_view->GetIosContext();
1457  FML_DCHECK(context);
1458 
1459  // Lambda captures by pointers to ObjC objects are fine here because the
1460  // create call is synchronous.
1461  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
1462  [result, context](flutter::Shell& shell) {
1463  [result recreatePlatformViewsController];
1464  result.platformViewsController.taskRunner = shell.GetTaskRunners().GetPlatformTaskRunner();
1465  return std::make_unique<flutter::PlatformViewIOS>(
1466  shell, context, result.platformViewsController, shell.GetTaskRunners());
1467  };
1468 
1469  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
1470  [](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };
1471 
1472  std::string cppInitialRoute;
1473  if (initialRoute) {
1474  cppInitialRoute = [initialRoute UTF8String];
1475  }
1476 
1477  std::unique_ptr<flutter::Shell> shell = _shell->Spawn(
1478  std::move(configuration), cppInitialRoute, on_create_platform_view, on_create_rasterizer);
1479 
1480  result->_threadHost = _threadHost;
1481  result->_profiler = _profiler;
1482  result->_isGpuDisabled = _isGpuDisabled;
1483  [result setUpShell:std::move(shell) withVMServicePublication:NO];
1484  return result;
1485 }
1486 
1487 - (const flutter::ThreadHost&)threadHost {
1488  return *_threadHost;
1489 }
1490 
1491 - (FlutterDartProject*)project {
1492  return self.dartProject;
1493 }
1494 
1495 @end
1496 
1497 @implementation FlutterEngineRegistrar {
1498  NSString* _pluginKey;
1499 }
1500 
1501 - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {
1502  self = [super init];
1503  NSAssert(self, @"Super init cannot be nil");
1504  _pluginKey = [pluginKey copy];
1505  _flutterEngine = flutterEngine;
1506  return self;
1507 }
1508 
1509 - (NSObject<FlutterBinaryMessenger>*)messenger {
1510  return _flutterEngine.binaryMessenger;
1511 }
1512 
1513 - (NSObject<FlutterTextureRegistry>*)textures {
1514  return _flutterEngine.textureRegistry;
1515 }
1516 
1517 - (void)publish:(NSObject*)value {
1518  _flutterEngine.pluginPublications[_pluginKey] = value;
1519 }
1520 
1521 - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
1522  channel:(FlutterMethodChannel*)channel {
1523  [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1524  [delegate handleMethodCall:call result:result];
1525  }];
1526 }
1527 
1528 - (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
1529  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") {
1530  id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
1531  if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
1532  id<FlutterAppLifeCycleProvider> lifeCycleProvider =
1533  (id<FlutterAppLifeCycleProvider>)appDelegate;
1534  [lifeCycleProvider addApplicationLifeCycleDelegate:delegate];
1535  }
1536 }
1537 
1538 - (NSString*)lookupKeyForAsset:(NSString*)asset {
1539  return [_flutterEngine lookupKeyForAsset:asset];
1540 }
1541 
1542 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
1543  return [_flutterEngine lookupKeyForAsset:asset fromPackage:package];
1544 }
1545 
1546 - (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
1547  withId:(NSString*)factoryId {
1548  [self registerViewFactory:factory
1549  withId:factoryId
1550  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1551 }
1552 
1553 - (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
1554  withId:(NSString*)factoryId
1555  gestureRecognizersBlockingPolicy:
1556  (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy {
1557  [_flutterEngine.platformViewsController registerViewFactory:factory
1558  withId:factoryId
1559  gestureRecognizersBlockingPolicy:gestureRecognizersBlockingPolicy];
1560 }
1561 
1562 @end
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
int64_t FlutterBinaryMessengerConnection
void(^ FlutterResult)(id _Nullable result)
NSString *const FlutterDefaultDartEntrypoint
std::shared_ptr< flutter::SamplingProfiler > _profiler
std::unique_ptr< flutter::Shell > _shell
NSString *const kFlutterKeyDataChannel
NSString *const FlutterDefaultInitialRoute
flutter::IOSRenderingAPI _renderingApi
FlutterTextureRegistryRelay * _textureRegistry
static FLUTTER_ASSERT_ARC void IOSPlatformThreadConfigSetter(const fml::Thread::ThreadConfig &config)
static constexpr int kNumProfilerSamplesPerSec
FlutterBinaryMessengerRelay * _binaryMessenger
std::unique_ptr< flutter::PlatformViewIOS > platform_view
FlutterPlatformViewGestureRecognizersBlockingPolicy
BOOL _restorationEnabled
FlutterViewController * viewController
FlutterTextInputPlugin * textInputPlugin
FlutterEngineProcTable & embedderAPI
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
const flutter::Settings & settings()
NSString * lookupKeyForAsset:(NSString *asset)
static NSObject< FlutterTaskQueue > * MakeBackgroundTaskQueue()
const std::shared_ptr< IOSContext > & GetIosContext()
void SetSemanticsEnabled(bool enabled) override
NSObject< FlutterBinaryMessenger > * parent
FlutterMethodChannel * textInputChannel
flutter::PlatformViewIOS * platformView()
flutter::Shell & shell()
FlutterMethodChannel * navigationChannel
FlutterBasicMessageChannel * keyEventChannel
FlutterBasicMessageChannel * lifecycleChannel
FlutterMethodChannel * platformChannel
FlutterMethodChannel * localizationChannel
NSString * isolateId
FlutterBasicMessageChannel * systemChannel
FlutterBasicMessageChannel * settingsChannel
FlutterMethodChannel * restorationChannel
FlutterEngine * flutterEngine
instancetype errorWithCode:message:details:(NSString *code,[message] NSString *_Nullable message,[details] id _Nullable details)
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
NSObject< FlutterTextureRegistry > * parent
fml::MallocMapping CopyNSDataToMapping(NSData *data)
IOSRenderingAPI GetRenderingAPIForProcess(bool force_software)
instancetype sharedInstance()
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)