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