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 <UIKit/UIKit.h>
6 #include "common/settings.h"
7 #define FML_USED_ON_EMBEDDER
8 
10 
11 #include <memory>
12 
13 #include "flutter/common/constants.h"
14 #include "flutter/fml/message_loop.h"
15 #include "flutter/fml/platform/darwin/platform_version.h"
16 #include "flutter/fml/trace_event.h"
17 #include "flutter/runtime/ptrace_check.h"
18 #include "flutter/shell/common/engine.h"
19 #include "flutter/shell/common/platform_view.h"
20 #include "flutter/shell/common/shell.h"
21 #include "flutter/shell/common/switches.h"
22 #include "flutter/shell/common/thread_host.h"
23 #include "flutter/shell/common/variable_refresh_rate_display.h"
24 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
27 #import "flutter/shell/platform/darwin/ios/InternalFlutterSwift/InternalFlutterSwift.h"
45 #include "flutter/shell/profiling/sampling_profiler.h"
46 
48 
49 /// Inheriting ThreadConfigurer and use iOS platform thread API to configure the thread priorities
50 /// Using iOS platform thread API to configure thread priority
51 static void IOSPlatformThreadConfigSetter(const fml::Thread::ThreadConfig& config) {
52  // set thread name
53  fml::Thread::SetCurrentThreadName(config);
54 
55  // set thread priority
56  switch (config.priority) {
57  case fml::Thread::ThreadPriority::kBackground: {
58  pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0);
59  [[NSThread currentThread] setThreadPriority:0];
60  break;
61  }
62  case fml::Thread::ThreadPriority::kNormal: {
63  pthread_set_qos_class_self_np(QOS_CLASS_DEFAULT, 0);
64  [[NSThread currentThread] setThreadPriority:0.5];
65  break;
66  }
67  case fml::Thread::ThreadPriority::kRaster:
68  case fml::Thread::ThreadPriority::kDisplay: {
69  pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
70  [[NSThread currentThread] setThreadPriority:1.0];
71  sched_param param;
72  int policy;
73  pthread_t thread = pthread_self();
74  if (!pthread_getschedparam(thread, &policy, &param)) {
75  param.sched_priority = 50;
76  pthread_setschedparam(thread, policy, &param);
77  }
78  break;
79  }
80  }
81 }
82 
83 #pragma mark - Public exported constants
84 
85 NSString* const FlutterDefaultDartEntrypoint = nil;
86 NSString* const FlutterDefaultInitialRoute = nil;
87 
88 #pragma mark - Internal constants
89 
90 NSString* const kFlutterKeyDataChannel = @"flutter/keydata";
91 static constexpr int kNumProfilerSamplesPerSec = 5;
92 
94 @property(nonatomic, weak) FlutterEngine* flutterEngine;
95 - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine;
96 @end
97 
103 
104 #pragma mark - Properties
105 
106 @property(nonatomic, readonly) FlutterDartProject* dartProject;
107 @property(nonatomic, readonly, copy) NSString* labelPrefix;
108 @property(nonatomic, readonly, assign) BOOL allowHeadlessExecution;
109 @property(nonatomic, readonly, assign) BOOL restorationEnabled;
110 
111 @property(nonatomic, strong) FlutterPlatformViewsController* platformViewsController;
112 
113 // Maintains a dictionary of plugin names that have registered with the engine. Used by
114 // FlutterEngineRegistrar to implement a FlutterPluginRegistrar.
115 @property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
116 @property(nonatomic, readonly) NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* registrars;
117 
118 @property(nonatomic, readwrite, copy) NSString* isolateId;
119 @property(nonatomic, copy) NSString* initialRoute;
120 @property(nonatomic, strong) id<NSObject> flutterViewControllerWillDeallocObserver;
121 @property(nonatomic, strong) FlutterDartVMServicePublisher* publisher;
122 @property(nonatomic, strong) FlutterConnectionCollection* connections;
123 @property(nonatomic, assign) int64_t nextTextureId;
124 
125 #pragma mark - Channel properties
126 
127 @property(nonatomic, strong) FlutterPlatformPlugin* platformPlugin;
128 @property(nonatomic, strong) FlutterTextInputPlugin* textInputPlugin;
129 @property(nonatomic, strong) FlutterUndoManagerPlugin* undoManagerPlugin;
130 @property(nonatomic, strong) FlutterSpellCheckPlugin* spellCheckPlugin;
131 @property(nonatomic, strong) FlutterRestorationPlugin* restorationPlugin;
132 @property(nonatomic, strong) FlutterMethodChannel* localizationChannel;
133 @property(nonatomic, strong) FlutterMethodChannel* navigationChannel;
134 @property(nonatomic, strong) FlutterMethodChannel* restorationChannel;
135 @property(nonatomic, strong) FlutterMethodChannel* platformChannel;
136 @property(nonatomic, strong) FlutterMethodChannel* platformViewsChannel;
137 @property(nonatomic, strong) FlutterMethodChannel* textInputChannel;
138 @property(nonatomic, strong) FlutterMethodChannel* undoManagerChannel;
139 @property(nonatomic, strong) FlutterMethodChannel* scribbleChannel;
140 @property(nonatomic, strong) FlutterMethodChannel* spellCheckChannel;
141 @property(nonatomic, strong) FlutterBasicMessageChannel* lifecycleChannel;
142 @property(nonatomic, strong) FlutterBasicMessageChannel* systemChannel;
143 @property(nonatomic, strong) FlutterBasicMessageChannel* settingsChannel;
144 @property(nonatomic, strong) FlutterBasicMessageChannel* keyEventChannel;
145 @property(nonatomic, strong) FlutterMethodChannel* screenshotChannel;
146 
147 #pragma mark - Embedder API properties
148 
149 @property(nonatomic, assign) BOOL enableEmbedderAPI;
150 // Function pointers for interacting with the embedder.h API.
151 @property(nonatomic) FlutterEngineProcTable& embedderAPI;
152 
153 @end
154 
155 @implementation FlutterEngine {
156  std::shared_ptr<flutter::ThreadHost> _threadHost;
157  std::unique_ptr<flutter::Shell> _shell;
158 
160  std::shared_ptr<flutter::SamplingProfiler> _profiler;
161 
164 }
165 
166 - (int64_t)engineIdentifier {
167  return reinterpret_cast<int64_t>((__bridge void*)self);
168 }
169 
170 - (instancetype)init {
171  return [self initWithName:@"FlutterEngine" project:nil allowHeadlessExecution:YES];
172 }
173 
174 - (instancetype)initWithName:(NSString*)labelPrefix {
175  return [self initWithName:labelPrefix project:nil allowHeadlessExecution:YES];
176 }
177 
178 - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
179  return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES];
180 }
181 
182 - (instancetype)initWithName:(NSString*)labelPrefix
183  project:(FlutterDartProject*)project
184  allowHeadlessExecution:(BOOL)allowHeadlessExecution {
185  return [self initWithName:labelPrefix
186  project:project
187  allowHeadlessExecution:allowHeadlessExecution
188  restorationEnabled:NO];
189 }
190 
191 - (instancetype)initWithName:(NSString*)labelPrefix
192  project:(FlutterDartProject*)project
193  allowHeadlessExecution:(BOOL)allowHeadlessExecution
194  restorationEnabled:(BOOL)restorationEnabled {
195  self = [super init];
196  NSAssert(self, @"Super init cannot be nil");
197  NSAssert(labelPrefix, @"labelPrefix is required");
198 
199  _restorationEnabled = restorationEnabled;
200  _allowHeadlessExecution = allowHeadlessExecution;
201  _labelPrefix = [labelPrefix copy];
202  _dartProject = project ?: [[FlutterDartProject alloc] init];
203 
204  _enableEmbedderAPI = _dartProject.settings.enable_embedder_api;
205  if (_enableEmbedderAPI) {
206  NSLog(@"============== iOS: enable_embedder_api is on ==============");
207  _embedderAPI.struct_size = sizeof(FlutterEngineProcTable);
208  FlutterEngineGetProcAddresses(&_embedderAPI);
209  }
210 
211  if (!EnableTracingIfNecessary(_dartProject.settings)) {
212  NSLog(
213  @"Cannot create a FlutterEngine instance in debug mode without Flutter tooling or "
214  @"Xcode.\n\nTo launch in debug mode in iOS 14+, run flutter run from Flutter tools, run "
215  @"from an IDE with a Flutter IDE plugin or run the iOS project from Xcode.\nAlternatively "
216  @"profile and release mode apps can be launched from the home screen.");
217  return nil;
218  }
219 
220  _pluginPublications = [[NSMutableDictionary alloc] init];
221  _registrars = [[NSMutableDictionary alloc] init];
222  [self recreatePlatformViewsController];
223  _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
224  _textureRegistry = [[FlutterTextureRegistryRelay alloc] initWithParent:self];
225  _connections = [[FlutterConnectionCollection alloc] init];
226 
227  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
228  [center addObserver:self
229  selector:@selector(onMemoryWarning:)
230  name:UIApplicationDidReceiveMemoryWarningNotification
231  object:nil];
232 
233  [self setUpLifecycleNotifications:center];
234 
235  [center addObserver:self
236  selector:@selector(onLocaleUpdated:)
237  name:NSCurrentLocaleDidChangeNotification
238  object:nil];
239 
240  return self;
241 }
242 
243 + (FlutterEngine*)engineForIdentifier:(int64_t)identifier {
244  NSAssert([[NSThread currentThread] isMainThread], @"Must be called on the main thread.");
245  return (__bridge FlutterEngine*)reinterpret_cast<void*>(identifier);
246 }
247 
248 - (void)setUpLifecycleNotifications:(NSNotificationCenter*)center {
249  // If the application is not available, use the scene for lifecycle notifications if available.
251  [center addObserver:self
252  selector:@selector(sceneWillEnterForeground:)
253  name:UISceneWillEnterForegroundNotification
254  object:nil];
255  [center addObserver:self
256  selector:@selector(sceneDidEnterBackground:)
257  name:UISceneDidEnterBackgroundNotification
258  object:nil];
259  return;
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*)vmServiceUrl {
510  return self.publisher.url;
511 }
512 
513 - (void)resetChannels {
514  self.localizationChannel = nil;
515  self.navigationChannel = nil;
516  self.restorationChannel = nil;
517  self.platformChannel = nil;
518  self.platformViewsChannel = nil;
519  self.textInputChannel = nil;
520  self.undoManagerChannel = nil;
521  self.scribbleChannel = nil;
522  self.lifecycleChannel = nil;
523  self.systemChannel = nil;
524  self.settingsChannel = nil;
525  self.keyEventChannel = nil;
526  self.spellCheckChannel = nil;
527 }
528 
529 - (void)startProfiler {
530  FML_DCHECK(!_threadHost->name_prefix.empty());
531  _profiler = std::make_shared<flutter::SamplingProfiler>(
532  _threadHost->name_prefix.c_str(), _threadHost->profiler_thread->GetTaskRunner(),
533  []() {
534  flutter::ProfilerMetricsIOS profiler_metrics;
535  return profiler_metrics.GenerateSample();
536  },
538  _profiler->Start();
539 }
540 
541 // If you add a channel, be sure to also update `resetChannels`.
542 // Channels get a reference to the engine, and therefore need manual
543 // cleanup for proper collection.
544 - (void)setUpChannels {
545  // This will be invoked once the shell is done setting up and the isolate ID
546  // for the UI isolate is available.
547  __weak FlutterEngine* weakSelf = self;
548  [_binaryMessenger setMessageHandlerOnChannel:@"flutter/isolate"
549  binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
550  if (weakSelf) {
551  weakSelf.isolateId =
552  [[FlutterStringCodec sharedInstance] decode:message];
553  }
554  }];
555 
556  self.localizationChannel =
557  [[FlutterMethodChannel alloc] initWithName:@"flutter/localization"
558  binaryMessenger:self.binaryMessenger
559  codec:[FlutterJSONMethodCodec sharedInstance]];
560 
561  self.navigationChannel =
562  [[FlutterMethodChannel alloc] initWithName:@"flutter/navigation"
563  binaryMessenger:self.binaryMessenger
564  codec:[FlutterJSONMethodCodec sharedInstance]];
565 
566  if ([_initialRoute length] > 0) {
567  // Flutter isn't ready to receive this method call yet but the channel buffer will cache this.
568  [self.navigationChannel invokeMethod:@"setInitialRoute" arguments:_initialRoute];
569  _initialRoute = nil;
570  }
571 
572  self.restorationChannel =
573  [[FlutterMethodChannel alloc] initWithName:@"flutter/restoration"
574  binaryMessenger:self.binaryMessenger
575  codec:[FlutterStandardMethodCodec sharedInstance]];
576 
577  self.platformChannel =
578  [[FlutterMethodChannel alloc] initWithName:@"flutter/platform"
579  binaryMessenger:self.binaryMessenger
580  codec:[FlutterJSONMethodCodec sharedInstance]];
581 
582  self.platformViewsChannel =
583  [[FlutterMethodChannel alloc] initWithName:@"flutter/platform_views"
584  binaryMessenger:self.binaryMessenger
585  codec:[FlutterStandardMethodCodec sharedInstance]];
586 
587  self.textInputChannel =
588  [[FlutterMethodChannel alloc] initWithName:@"flutter/textinput"
589  binaryMessenger:self.binaryMessenger
590  codec:[FlutterJSONMethodCodec sharedInstance]];
591 
592  self.undoManagerChannel =
593  [[FlutterMethodChannel alloc] initWithName:@"flutter/undomanager"
594  binaryMessenger:self.binaryMessenger
595  codec:[FlutterJSONMethodCodec sharedInstance]];
596 
597  self.scribbleChannel =
598  [[FlutterMethodChannel alloc] initWithName:@"flutter/scribble"
599  binaryMessenger:self.binaryMessenger
600  codec:[FlutterJSONMethodCodec sharedInstance]];
601 
602  self.spellCheckChannel =
603  [[FlutterMethodChannel alloc] initWithName:@"flutter/spellcheck"
604  binaryMessenger:self.binaryMessenger
605  codec:[FlutterStandardMethodCodec sharedInstance]];
606 
607  self.lifecycleChannel =
608  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/lifecycle"
609  binaryMessenger:self.binaryMessenger
611 
612  self.systemChannel =
613  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/system"
614  binaryMessenger:self.binaryMessenger
616 
617  self.settingsChannel =
618  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/settings"
619  binaryMessenger:self.binaryMessenger
621 
622  self.keyEventChannel =
623  [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/keyevent"
624  binaryMessenger:self.binaryMessenger
626 
627  self.textInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:self];
628  self.textInputPlugin.indirectScribbleDelegate = self;
629  [self.textInputPlugin setUpIndirectScribbleInteraction:self.viewController];
630 
631  self.undoManagerPlugin = [[FlutterUndoManagerPlugin alloc] initWithDelegate:self];
632  self.platformPlugin = [[FlutterPlatformPlugin alloc] initWithEngine:self];
633 
634  self.restorationPlugin =
635  [[FlutterRestorationPlugin alloc] initWithChannel:self.restorationChannel
636  restorationEnabled:self.restorationEnabled];
637  self.spellCheckPlugin = [[FlutterSpellCheckPlugin alloc] init];
638 
639  self.screenshotChannel =
640  [[FlutterMethodChannel alloc] initWithName:@"flutter/screenshot"
641  binaryMessenger:self.binaryMessenger
642  codec:[FlutterStandardMethodCodec sharedInstance]];
643 
644  [self.screenshotChannel setMethodCallHandler:^(FlutterMethodCall* _Nonnull call,
645  FlutterResult _Nonnull result) {
646  FlutterEngine* strongSelf = weakSelf;
647  if (!(strongSelf && strongSelf->_shell && strongSelf->_shell->IsSetup())) {
648  return result([FlutterError
649  errorWithCode:@"invalid_state"
650  message:@"Requesting screenshot while engine is not running."
651  details:nil]);
652  }
653  flutter::Rasterizer::Screenshot screenshot =
654  [strongSelf screenshot:flutter::Rasterizer::ScreenshotType::SurfaceData base64Encode:NO];
655  if (!screenshot.data) {
656  return result([FlutterError errorWithCode:@"failure"
657  message:@"Unable to get screenshot."
658  details:nil]);
659  }
660  // TODO(gaaclarke): Find way to eliminate this data copy.
661  NSData* data = [NSData dataWithBytes:screenshot.data->writable_data()
662  length:screenshot.data->size()];
663  NSString* format = [NSString stringWithUTF8String:screenshot.format.c_str()];
664  NSNumber* width = @(screenshot.frame_size.width);
665  NSNumber* height = @(screenshot.frame_size.height);
666  return result(@[ width, height, format ?: [NSNull null], data ]);
667  }];
668 }
669 
670 - (void)maybeSetupPlatformViewChannels {
671  if (_shell && self.shell.IsSetup()) {
672  __weak FlutterEngine* weakSelf = self;
673 
674  [self.platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
675  [weakSelf.platformPlugin handleMethodCall:call result:result];
676  }];
677 
678  [self.platformViewsChannel
679  setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
680  if (weakSelf) {
681  [weakSelf.platformViewsController onMethodCall:call result:result];
682  }
683  }];
684 
685  [self.textInputChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
686  [weakSelf.textInputPlugin handleMethodCall:call result:result];
687  }];
688 
689  [self.undoManagerChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
690  [weakSelf.undoManagerPlugin handleMethodCall:call result:result];
691  }];
692 
693  [self.spellCheckChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
694  [weakSelf.spellCheckPlugin handleMethodCall:call result:result];
695  }];
696  }
697 }
698 
699 - (flutter::Rasterizer::Screenshot)screenshot:(flutter::Rasterizer::ScreenshotType)type
700  base64Encode:(bool)base64Encode {
701  return self.shell.Screenshot(type, base64Encode);
702 }
703 
704 - (void)launchEngine:(NSString*)entrypoint
705  libraryURI:(NSString*)libraryOrNil
706  entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
707  // Launch the Dart application with the inferred run configuration.
708  flutter::RunConfiguration configuration =
709  [self.dartProject runConfigurationForEntrypoint:entrypoint
710  libraryOrNil:libraryOrNil
711  entrypointArgs:entrypointArgs];
712 
713  configuration.SetEngineId(self.engineIdentifier);
714  self.shell.RunEngine(std::move(configuration));
715 }
716 
717 - (void)setUpShell:(std::unique_ptr<flutter::Shell>)shell
718  withVMServicePublication:(BOOL)doesVMServicePublication {
719  _shell = std::move(shell);
720  [self setUpChannels];
721  [self onLocaleUpdated:nil];
722  [self updateDisplays];
723  self.publisher = [[FlutterDartVMServicePublisher alloc]
724  initWithEnableVMServicePublication:doesVMServicePublication];
725  [self maybeSetupPlatformViewChannels];
726  _shell->SetGpuAvailability(_isGpuDisabled ? flutter::GpuAvailability::kUnavailable
727  : flutter::GpuAvailability::kAvailable);
728 }
729 
730 + (BOOL)isProfilerEnabled {
731  bool profilerEnabled = false;
732 #if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) || \
733  (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE)
734  profilerEnabled = true;
735 #endif
736  return profilerEnabled;
737 }
738 
739 + (NSString*)generateThreadLabel:(NSString*)labelPrefix {
740  static size_t s_shellCount = 0;
741  return [NSString stringWithFormat:@"%@.%zu", labelPrefix, ++s_shellCount];
742 }
743 
744 static flutter::ThreadHost MakeThreadHost(NSString* thread_label,
745  const flutter::Settings& settings) {
746  // The current thread will be used as the platform thread. Ensure that the message loop is
747  // initialized.
748  fml::MessageLoop::EnsureInitializedForCurrentThread();
749 
750  uint32_t threadHostType = flutter::ThreadHost::Type::kRaster | flutter::ThreadHost::Type::kIo;
751  if (settings.merged_platform_ui_thread != flutter::Settings::MergedPlatformUIThread::kEnabled) {
752  threadHostType |= flutter::ThreadHost::Type::kUi;
753  }
754 
755  if ([FlutterEngine isProfilerEnabled]) {
756  threadHostType = threadHostType | flutter::ThreadHost::Type::kProfiler;
757  }
758 
759  flutter::ThreadHost::ThreadHostConfig host_config(thread_label.UTF8String, threadHostType,
761 
762  host_config.ui_config =
763  fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName(
764  flutter::ThreadHost::Type::kUi, thread_label.UTF8String),
765  fml::Thread::ThreadPriority::kDisplay);
766  host_config.raster_config =
767  fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName(
768  flutter::ThreadHost::Type::kRaster, thread_label.UTF8String),
769  fml::Thread::ThreadPriority::kRaster);
770 
771  host_config.io_config =
772  fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName(
773  flutter::ThreadHost::Type::kIo, thread_label.UTF8String),
774  fml::Thread::ThreadPriority::kNormal);
775 
776  return (flutter::ThreadHost){host_config};
777 }
778 
779 static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSString* libraryURI) {
780  if (libraryURI) {
781  FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library";
782  settings->advisory_script_entrypoint = entrypoint.UTF8String;
783  settings->advisory_script_uri = libraryURI.UTF8String;
784  } else if (entrypoint) {
785  settings->advisory_script_entrypoint = entrypoint.UTF8String;
786  settings->advisory_script_uri = std::string("main.dart");
787  } else {
788  settings->advisory_script_entrypoint = std::string("main");
789  settings->advisory_script_uri = std::string("main.dart");
790  }
791 }
792 
793 - (BOOL)createShell:(NSString*)entrypoint
794  libraryURI:(NSString*)libraryURI
795  initialRoute:(NSString*)initialRoute {
796  if (_shell != nullptr) {
797  [FlutterLogger logWarning:@"This FlutterEngine was already invoked."];
798  return NO;
799  }
800 
801  self.initialRoute = initialRoute;
802 
803  auto settings = [self.dartProject settings];
804  if (initialRoute != nil) {
805  self.initialRoute = initialRoute;
806  } else if (settings.route.empty() == false) {
807  self.initialRoute = [NSString stringWithUTF8String:settings.route.c_str()];
808  }
809 
810  auto platformData = [self.dartProject defaultPlatformData];
811 
812  SetEntryPoint(&settings, entrypoint, libraryURI);
813 
814  NSString* threadLabel = [FlutterEngine generateThreadLabel:self.labelPrefix];
815  _threadHost = std::make_shared<flutter::ThreadHost>();
816  *_threadHost = MakeThreadHost(threadLabel, settings);
817 
818  __weak FlutterEngine* weakSelf = self;
819  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
820  [weakSelf](flutter::Shell& shell) {
821  FlutterEngine* strongSelf = weakSelf;
822  if (!strongSelf) {
823  return std::unique_ptr<flutter::PlatformViewIOS>();
824  }
825  [strongSelf recreatePlatformViewsController];
826  strongSelf.platformViewsController.taskRunner =
827  shell.GetTaskRunners().GetPlatformTaskRunner();
828  return std::make_unique<flutter::PlatformViewIOS>(
829  shell, strongSelf->_renderingApi, strongSelf.platformViewsController,
830  shell.GetTaskRunners(), shell.GetConcurrentWorkerTaskRunner(),
831  shell.GetIsGpuDisabledSyncSwitch());
832  };
833 
834  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
835  [](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };
836 
837  fml::RefPtr<fml::TaskRunner> ui_runner;
838  if (settings.enable_impeller &&
839  settings.merged_platform_ui_thread == flutter::Settings::MergedPlatformUIThread::kEnabled) {
840  ui_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
841  } else {
842  ui_runner = _threadHost->ui_thread->GetTaskRunner();
843  }
844  flutter::TaskRunners task_runners(threadLabel.UTF8String, // label
845  fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform
846  _threadHost->raster_thread->GetTaskRunner(), // raster
847  ui_runner, // ui
848  _threadHost->io_thread->GetTaskRunner() // io
849  );
850 
851  // Disable GPU if the app or scene is running in the background.
852  self.isGpuDisabled = self.viewController
853  ? self.viewController.stateIsBackground
855  FlutterSharedApplication.application.applicationState ==
856  UIApplicationStateBackground;
857 
858  // Create the shell. This is a blocking operation.
859  std::unique_ptr<flutter::Shell> shell = flutter::Shell::Create(
860  /*platform_data=*/platformData,
861  /*task_runners=*/task_runners,
862  /*settings=*/settings,
863  /*on_create_platform_view=*/on_create_platform_view,
864  /*on_create_rasterizer=*/on_create_rasterizer,
865  /*is_gpu_disabled=*/_isGpuDisabled);
866 
867  if (shell == nullptr) {
868  NSString* errorMessage = [NSString
869  stringWithFormat:@"Could not start a shell FlutterEngine with entrypoint: %@", entrypoint];
870  [FlutterLogger logError:errorMessage];
871  } else {
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 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1065  performContextMenuCustomActionWithActionID:(NSString*)actionID
1066  textInputClient:(int)client {
1067  [self.platformChannel invokeMethod:@"ContextMenu.onPerformCustomAction"
1068  arguments:@[ @(client), actionID ]];
1069 }
1070 
1071 #pragma mark - FlutterViewEngineDelegate
1072 
1073 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client {
1074  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1075  // the framework has finished transitioning to the Scribble channel.
1076  // https://github.com/flutter/flutter/pull/115296
1077  [self.textInputChannel invokeMethod:@"TextInputClient.showToolbar" arguments:@[ @(client) ]];
1078 }
1079 
1080 - (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
1081  focusElement:(UIScribbleElementIdentifier)elementIdentifier
1082  atPoint:(CGPoint)referencePoint
1083  result:(FlutterResult)callback {
1084  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1085  // the framework has finished transitioning to the Scribble channel.
1086  // https://github.com/flutter/flutter/pull/115296
1087  [self.textInputChannel
1088  invokeMethod:@"TextInputClient.focusElement"
1089  arguments:@[ elementIdentifier, @(referencePoint.x), @(referencePoint.y) ]
1090  result:callback];
1091 }
1092 
1093 - (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
1094  requestElementsInRect:(CGRect)rect
1095  result:(FlutterResult)callback {
1096  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1097  // the framework has finished transitioning to the Scribble channel.
1098  // https://github.com/flutter/flutter/pull/115296
1099  [self.textInputChannel
1100  invokeMethod:@"TextInputClient.requestElementsInRect"
1101  arguments:@[ @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height) ]
1102  result:callback];
1103 }
1104 
1105 - (void)flutterTextInputViewScribbleInteractionBegan:(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.scribbleInteractionBegan" arguments:nil];
1110 }
1111 
1112 - (void)flutterTextInputViewScribbleInteractionFinished:(FlutterTextInputView*)textInputView {
1113  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1114  // the framework has finished transitioning to the Scribble channel.
1115  // https://github.com/flutter/flutter/pull/115296
1116  [self.textInputChannel invokeMethod:@"TextInputClient.scribbleInteractionFinished" arguments:nil];
1117 }
1118 
1119 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1120  insertTextPlaceholderWithSize:(CGSize)size
1121  withClient:(int)client {
1122  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1123  // the framework has finished transitioning to the Scribble channel.
1124  // https://github.com/flutter/flutter/pull/115296
1125  [self.textInputChannel invokeMethod:@"TextInputClient.insertTextPlaceholder"
1126  arguments:@[ @(client), @(size.width), @(size.height) ]];
1127 }
1128 
1129 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1130  removeTextPlaceholder:(int)client {
1131  // TODO(justinmc): Switch from the TextInputClient to Scribble channel when
1132  // the framework has finished transitioning to the Scribble channel.
1133  // https://github.com/flutter/flutter/pull/115296
1134  [self.textInputChannel invokeMethod:@"TextInputClient.removeTextPlaceholder"
1135  arguments:@[ @(client) ]];
1136 }
1137 
1138 - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1139  didResignFirstResponderWithTextInputClient:(int)client {
1140  // When flutter text input view resign first responder, send a message to
1141  // framework to ensure the focus state is correct. This is useful when close
1142  // keyboard from platform side.
1143  [self.textInputChannel invokeMethod:@"TextInputClient.onConnectionClosed"
1144  arguments:@[ @(client) ]];
1145 
1146  // Platform view's first responder detection logic:
1147  //
1148  // All text input widgets (e.g. EditableText) are backed by a dummy UITextInput view
1149  // in the TextInputPlugin. When this dummy UITextInput view resigns first responder,
1150  // check if any platform view becomes first responder. If any platform view becomes
1151  // first responder, send a "viewFocused" channel message to inform the framework to un-focus
1152  // the previously focused text input.
1153  //
1154  // Caveat:
1155  // 1. This detection logic does not cover the scenario when a platform view becomes
1156  // first responder without any flutter text input resigning its first responder status
1157  // (e.g. user tapping on platform view first). For now it works fine because the TextInputPlugin
1158  // does not track the focused platform view id (which is different from Android implementation).
1159  //
1160  // 2. This detection logic assumes that all text input widgets are backed by a dummy
1161  // UITextInput view in the TextInputPlugin, which may not hold true in the future.
1162 
1163  // Have to check in the next run loop, because iOS requests the previous first responder to
1164  // resign before requesting the next view to become first responder.
1165  dispatch_async(dispatch_get_main_queue(), ^(void) {
1166  long platform_view_id = [self.platformViewsController firstResponderPlatformViewId];
1167  if (platform_view_id == -1) {
1168  return;
1169  }
1170 
1171  [self.platformViewsChannel invokeMethod:@"viewFocused" arguments:@(platform_view_id)];
1172  });
1173 }
1174 
1175 #pragma mark - Undo Manager Delegate
1176 
1177 - (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction {
1178  NSString* action = (direction == FlutterUndoRedoDirectionUndo) ? @"undo" : @"redo";
1179  [self.undoManagerChannel invokeMethod:@"UndoManagerClient.handleUndo" arguments:@[ action ]];
1180 }
1181 
1182 - (UIView<UITextInput>*)activeTextInputView {
1183  return [[self textInputPlugin] textInputView];
1184 }
1185 
1186 - (NSUndoManager*)undoManager {
1187  return self.viewController.undoManager;
1188 }
1189 
1190 #pragma mark - Screenshot Delegate
1191 
1192 - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
1193  asBase64Encoded:(BOOL)base64Encode {
1194  FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";
1195  return _shell->Screenshot(type, base64Encode);
1196 }
1197 
1198 - (void)flutterViewAccessibilityDidCall {
1199  if (self.viewController.view.accessibilityElements == nil) {
1200  [self ensureSemanticsEnabled];
1201  }
1202 }
1203 
1204 - (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
1205  return _binaryMessenger;
1206 }
1207 
1208 - (NSObject<FlutterTextureRegistry>*)textureRegistry {
1209  return _textureRegistry;
1210 }
1211 
1212 // For test only. Ideally we should create a dependency injector for all dependencies and
1213 // remove this.
1214 - (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger {
1215  // Discard the previous messenger and keep the new one.
1216  if (binaryMessenger != _binaryMessenger) {
1217  _binaryMessenger.parent = nil;
1218  _binaryMessenger = binaryMessenger;
1219  }
1220 }
1221 
1222 #pragma mark - FlutterBinaryMessenger
1223 
1224 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
1225  [self sendOnChannel:channel message:message binaryReply:nil];
1226 }
1227 
1228 - (void)sendOnChannel:(NSString*)channel
1229  message:(NSData*)message
1230  binaryReply:(FlutterBinaryReply)callback {
1231  NSParameterAssert(channel);
1232  NSAssert(_shell && _shell->IsSetup(),
1233  @"Sending a message before the FlutterEngine has been run.");
1234  fml::RefPtr<flutter::PlatformMessageResponseDarwin> response =
1235  (callback == nil) ? nullptr
1236  : fml::MakeRefCounted<flutter::PlatformMessageResponseDarwin>(
1237  ^(NSData* reply) {
1238  callback(reply);
1239  },
1240  _shell->GetTaskRunners().GetPlatformTaskRunner());
1241  std::unique_ptr<flutter::PlatformMessage> platformMessage =
1242  (message == nil) ? std::make_unique<flutter::PlatformMessage>(channel.UTF8String, response)
1243  : std::make_unique<flutter::PlatformMessage>(
1244  channel.UTF8String, flutter::CopyNSDataToMapping(message), response);
1245 
1246  _shell->GetPlatformView()->DispatchPlatformMessage(std::move(platformMessage));
1247  // platformMessage takes ownership of response.
1248  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
1249 }
1250 
1251 - (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
1253 }
1254 
1255 - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel
1256  binaryMessageHandler:
1257  (FlutterBinaryMessageHandler)handler {
1258  return [self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
1259 }
1260 
1262  setMessageHandlerOnChannel:(NSString*)channel
1263  binaryMessageHandler:(FlutterBinaryMessageHandler)handler
1264  taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
1265  NSParameterAssert(channel);
1266  if (_shell && _shell->IsSetup()) {
1267  self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String,
1268  handler, taskQueue);
1269  return [self.connections acquireConnectionForChannel:channel];
1270  } else {
1271  NSAssert(!handler, @"Setting a message handler before the FlutterEngine has been run.");
1272  // Setting a handler to nil for a channel that has not yet been set up is a no-op.
1273  return [FlutterConnectionCollection makeErrorConnectionWithErrorCode:-1L];
1274  }
1275 }
1276 
1277 - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection {
1278  if (_shell && _shell->IsSetup()) {
1279  NSString* channel = [self.connections cleanupConnectionWithID:connection];
1280  if (channel.length > 0) {
1281  self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String, nil,
1282  nil);
1283  }
1284  }
1285 }
1286 
1287 #pragma mark - FlutterTextureRegistry
1288 
1289 - (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
1290  FML_DCHECK(self.platformView);
1291  int64_t textureId = self.nextTextureId++;
1292  self.platformView->RegisterExternalTexture(textureId, texture);
1293  return textureId;
1294 }
1295 
1296 - (void)unregisterTexture:(int64_t)textureId {
1297  _shell->GetPlatformView()->UnregisterTexture(textureId);
1298 }
1299 
1300 - (void)textureFrameAvailable:(int64_t)textureId {
1301  _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId);
1302 }
1303 
1304 - (NSString*)lookupKeyForAsset:(NSString*)asset {
1305  return [FlutterDartProject lookupKeyForAsset:asset];
1306 }
1307 
1308 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
1309  return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
1310 }
1311 
1312 - (id<FlutterPluginRegistry>)pluginRegistry {
1313  return self;
1314 }
1315 
1316 #pragma mark - FlutterPluginRegistry
1317 
1318 - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
1319  NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);
1320  self.pluginPublications[pluginKey] = [NSNull null];
1321  FlutterEngineRegistrar* result = [[FlutterEngineRegistrar alloc] initWithPlugin:pluginKey
1322  flutterEngine:self];
1323  self.registrars[pluginKey] = result;
1324  return result;
1325 }
1326 
1327 - (BOOL)hasPlugin:(NSString*)pluginKey {
1328  return _pluginPublications[pluginKey] != nil;
1329 }
1330 
1331 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
1332  return _pluginPublications[pluginKey];
1333 }
1334 
1335 #pragma mark - Notifications
1336 
1337 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1338  [self flutterWillEnterForeground:notification];
1339 }
1340 
1341 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1342  [self flutterDidEnterBackground:notification];
1343 }
1344 
1345 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1346  [self flutterWillEnterForeground:notification];
1347 }
1348 
1349 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1350  [self flutterDidEnterBackground:notification];
1351 }
1352 
1353 - (void)flutterWillEnterForeground:(NSNotification*)notification {
1354  [self setIsGpuDisabled:NO];
1355 }
1356 
1357 - (void)flutterDidEnterBackground:(NSNotification*)notification {
1358  [self setIsGpuDisabled:YES];
1359  [self notifyLowMemory];
1360 }
1361 
1362 - (void)onMemoryWarning:(NSNotification*)notification {
1363  [self notifyLowMemory];
1364 }
1365 
1366 - (void)setIsGpuDisabled:(BOOL)value {
1367  if (_shell) {
1368  _shell->SetGpuAvailability(value ? flutter::GpuAvailability::kUnavailable
1369  : flutter::GpuAvailability::kAvailable);
1370  }
1371  _isGpuDisabled = value;
1372 }
1373 
1374 #pragma mark - Locale updates
1375 
1376 - (void)onLocaleUpdated:(NSNotification*)notification {
1377  // Get and pass the user's preferred locale list to dart:ui.
1378  NSMutableArray<NSString*>* localeData = [[NSMutableArray alloc] init];
1379  NSArray<NSString*>* preferredLocales = [NSLocale preferredLanguages];
1380  for (NSString* localeID in preferredLocales) {
1381  NSLocale* locale = [[NSLocale alloc] initWithLocaleIdentifier:localeID];
1382  NSString* languageCode = [locale objectForKey:NSLocaleLanguageCode];
1383  NSString* countryCode = [locale objectForKey:NSLocaleCountryCode];
1384  NSString* scriptCode = [locale objectForKey:NSLocaleScriptCode];
1385  NSString* variantCode = [locale objectForKey:NSLocaleVariantCode];
1386  if (!languageCode) {
1387  continue;
1388  }
1389  [localeData addObject:languageCode];
1390  [localeData addObject:(countryCode ? countryCode : @"")];
1391  [localeData addObject:(scriptCode ? scriptCode : @"")];
1392  [localeData addObject:(variantCode ? variantCode : @"")];
1393  }
1394  if (localeData.count == 0) {
1395  return;
1396  }
1397  [self.localizationChannel invokeMethod:@"setLocale" arguments:localeData];
1398 }
1399 
1400 - (void)waitForFirstFrameSync:(NSTimeInterval)timeout
1401  callback:(NS_NOESCAPE void (^_Nonnull)(BOOL didTimeout))callback {
1402  fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000);
1403  fml::Status status = self.shell.WaitForFirstFrame(waitTime);
1404  callback(status.code() == fml::StatusCode::kDeadlineExceeded);
1405 }
1406 
1407 - (void)waitForFirstFrame:(NSTimeInterval)timeout
1408  callback:(void (^_Nonnull)(BOOL didTimeout))callback {
1409  dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
1410  dispatch_group_t group = dispatch_group_create();
1411 
1412  __weak FlutterEngine* weakSelf = self;
1413  __block BOOL didTimeout = NO;
1414  dispatch_group_async(group, queue, ^{
1415  FlutterEngine* strongSelf = weakSelf;
1416  if (!strongSelf) {
1417  return;
1418  }
1419 
1420  fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000);
1421  fml::Status status = strongSelf.shell.WaitForFirstFrame(waitTime);
1422  didTimeout = status.code() == fml::StatusCode::kDeadlineExceeded;
1423  });
1424 
1425  // Only execute the main queue task once the background task has completely finished executing.
1426  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
1427  // Strongly capture self on the task dispatched to the main thread.
1428  //
1429  // When we capture weakSelf strongly in the above block on a background thread, we risk the
1430  // possibility that all other strong references to FlutterEngine go out of scope while the block
1431  // executes and that the engine is dealloc'ed at the end of the above block on a background
1432  // thread. FlutterEngine is not safe to release on any thread other than the main thread.
1433  //
1434  // self is never nil here since it's a strong reference that's verified non-nil above, but we
1435  // use a conditional check to avoid an unused expression compiler warning.
1436  FlutterEngine* strongSelf = self;
1437  if (!strongSelf) {
1438  return;
1439  }
1440  callback(didTimeout);
1441  });
1442 }
1443 
1444 - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint
1445  libraryURI:(/*nullable*/ NSString*)libraryURI
1446  initialRoute:(/*nullable*/ NSString*)initialRoute
1447  entrypointArgs:(/*nullable*/ NSArray<NSString*>*)entrypointArgs {
1448  NSAssert(_shell, @"Spawning from an engine without a shell (possibly not run).");
1449  FlutterEngine* result = [[FlutterEngine alloc] initWithName:self.labelPrefix
1450  project:self.dartProject
1451  allowHeadlessExecution:self.allowHeadlessExecution];
1452  flutter::RunConfiguration configuration =
1453  [self.dartProject runConfigurationForEntrypoint:entrypoint
1454  libraryOrNil:libraryURI
1455  entrypointArgs:entrypointArgs];
1456 
1457  configuration.SetEngineId(result.engineIdentifier);
1458 
1459  fml::WeakPtr<flutter::PlatformView> platform_view = _shell->GetPlatformView();
1460  FML_DCHECK(platform_view);
1461  // Static-cast safe since this class always creates PlatformViewIOS instances.
1462  flutter::PlatformViewIOS* ios_platform_view =
1463  static_cast<flutter::PlatformViewIOS*>(platform_view.get());
1464  std::shared_ptr<flutter::IOSContext> context = ios_platform_view->GetIosContext();
1465  FML_DCHECK(context);
1466 
1467  // Lambda captures by pointers to ObjC objects are fine here because the
1468  // create call is synchronous.
1469  flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
1470  [result, context](flutter::Shell& shell) {
1471  [result recreatePlatformViewsController];
1472  result.platformViewsController.taskRunner = shell.GetTaskRunners().GetPlatformTaskRunner();
1473  return std::make_unique<flutter::PlatformViewIOS>(
1474  shell, context, result.platformViewsController, shell.GetTaskRunners());
1475  };
1476 
1477  flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
1478  [](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };
1479 
1480  std::string cppInitialRoute;
1481  if (initialRoute) {
1482  cppInitialRoute = [initialRoute UTF8String];
1483  }
1484 
1485  std::unique_ptr<flutter::Shell> shell = _shell->Spawn(
1486  std::move(configuration), cppInitialRoute, on_create_platform_view, on_create_rasterizer);
1487 
1488  result->_threadHost = _threadHost;
1489  result->_profiler = _profiler;
1490  result->_isGpuDisabled = _isGpuDisabled;
1491  [result setUpShell:std::move(shell) withVMServicePublication:NO];
1492  return result;
1493 }
1494 
1495 - (const flutter::ThreadHost&)threadHost {
1496  return *_threadHost;
1497 }
1498 
1499 - (FlutterDartProject*)project {
1500  return self.dartProject;
1501 }
1502 
1503 @end
1504 
1505 @implementation FlutterEngineRegistrar {
1506  NSString* _pluginKey;
1507 }
1508 
1509 - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {
1510  self = [super init];
1511  NSAssert(self, @"Super init cannot be nil");
1512  _pluginKey = [pluginKey copy];
1513  _flutterEngine = flutterEngine;
1514  return self;
1515 }
1516 
1517 - (NSObject<FlutterBinaryMessenger>*)messenger {
1518  return _flutterEngine.binaryMessenger;
1519 }
1520 
1521 - (NSObject<FlutterTextureRegistry>*)textures {
1522  return _flutterEngine.textureRegistry;
1523 }
1524 
1525 - (nullable UIViewController*)viewController {
1526  return _flutterEngine.viewController;
1527 }
1528 
1529 - (void)publish:(NSObject*)value {
1530  _flutterEngine.pluginPublications[_pluginKey] = value;
1531 }
1532 
1533 - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
1534  channel:(FlutterMethodChannel*)channel {
1535  [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
1536  [delegate handleMethodCall:call result:result];
1537  }];
1538 }
1539 
1540 - (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
1541  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") {
1542  id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
1543  if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
1544  id<FlutterAppLifeCycleProvider> lifeCycleProvider =
1545  (id<FlutterAppLifeCycleProvider>)appDelegate;
1546  [lifeCycleProvider addApplicationLifeCycleDelegate:delegate];
1547  }
1548 }
1549 
1550 - (NSString*)lookupKeyForAsset:(NSString*)asset {
1551  return [_flutterEngine lookupKeyForAsset:asset];
1552 }
1553 
1554 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
1555  return [_flutterEngine lookupKeyForAsset:asset fromPackage:package];
1556 }
1557 
1558 - (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
1559  withId:(NSString*)factoryId {
1560  [self registerViewFactory:factory
1561  withId:factoryId
1562  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1563 }
1564 
1565 - (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
1566  withId:(NSString*)factoryId
1567  gestureRecognizersBlockingPolicy:
1568  (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy {
1569  [_flutterEngine.platformViewsController registerViewFactory:factory
1570  withId:factoryId
1571  gestureRecognizersBlockingPolicy:gestureRecognizersBlockingPolicy];
1572 }
1573 
1574 @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)