1 | // Copyright 2014 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 | /// @docImport 'dart:ui'; |
6 | /// |
7 | /// @docImport 'package:flutter/widgets.dart'; |
8 | /// |
9 | /// @docImport 'system_chrome.dart'; |
10 | library; |
11 | |
12 | import 'dart:async'; |
13 | import 'dart:convert'; |
14 | import 'dart:io'; |
15 | import 'dart:ui' as ui; |
16 | |
17 | import 'package:flutter/foundation.dart'; |
18 | import 'package:flutter/scheduler.dart'; |
19 | |
20 | import 'asset_bundle.dart'; |
21 | import 'binary_messenger.dart'; |
22 | import 'debug.dart'; |
23 | import 'hardware_keyboard.dart'; |
24 | import 'message_codec.dart'; |
25 | import 'platform_channel.dart'; |
26 | import 'raw_keyboard.dart' show RawKeyboard; |
27 | import 'restoration.dart'; |
28 | import 'service_extensions.dart'; |
29 | import 'system_channels.dart'; |
30 | import 'system_chrome.dart'; |
31 | import 'text_input.dart'; |
32 | |
33 | export 'dart:ui' show ChannelBuffers, RootIsolateToken; |
34 | |
35 | export 'binary_messenger.dart' show BinaryMessenger; |
36 | export 'hardware_keyboard.dart' show HardwareKeyboard, KeyEventManager; |
37 | export 'restoration.dart' show RestorationManager; |
38 | |
39 | /// Listens for platform messages and directs them to the [defaultBinaryMessenger]. |
40 | /// |
41 | /// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes |
42 | /// the licenses found in the `LICENSE` file stored at the root of the asset |
43 | /// bundle, and implements the `ext.flutter.evict` service extension (see |
44 | /// [evict]). |
45 | mixin ServicesBinding on BindingBase, SchedulerBinding { |
46 | @override |
47 | void initInstances() { |
48 | super.initInstances(); |
49 | _instance = this; |
50 | _defaultBinaryMessenger = createBinaryMessenger(); |
51 | _restorationManager = createRestorationManager(); |
52 | _initKeyboard(); |
53 | initLicenses(); |
54 | SystemChannels.system.setMessageHandler( |
55 | (dynamic message) => handleSystemMessage(message as Object), |
56 | ); |
57 | SystemChannels.accessibility.setMessageHandler( |
58 | (dynamic message) => _handleAccessibilityMessage(message as Object), |
59 | ); |
60 | SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage); |
61 | SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage); |
62 | platformDispatcher.onViewFocusChange = handleViewFocusChanged; |
63 | TextInput.ensureInitialized(); |
64 | readInitialLifecycleStateFromNativeWindow(); |
65 | initializationComplete(); |
66 | } |
67 | |
68 | /// The current [ServicesBinding], if one has been created. |
69 | /// |
70 | /// Provides access to the features exposed by this mixin. The binding must |
71 | /// be initialized before using this getter; this is typically done by calling |
72 | /// [runApp] or [WidgetsFlutterBinding.ensureInitialized]. |
73 | static ServicesBinding get instance => BindingBase.checkInstance(_instance); |
74 | static ServicesBinding? _instance; |
75 | |
76 | /// The global singleton instance of [HardwareKeyboard], which can be used to |
77 | /// query keyboard states. |
78 | HardwareKeyboard get keyboard => _keyboard; |
79 | late final HardwareKeyboard _keyboard; |
80 | |
81 | /// The global singleton instance of [KeyEventManager], which is used |
82 | /// internally to dispatch key messages. |
83 | /// |
84 | /// This property is deprecated, and will be removed. See |
85 | /// [HardwareKeyboard.addHandler] instead. |
86 | @Deprecated( |
87 | 'No longer supported. Add a handler to HardwareKeyboard instead. ' |
88 | 'This feature was deprecated after v3.18.0-2.0.pre.', |
89 | ) |
90 | KeyEventManager get keyEventManager => _keyEventManager; |
91 | late final KeyEventManager _keyEventManager; |
92 | |
93 | void _initKeyboard() { |
94 | _keyboard = HardwareKeyboard(); |
95 | _keyEventManager = KeyEventManager(_keyboard, RawKeyboard.instance); |
96 | _keyboard.syncKeyboardState().then((_) { |
97 | platformDispatcher.onKeyData = _keyEventManager.handleKeyData; |
98 | SystemChannels.keyEvent.setMessageHandler(_keyEventManager.handleRawKeyMessage); |
99 | }); |
100 | } |
101 | |
102 | /// The default instance of [BinaryMessenger]. |
103 | /// |
104 | /// This is used to send messages from the application to the platform, and |
105 | /// keeps track of which handlers have been registered on each channel so |
106 | /// it may dispatch incoming messages to the registered handler. |
107 | /// |
108 | /// The default implementation returns a [BinaryMessenger] that delivers the |
109 | /// messages in the same order in which they are sent. |
110 | BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger; |
111 | late final BinaryMessenger _defaultBinaryMessenger; |
112 | |
113 | /// A token that represents the root isolate, used for coordinating with |
114 | /// background isolates. |
115 | /// |
116 | /// This property is primarily intended for use with |
117 | /// [BackgroundIsolateBinaryMessenger.ensureInitialized], which takes a |
118 | /// [RootIsolateToken] as its argument. The value `null` is returned when |
119 | /// executed from background isolates. |
120 | static ui.RootIsolateToken? get rootIsolateToken => ui.RootIsolateToken.instance; |
121 | |
122 | /// The low level buffering and dispatch mechanism for messages sent by |
123 | /// plugins on the engine side to their corresponding plugin code on |
124 | /// the framework side. |
125 | /// |
126 | /// This exposes the [dart:ui.channelBuffers] object. Bindings can override |
127 | /// this getter to intercept calls to the [ChannelBuffers] mechanism (for |
128 | /// example, for tests). |
129 | /// |
130 | /// In production, direct access to this object should not be necessary. |
131 | /// Messages are received and dispatched by the [defaultBinaryMessenger]. This |
132 | /// object is primarily used to send mock messages in tests, via the |
133 | /// [ChannelBuffers.push] method (simulating a plugin sending a message to the |
134 | /// framework). |
135 | /// |
136 | /// See also: |
137 | /// |
138 | /// * [PlatformDispatcher.sendPlatformMessage], which is used for sending |
139 | /// messages to plugins from the framework (the opposite of |
140 | /// [channelBuffers]). |
141 | /// * [platformDispatcher], the [PlatformDispatcher] singleton. |
142 | ui.ChannelBuffers get channelBuffers => ui.channelBuffers; |
143 | |
144 | /// Creates a default [BinaryMessenger] instance that can be used for sending |
145 | /// platform messages. |
146 | /// |
147 | /// Many Flutter framework components that communicate with the platform |
148 | /// assume messages are received by the platform in the same order in which |
149 | /// they are sent. When overriding this method, be sure the [BinaryMessenger] |
150 | /// implementation guarantees FIFO delivery. |
151 | @protected |
152 | BinaryMessenger createBinaryMessenger() { |
153 | return const _DefaultBinaryMessenger._(); |
154 | } |
155 | |
156 | /// Called when the operating system notifies the application of a memory |
157 | /// pressure situation. |
158 | /// |
159 | /// This method exposes the `memoryPressure` notification from |
160 | /// [SystemChannels.system]. |
161 | @protected |
162 | @mustCallSuper |
163 | void handleMemoryPressure() { |
164 | rootBundle.clear(); |
165 | } |
166 | |
167 | /// Handler called for messages received on the [SystemChannels.system] |
168 | /// message channel. |
169 | /// |
170 | /// Other bindings may override this to respond to incoming system messages. |
171 | @protected |
172 | @mustCallSuper |
173 | Future<void> handleSystemMessage(Object systemMessage) async { |
174 | final Map<String, dynamic> message = systemMessage as Map<String, dynamic>; |
175 | final String type = message['type'] as String; |
176 | switch (type) { |
177 | case 'memoryPressure': |
178 | handleMemoryPressure(); |
179 | } |
180 | return; |
181 | } |
182 | |
183 | /// Adds relevant licenses to the [LicenseRegistry]. |
184 | /// |
185 | /// By default, the [ServicesBinding]'s implementation of [initLicenses] adds |
186 | /// all the licenses collected by the `flutter` tool during compilation. |
187 | @protected |
188 | @mustCallSuper |
189 | void initLicenses() { |
190 | LicenseRegistry.addLicense(_addLicenses); |
191 | } |
192 | |
193 | Stream<LicenseEntry> _addLicenses() { |
194 | late final StreamController<LicenseEntry> controller; |
195 | controller = StreamController<LicenseEntry>( |
196 | onListen: () async { |
197 | late final String rawLicenses; |
198 | if (kIsWeb) { |
199 | // NOTICES for web isn't compressed since we don't have access to |
200 | // dart:io on the client side and it's already compressed between |
201 | // the server and client. |
202 | rawLicenses = await rootBundle.loadString('NOTICES', cache: false); |
203 | } else { |
204 | // The compressed version doesn't have a more common .gz extension |
205 | // because gradle for Android non-transparently manipulates .gz files. |
206 | final ByteData licenseBytes = await rootBundle.load('NOTICES.Z'); |
207 | final List<int> unzippedBytes = await compute<List<int>, List<int>>( |
208 | gzip.decode, |
209 | licenseBytes.buffer.asUint8List(), |
210 | debugLabel: 'decompressLicenses', |
211 | ); |
212 | rawLicenses = await compute<List<int>, String>( |
213 | utf8.decode, |
214 | unzippedBytes, |
215 | debugLabel: 'utf8DecodeLicenses', |
216 | ); |
217 | } |
218 | final List<LicenseEntry> licenses = await compute<String, List<LicenseEntry>>( |
219 | _parseLicenses, |
220 | rawLicenses, |
221 | debugLabel: 'parseLicenses', |
222 | ); |
223 | licenses.forEach(controller.add); |
224 | await controller.close(); |
225 | }, |
226 | ); |
227 | return controller.stream; |
228 | } |
229 | |
230 | // This is run in another isolate created by _addLicenses above. |
231 | static List<LicenseEntry> _parseLicenses(String rawLicenses) { |
232 | final String licenseSeparator = '\n${ '-'* 80} \n'; |
233 | return <LicenseEntry>[ |
234 | for (final String license in rawLicenses.split(licenseSeparator)) |
235 | if (license.indexOf('\n\n') case final int split when split >= 0) |
236 | LicenseEntryWithLineBreaks( |
237 | license.substring(0, split).split('\n'), |
238 | license.substring(split + 2), |
239 | ) |
240 | else |
241 | LicenseEntryWithLineBreaks(const <String>[], license), |
242 | ]; |
243 | } |
244 | |
245 | @override |
246 | void initServiceExtensions() { |
247 | super.initServiceExtensions(); |
248 | |
249 | assert(() { |
250 | registerStringServiceExtension( |
251 | name: ServicesServiceExtensions.evict.name, |
252 | getter: () async => '', |
253 | setter: (String value) async { |
254 | evict(value); |
255 | }, |
256 | ); |
257 | return true; |
258 | }()); |
259 | |
260 | if (!kReleaseMode) { |
261 | registerBoolServiceExtension( |
262 | name: ServicesServiceExtensions.profilePlatformChannels.name, |
263 | getter: () async => debugProfilePlatformChannels, |
264 | setter: (bool value) async { |
265 | debugProfilePlatformChannels = value; |
266 | }, |
267 | ); |
268 | } |
269 | } |
270 | |
271 | /// Called in response to the `ext.flutter.evict` service extension. |
272 | /// |
273 | /// This is used by the `flutter` tool during hot reload so that any images |
274 | /// that have changed on disk get cleared from caches. |
275 | @protected |
276 | @mustCallSuper |
277 | void evict(String asset) { |
278 | rootBundle.evict(asset); |
279 | } |
280 | |
281 | // App life cycle |
282 | |
283 | /// Initializes the [lifecycleState] with the |
284 | /// [dart:ui.PlatformDispatcher.initialLifecycleState]. |
285 | /// |
286 | /// Once the [lifecycleState] is populated through any means (including this |
287 | /// method), this method will do nothing. This is because the |
288 | /// [dart:ui.PlatformDispatcher.initialLifecycleState] may already be stale |
289 | /// and it no longer makes sense to use the initial state at dart vm startup |
290 | /// as the current state anymore. |
291 | /// |
292 | /// The latest state should be obtained by subscribing to |
293 | /// [WidgetsBindingObserver.didChangeAppLifecycleState]. |
294 | @protected |
295 | void readInitialLifecycleStateFromNativeWindow() { |
296 | if (lifecycleState != null || platformDispatcher.initialLifecycleState.isEmpty) { |
297 | return; |
298 | } |
299 | _handleLifecycleMessage(platformDispatcher.initialLifecycleState); |
300 | } |
301 | |
302 | Future<String?> _handleLifecycleMessage(String? message) async { |
303 | final AppLifecycleState? state = _parseAppLifecycleMessage(message!); |
304 | final List<AppLifecycleState> generated = _generateStateTransitions(lifecycleState, state!); |
305 | for (final AppLifecycleState stateChange in generated) { |
306 | handleAppLifecycleStateChanged(stateChange); |
307 | SystemChrome.handleAppLifecycleStateChanged(stateChange); |
308 | } |
309 | return null; |
310 | } |
311 | |
312 | List<AppLifecycleState> _generateStateTransitions( |
313 | AppLifecycleState? previousState, |
314 | AppLifecycleState state, |
315 | ) { |
316 | if (previousState == state) { |
317 | return const <AppLifecycleState>[]; |
318 | } |
319 | final List<AppLifecycleState> stateChanges = <AppLifecycleState>[]; |
320 | if (previousState == null) { |
321 | // If there was no previous state, just jump directly to the new state. |
322 | stateChanges.add(state); |
323 | } else { |
324 | final int previousStateIndex = AppLifecycleState.values.indexOf(previousState); |
325 | final int stateIndex = AppLifecycleState.values.indexOf(state); |
326 | assert(previousStateIndex != -1, 'State$previousState missing in stateOrder array'); |
327 | assert(stateIndex != -1, 'State$state missing in stateOrder array'); |
328 | if (state == AppLifecycleState.detached) { |
329 | for (int i = previousStateIndex + 1; i < AppLifecycleState.values.length; ++i) { |
330 | stateChanges.add(AppLifecycleState.values[i]); |
331 | } |
332 | stateChanges.add(AppLifecycleState.detached); |
333 | } else if (previousStateIndex > stateIndex) { |
334 | for (int i = stateIndex; i < previousStateIndex; ++i) { |
335 | stateChanges.insert(0, AppLifecycleState.values[i]); |
336 | } |
337 | } else { |
338 | for (int i = previousStateIndex + 1; i <= stateIndex; ++i) { |
339 | stateChanges.add(AppLifecycleState.values[i]); |
340 | } |
341 | } |
342 | } |
343 | assert( |
344 | () { |
345 | AppLifecycleState? starting = previousState; |
346 | for (final AppLifecycleState ending in stateChanges) { |
347 | if (!_debugVerifyLifecycleChange(starting, ending)) { |
348 | return false; |
349 | } |
350 | starting = ending; |
351 | } |
352 | return true; |
353 | }(), |
354 | 'Invalid lifecycle state transition generated from$previousState to$state (generated$stateChanges )', |
355 | ); |
356 | return stateChanges; |
357 | } |
358 | |
359 | static bool _debugVerifyLifecycleChange(AppLifecycleState? starting, AppLifecycleState ending) { |
360 | if (starting == null) { |
361 | // Any transition from null is fine, since it is initializing the state. |
362 | return true; |
363 | } |
364 | if (starting == ending) { |
365 | // Any transition to itself shouldn't happen. |
366 | return false; |
367 | } |
368 | return switch (starting) { |
369 | // Can't go from resumed to detached directly (must go through paused). |
370 | AppLifecycleState.resumed => ending == AppLifecycleState.inactive, |
371 | AppLifecycleState.detached => |
372 | ending == AppLifecycleState.resumed || ending == AppLifecycleState.paused, |
373 | AppLifecycleState.inactive => |
374 | ending == AppLifecycleState.resumed || ending == AppLifecycleState.hidden, |
375 | AppLifecycleState.hidden => |
376 | ending == AppLifecycleState.paused || ending == AppLifecycleState.inactive, |
377 | AppLifecycleState.paused => |
378 | ending == AppLifecycleState.hidden || ending == AppLifecycleState.detached, |
379 | }; |
380 | } |
381 | |
382 | /// Listenable that notifies when the accessibility focus on the system have changed. |
383 | final ValueNotifier<int?> accessibilityFocus = ValueNotifier<int?>(null); |
384 | |
385 | Future<void> _handleAccessibilityMessage(Object accessibilityMessage) async { |
386 | final Map<String, dynamic> message = |
387 | (accessibilityMessage as Map<Object?, Object?>).cast<String, dynamic>(); |
388 | final String type = message['type'] as String; |
389 | switch (type) { |
390 | case 'didGainFocus': |
391 | accessibilityFocus.value = message['nodeId'] as int; |
392 | } |
393 | return; |
394 | } |
395 | |
396 | /// Called whenever the [PlatformDispatcher] receives a notification that the |
397 | /// focus state on a view has changed. |
398 | /// |
399 | /// The [event] contains the view ID for the view that changed its focus |
400 | /// state. |
401 | /// |
402 | /// See also: |
403 | /// |
404 | /// * [PlatformDispatcher.onViewFocusChange], which calls this method. |
405 | @protected |
406 | @mustCallSuper |
407 | void handleViewFocusChanged(ui.ViewFocusEvent event) {} |
408 | |
409 | Future<dynamic> _handlePlatformMessage(MethodCall methodCall) async { |
410 | final String method = methodCall.method; |
411 | switch (method) { |
412 | // Called when the system dismisses the system context menu, such as when |
413 | // the user taps outside the menu. Not called when Flutter shows a new |
414 | // system context menu while an old one is still visible. |
415 | case 'ContextMenu.onDismissSystemContextMenu': |
416 | if (_systemContextMenuClient == null) { |
417 | assert( |
418 | false, |
419 | 'Platform sent onDismissSystemContextMenu when no SystemContextMenuClient was registered.', |
420 | ); |
421 | return; |
422 | } |
423 | _systemContextMenuClient!.handleSystemHide(); |
424 | _systemContextMenuClient = null; |
425 | case 'SystemChrome.systemUIChange': |
426 | final List<dynamic> args = methodCall.arguments as List<dynamic>; |
427 | if (_systemUiChangeCallback != null) { |
428 | await _systemUiChangeCallback!(args[0] as bool); |
429 | } |
430 | case 'System.requestAppExit': |
431 | return <String, dynamic>{'response': (await handleRequestAppExit()).name}; |
432 | default: |
433 | throw AssertionError('Method "$method " not handled.'); |
434 | } |
435 | } |
436 | |
437 | static AppLifecycleState? _parseAppLifecycleMessage(String message) { |
438 | return switch (message) { |
439 | 'AppLifecycleState.resumed'=> AppLifecycleState.resumed, |
440 | 'AppLifecycleState.inactive'=> AppLifecycleState.inactive, |
441 | 'AppLifecycleState.hidden'=> AppLifecycleState.hidden, |
442 | 'AppLifecycleState.paused'=> AppLifecycleState.paused, |
443 | 'AppLifecycleState.detached'=> AppLifecycleState.detached, |
444 | _ => null, |
445 | }; |
446 | } |
447 | |
448 | /// Handles any requests for application exit that may be received on the |
449 | /// [SystemChannels.platform] method channel. |
450 | /// |
451 | /// By default, returns [ui.AppExitResponse.exit]. |
452 | /// |
453 | /// {@template flutter.services.binding.ServicesBinding.requestAppExit} |
454 | /// Not all exits are cancelable, so not all exits will call this function. Do |
455 | /// not rely on this function as a place to save critical data, because you |
456 | /// will be disappointed. There are a number of ways that the application can |
457 | /// exit without letting the application know first: power can be unplugged, |
458 | /// the battery removed, the application can be killed in a task manager or |
459 | /// command line, or the device could have a rapid unplanned disassembly (i.e. |
460 | /// it could explode). In all of those cases (and probably others), no |
461 | /// notification will be given to the application that it is about to exit. |
462 | /// {@endtemplate} |
463 | /// |
464 | /// {@tool sample} |
465 | /// This examples shows how an application can cancel (or not) OS requests for |
466 | /// quitting an application. Currently this is only supported on macOS and |
467 | /// Linux. |
468 | /// |
469 | /// ** See code in examples/api/lib/services/binding/handle_request_app_exit.0.dart ** |
470 | /// {@end-tool} |
471 | /// |
472 | /// See also: |
473 | /// |
474 | /// * [WidgetsBindingObserver.didRequestAppExit], which can be overridden to |
475 | /// respond to this message. |
476 | /// * [WidgetsBinding.handleRequestAppExit] which overrides this method to |
477 | /// notify its observers. |
478 | Future<ui.AppExitResponse> handleRequestAppExit() async { |
479 | return ui.AppExitResponse.exit; |
480 | } |
481 | |
482 | /// Exits the application by calling the native application API method for |
483 | /// exiting an application cleanly. |
484 | /// |
485 | /// This differs from calling `dart:io`'s [exit] function in that it gives the |
486 | /// engine a chance to clean up resources so that it doesn't crash on exit, so |
487 | /// calling this is always preferred over calling [exit]. It also optionally |
488 | /// gives handlers of [handleRequestAppExit] a chance to cancel the |
489 | /// application exit. |
490 | /// |
491 | /// The [exitType] indicates what kind of exit to perform. For |
492 | /// [ui.AppExitType.cancelable] exits, the application is queried through a |
493 | /// call to [handleRequestAppExit], where the application can optionally |
494 | /// cancel the request for exit. If the [exitType] is |
495 | /// [ui.AppExitType.required], then the application exits immediately without |
496 | /// querying the application. |
497 | /// |
498 | /// For [ui.AppExitType.cancelable] exits, the returned response value is the |
499 | /// response obtained from the application as to whether the exit was canceled |
500 | /// or not. Practically, the response will never be [ui.AppExitResponse.exit], |
501 | /// since the application will have already exited by the time the result |
502 | /// would have been received. |
503 | /// |
504 | /// The optional [exitCode] argument will be used as the application exit code |
505 | /// on platforms where an exit code is supported. On other platforms it may be |
506 | /// ignored. It defaults to zero. |
507 | /// |
508 | /// See also: |
509 | /// |
510 | /// * [WidgetsBindingObserver.didRequestAppExit] for a handler you can |
511 | /// override on a [WidgetsBindingObserver] to receive exit requests. |
512 | Future<ui.AppExitResponse> exitApplication(ui.AppExitType exitType, [int exitCode = 0]) async { |
513 | final Map<String, Object?>? result = await SystemChannels.platform |
514 | .invokeMethod<Map<String, Object?>>('System.exitApplication', <String, Object?>{ |
515 | 'type': exitType.name, |
516 | 'exitCode': exitCode, |
517 | }); |
518 | if (result == null) { |
519 | return ui.AppExitResponse.cancel; |
520 | } |
521 | switch (result['response']) { |
522 | case 'cancel': |
523 | return ui.AppExitResponse.cancel; |
524 | case 'exit': |
525 | default: |
526 | // In practice, this will never get returned, because the application |
527 | // will have exited before it returns. |
528 | return ui.AppExitResponse.exit; |
529 | } |
530 | } |
531 | |
532 | /// The [RestorationManager] synchronizes the restoration data between |
533 | /// engine and framework. |
534 | /// |
535 | /// See the docs for [RestorationManager] for a discussion of restoration |
536 | /// state and how it is organized in Flutter. |
537 | /// |
538 | /// To use a different [RestorationManager] subclasses can override |
539 | /// [createRestorationManager], which is called to create the instance |
540 | /// returned by this getter. |
541 | RestorationManager get restorationManager => _restorationManager; |
542 | late RestorationManager _restorationManager; |
543 | |
544 | /// Creates the [RestorationManager] instance available via |
545 | /// [restorationManager]. |
546 | /// |
547 | /// Can be overridden in subclasses to create a different [RestorationManager]. |
548 | @protected |
549 | RestorationManager createRestorationManager() { |
550 | return RestorationManager(); |
551 | } |
552 | |
553 | SystemUiChangeCallback? _systemUiChangeCallback; |
554 | |
555 | /// Sets the callback for the `SystemChrome.systemUIChange` method call |
556 | /// received on the [SystemChannels.platform] channel. |
557 | /// |
558 | /// This is typically not called directly. System UI changes that this method |
559 | /// responds to are associated with [SystemUiMode]s, which are configured |
560 | /// using [SystemChrome]. Use [SystemChrome.setSystemUIChangeCallback] to configure |
561 | /// along with other SystemChrome settings. |
562 | /// |
563 | /// See also: |
564 | /// |
565 | /// * [SystemChrome.setEnabledSystemUIMode], which specifies the |
566 | /// [SystemUiMode] to have visible when the application is running. |
567 | // ignore: use_setters_to_change_properties, (API predates enforcing the lint) |
568 | void setSystemUiChangeCallback(SystemUiChangeCallback? callback) { |
569 | _systemUiChangeCallback = callback; |
570 | } |
571 | |
572 | /// Alert the engine that the binding is registered. This instructs the engine to |
573 | /// register its top level window handler on Windows. This signals that the app |
574 | /// is able to process "System.requestAppExit" signals from the engine. |
575 | @protected |
576 | Future<void> initializationComplete() async { |
577 | await SystemChannels.platform.invokeMethod('System.initializationComplete'); |
578 | } |
579 | |
580 | SystemContextMenuClient? _systemContextMenuClient; |
581 | |
582 | /// Registers a [SystemContextMenuClient] that will receive system context |
583 | /// menu calls from the engine. |
584 | /// |
585 | /// To unregister, set to null. |
586 | static set systemContextMenuClient(SystemContextMenuClient? client) { |
587 | instance._systemContextMenuClient = client; |
588 | } |
589 | } |
590 | |
591 | /// Signature for listening to changes in the [SystemUiMode]. |
592 | /// |
593 | /// Set by [SystemChrome.setSystemUIChangeCallback]. |
594 | typedef SystemUiChangeCallback = Future<void> Function(bool systemOverlaysAreVisible); |
595 | |
596 | /// The default implementation of [BinaryMessenger]. |
597 | /// |
598 | /// This messenger sends messages from the app-side to the platform-side and |
599 | /// dispatches incoming messages from the platform-side to the appropriate |
600 | /// handler. |
601 | class _DefaultBinaryMessenger extends BinaryMessenger { |
602 | const _DefaultBinaryMessenger._(); |
603 | |
604 | @override |
605 | Future<void> handlePlatformMessage( |
606 | String channel, |
607 | ByteData? message, |
608 | ui.PlatformMessageResponseCallback? callback, |
609 | ) async { |
610 | ui.channelBuffers.push(channel, message, (ByteData? data) => callback?.call(data)); |
611 | } |
612 | |
613 | @override |
614 | Future<ByteData?> send(String channel, ByteData? message) { |
615 | final Completer<ByteData?> completer = Completer<ByteData?>(); |
616 | // ui.PlatformDispatcher.instance is accessed directly instead of using |
617 | // ServicesBinding.instance.platformDispatcher because this method might be |
618 | // invoked before any binding is initialized. This issue was reported in |
619 | // #27541. It is not ideal to statically access |
620 | // ui.PlatformDispatcher.instance because the PlatformDispatcher may be |
621 | // dependency injected elsewhere with a different instance. However, static |
622 | // access at this location seems to be the least bad option. |
623 | // TODO(ianh): Use ServicesBinding.instance once we have better diagnostics |
624 | // on that getter. |
625 | ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) { |
626 | try { |
627 | completer.complete(reply); |
628 | } catch (exception, stack) { |
629 | FlutterError.reportError( |
630 | FlutterErrorDetails( |
631 | exception: exception, |
632 | stack: stack, |
633 | library: 'services library', |
634 | context: ErrorDescription('during a platform message response callback'), |
635 | ), |
636 | ); |
637 | } |
638 | }); |
639 | return completer.future; |
640 | } |
641 | |
642 | @override |
643 | void setMessageHandler(String channel, MessageHandler? handler) { |
644 | if (handler == null) { |
645 | ui.channelBuffers.clearListener(channel); |
646 | } else { |
647 | ui.channelBuffers.setListener(channel, ( |
648 | ByteData? data, |
649 | ui.PlatformMessageResponseCallback callback, |
650 | ) async { |
651 | ByteData? response; |
652 | try { |
653 | response = await handler(data); |
654 | } catch (exception, stack) { |
655 | FlutterError.reportError( |
656 | FlutterErrorDetails( |
657 | exception: exception, |
658 | stack: stack, |
659 | library: 'services library', |
660 | context: ErrorDescription('during a platform message callback'), |
661 | ), |
662 | ); |
663 | } finally { |
664 | callback(response); |
665 | } |
666 | }); |
667 | } |
668 | } |
669 | } |
670 | |
671 | /// An interface to receive calls related to the system context menu from the |
672 | /// engine. |
673 | /// |
674 | /// Currently this is only supported on iOS 16+. |
675 | /// |
676 | /// See also: |
677 | /// * [SystemContextMenuController], which uses this to provide a fully |
678 | /// featured way to control the system context menu. |
679 | /// * [ServicesBinding.systemContextMenuClient], which can be set to a |
680 | /// [SystemContextMenuClient] to register it to receive events, or null to |
681 | /// unregister. |
682 | /// * [MediaQuery.maybeSupportsShowingSystemContextMenu], which indicates |
683 | /// whether the system context menu is supported. |
684 | /// * [SystemContextMenu], which provides a widget interface for displaying the |
685 | /// system context menu. |
686 | mixin SystemContextMenuClient { |
687 | /// Handles the system hiding a context menu. |
688 | /// |
689 | /// Called only on the single active instance registered with |
690 | /// [ServicesBinding.systemContextMenuClient]. |
691 | void handleSystemHide(); |
692 | } |
693 |
Definitions
- ServicesBinding
- initInstances
- instance
- keyboard
- keyEventManager
- _initKeyboard
- defaultBinaryMessenger
- rootIsolateToken
- channelBuffers
- createBinaryMessenger
- handleMemoryPressure
- handleSystemMessage
- initLicenses
- _addLicenses
- _parseLicenses
- initServiceExtensions
- evict
- readInitialLifecycleStateFromNativeWindow
- _handleLifecycleMessage
- _generateStateTransitions
- _debugVerifyLifecycleChange
- _handleAccessibilityMessage
- handleViewFocusChanged
- _handlePlatformMessage
- _parseAppLifecycleMessage
- handleRequestAppExit
- exitApplication
- restorationManager
- createRestorationManager
- setSystemUiChangeCallback
- initializationComplete
- systemContextMenuClient
- _DefaultBinaryMessenger
- _
- handlePlatformMessage
- send
- setMessageHandler
- SystemContextMenuClient
Learn more about Flutter for embedded and desktop on industrialflutter.com