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 'package:flutter/material.dart';
6///
7/// @docImport 'message_codecs.dart';
8library;
9
10import 'dart:async';
11import 'dart:ui';
12
13import 'package:flutter/foundation.dart';
14import 'package:flutter/gestures.dart';
15
16import 'message_codec.dart';
17import 'system_channels.dart';
18
19export 'dart:ui' show Offset, Size, TextDirection, VoidCallback;
20
21export 'package:flutter/gestures.dart' show PointerEvent;
22
23export 'message_codec.dart' show MessageCodec;
24
25/// Converts a given point from the global coordinate system in logical pixels
26/// to the local coordinate system for a box.
27///
28/// Used by [AndroidViewController.pointTransformer].
29typedef PointTransformer = Offset Function(Offset position);
30
31/// The [PlatformViewsRegistry] responsible for generating unique identifiers for platform views.
32final PlatformViewsRegistry platformViewsRegistry = PlatformViewsRegistry._instance();
33
34/// A registry responsible for generating unique identifier for platform views.
35///
36/// A Flutter application has a single [PlatformViewsRegistry] which can be accesses
37/// through the [platformViewsRegistry] getter.
38class PlatformViewsRegistry {
39 PlatformViewsRegistry._instance();
40
41 // Always non-negative. The id value -1 is used in the accessibility bridge
42 // to indicate the absence of a platform view.
43 int _nextPlatformViewId = 0;
44
45 /// Allocates a unique identifier for a platform view.
46 ///
47 /// A platform view identifier can refer to a platform view that was never created,
48 /// a platform view that was disposed, or a platform view that is alive.
49 ///
50 /// Typically a platform view identifier is passed to a platform view widget
51 /// which creates the platform view and manages its lifecycle.
52 int getNextPlatformViewId() {
53 // On the Android side, the interface exposed to users uses 32-bit integers.
54 // See https://github.com/flutter/engine/pull/39476 for more details.
55
56 // We can safely assume that a Flutter application will not require more
57 // than MAX_INT32 platform views during its lifetime.
58 const int MAX_INT32 = 0x7FFFFFFF;
59 assert(_nextPlatformViewId <= MAX_INT32);
60 return _nextPlatformViewId++;
61 }
62}
63
64/// Callback signature for when a platform view was created.
65///
66/// The `id` parameter is the platform view's unique identifier.
67typedef PlatformViewCreatedCallback = void Function(int id);
68
69/// Provides access to the platform views service.
70///
71/// This service allows creating and controlling platform-specific views.
72class PlatformViewsService {
73 PlatformViewsService._() {
74 SystemChannels.platform_views.setMethodCallHandler(_onMethodCall);
75 }
76
77 static final PlatformViewsService _instance = PlatformViewsService._();
78
79 Future<void> _onMethodCall(MethodCall call) {
80 switch (call.method) {
81 case 'viewFocused':
82 final int id = call.arguments as int;
83 if (_focusCallbacks.containsKey(id)) {
84 _focusCallbacks[id]!();
85 }
86 default:
87 throw UnimplementedError(
88 "${call.method} was invoked but isn't implemented by PlatformViewsService",
89 );
90 }
91 return Future<void>.value();
92 }
93
94 /// Maps platform view IDs to focus callbacks.
95 ///
96 /// The callbacks are invoked when the platform view asks to be focused.
97 final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{};
98
99 /// {@template flutter.services.PlatformViewsService.initAndroidView}
100 /// Creates a controller for a new Android view.
101 ///
102 /// The `id` argument is an unused unique identifier generated with
103 /// [platformViewsRegistry].
104 ///
105 /// The `viewType` argument is the identifier of the Android view type to be
106 /// created, a factory for this view type must have been registered on the
107 /// platform side. Platform view factories are typically registered by plugin
108 /// code. Plugins can register a platform view factory with
109 /// [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
110 ///
111 /// The `creationParams` argument will be passed as the args argument of
112 /// [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
113 ///
114 /// The `creationParamsCodec` argument is the codec used to encode
115 /// `creationParams` before sending it to the platform side. It should match
116 /// the codec passed to the constructor of
117 /// [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
118 /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec],
119 /// [StringCodec], or [BinaryCodec].
120 ///
121 /// The `onFocus` argument is a callback that will be invoked when the Android
122 /// View asks to get the input focus.
123 ///
124 /// The Android view will only be created after
125 /// [AndroidViewController.setSize] is called for the first time.
126 ///
127 /// If `creationParams` is non null then `creationParamsCodec` must not be
128 /// null.
129 /// {@endtemplate}
130 ///
131 /// This attempts to use the newest and most efficient platform view
132 /// implementation when possible. In cases where that is not supported, it
133 /// falls back to using Virtual Display.
134 static AndroidViewController initAndroidView({
135 required int id,
136 required String viewType,
137 required TextDirection layoutDirection,
138 dynamic creationParams,
139 MessageCodec<dynamic>? creationParamsCodec,
140 VoidCallback? onFocus,
141 }) {
142 assert(creationParams == null || creationParamsCodec != null);
143
144 final TextureAndroidViewController controller = TextureAndroidViewController._(
145 viewId: id,
146 viewType: viewType,
147 layoutDirection: layoutDirection,
148 creationParams: creationParams,
149 creationParamsCodec: creationParamsCodec,
150 );
151
152 _instance._focusCallbacks[id] = onFocus ?? () {};
153 return controller;
154 }
155
156 /// {@macro flutter.services.PlatformViewsService.initAndroidView}
157 ///
158 /// This attempts to use the newest and most efficient platform view
159 /// implementation when possible. In cases where that is not supported, it
160 /// falls back to using Hybrid Composition, which is the mode used by
161 /// [initExpensiveAndroidView].
162 static SurfaceAndroidViewController initSurfaceAndroidView({
163 required int id,
164 required String viewType,
165 required TextDirection layoutDirection,
166 dynamic creationParams,
167 MessageCodec<dynamic>? creationParamsCodec,
168 VoidCallback? onFocus,
169 }) {
170 assert(creationParams == null || creationParamsCodec != null);
171
172 final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
173 viewId: id,
174 viewType: viewType,
175 layoutDirection: layoutDirection,
176 creationParams: creationParams,
177 creationParamsCodec: creationParamsCodec,
178 );
179 _instance._focusCallbacks[id] = onFocus ?? () {};
180 return controller;
181 }
182
183 /// {@macro flutter.services.PlatformViewsService.initAndroidView}
184 ///
185 /// When this factory is used, the Android view and Flutter widgets are
186 /// composed at the Android view hierarchy level.
187 ///
188 /// Using this method has a performance cost on devices running Android 9 or
189 /// earlier, or on underpowered devices. In most situations, you should use
190 /// [initAndroidView] or [initSurfaceAndroidView] instead.
191 static ExpensiveAndroidViewController initExpensiveAndroidView({
192 required int id,
193 required String viewType,
194 required TextDirection layoutDirection,
195 dynamic creationParams,
196 MessageCodec<dynamic>? creationParamsCodec,
197 VoidCallback? onFocus,
198 }) {
199 final ExpensiveAndroidViewController controller = ExpensiveAndroidViewController._(
200 viewId: id,
201 viewType: viewType,
202 layoutDirection: layoutDirection,
203 creationParams: creationParams,
204 creationParamsCodec: creationParamsCodec,
205 );
206
207 _instance._focusCallbacks[id] = onFocus ?? () {};
208 return controller;
209 }
210
211 /// {@macro flutter.services.PlatformViewsService.initAndroidView}
212 ///
213 /// When this factory is used, the Android view and Flutter widgets are
214 /// composed at the Android view hierarchy level.
215 ///
216 /// This functionality is only supported on Android devices running Vulkan on
217 /// API 34 or newer.
218 static HybridAndroidViewController initHybridAndroidView({
219 required int id,
220 required String viewType,
221 required TextDirection layoutDirection,
222 dynamic creationParams,
223 MessageCodec<dynamic>? creationParamsCodec,
224 VoidCallback? onFocus,
225 }) {
226 final HybridAndroidViewController controller = HybridAndroidViewController._(
227 viewId: id,
228 viewType: viewType,
229 layoutDirection: layoutDirection,
230 creationParams: creationParams,
231 creationParamsCodec: creationParamsCodec,
232 );
233
234 _instance._focusCallbacks[id] = onFocus ?? () {};
235 return controller;
236 }
237
238 /// Factory method to create a `UiKitView`.
239 ///
240 /// The `id` parameter is an unused unique identifier generated with
241 /// [platformViewsRegistry].
242 ///
243 /// The `viewType` parameter is the identifier of the iOS view type to be
244 /// created, a factory for this view type must have been registered on the
245 /// platform side. Platform view factories are typically registered by plugin
246 /// code.
247 ///
248 /// The `onFocus` parameter is a callback that will be invoked when the UIKit
249 /// view asks to get the input focus. If `creationParams` is non null then
250 /// `creationParamsCodec` must not be null.
251 ///
252 /// See: https://docs.flutter.dev/platform-integration/ios/platform-views
253 static Future<UiKitViewController> initUiKitView({
254 required int id,
255 required String viewType,
256 required TextDirection layoutDirection,
257 dynamic creationParams,
258 MessageCodec<dynamic>? creationParamsCodec,
259 VoidCallback? onFocus,
260 }) async {
261 assert(creationParams == null || creationParamsCodec != null);
262
263 // TODO(amirh): pass layoutDirection once the system channel supports it.
264 // https://github.com/flutter/flutter/issues/133682
265 final Map<String, dynamic> args = <String, dynamic>{'id': id, 'viewType': viewType};
266 if (creationParams != null) {
267 final ByteData paramsByteData = creationParamsCodec!.encodeMessage(creationParams)!;
268 args['params'] = Uint8List.view(paramsByteData.buffer, 0, paramsByteData.lengthInBytes);
269 }
270 await SystemChannels.platform_views.invokeMethod<void>('create', args);
271 if (onFocus != null) {
272 _instance._focusCallbacks[id] = onFocus;
273 }
274 return UiKitViewController._(id, layoutDirection);
275 }
276
277 // TODO(cbracken): Write and link website docs. https://github.com/flutter/website/issues/9424.
278 //
279 /// Factory method to create an `AppKitView`.
280 ///
281 /// The `id` parameter is an unused unique identifier generated with
282 /// [platformViewsRegistry].
283 ///
284 /// The `viewType` parameter is the identifier of the iOS view type to be
285 /// created, a factory for this view type must have been registered on the
286 /// platform side. Platform view factories are typically registered by plugin
287 /// code.
288 ///
289 /// The `onFocus` parameter is a callback that will be invoked when the UIKit
290 /// view asks to get the input focus. If `creationParams` is non null then
291 /// `creationParamsCodec` must not be null.
292 static Future<AppKitViewController> initAppKitView({
293 required int id,
294 required String viewType,
295 required TextDirection layoutDirection,
296 dynamic creationParams,
297 MessageCodec<dynamic>? creationParamsCodec,
298 VoidCallback? onFocus,
299 }) async {
300 assert(creationParams == null || creationParamsCodec != null);
301
302 // TODO(amirh): pass layoutDirection once the system channel supports it.
303 // https://github.com/flutter/flutter/issues/133682
304 final Map<String, dynamic> args = <String, dynamic>{'id': id, 'viewType': viewType};
305 if (creationParams != null) {
306 final ByteData paramsByteData = creationParamsCodec!.encodeMessage(creationParams)!;
307 args['params'] = Uint8List.view(paramsByteData.buffer, 0, paramsByteData.lengthInBytes);
308 }
309 await SystemChannels.platform_views.invokeMethod<void>('create', args);
310 if (onFocus != null) {
311 _instance._focusCallbacks[id] = onFocus;
312 }
313 return AppKitViewController._(id, layoutDirection);
314 }
315}
316
317/// Properties of an Android pointer.
318///
319/// A Dart version of Android's [MotionEvent.PointerProperties](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties).
320class AndroidPointerProperties {
321 /// Creates an [AndroidPointerProperties] object.
322 const AndroidPointerProperties({required this.id, required this.toolType});
323
324 /// See Android's [MotionEvent.PointerProperties#id](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#id).
325 final int id;
326
327 /// The type of tool used to make contact such as a finger or stylus, if known.
328 /// See Android's [MotionEvent.PointerProperties#toolType](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#toolType).
329 final int toolType;
330
331 /// Value for `toolType` when the tool type is unknown.
332 static const int kToolTypeUnknown = 0;
333
334 /// Value for `toolType` when the tool type is a finger.
335 static const int kToolTypeFinger = 1;
336
337 /// Value for `toolType` when the tool type is a stylus.
338 static const int kToolTypeStylus = 2;
339
340 /// Value for `toolType` when the tool type is a mouse.
341 static const int kToolTypeMouse = 3;
342
343 /// Value for `toolType` when the tool type is an eraser.
344 static const int kToolTypeEraser = 4;
345
346 List<int> _asList() => <int>[id, toolType];
347
348 @override
349 String toString() {
350 return '${objectRuntimeType(this, 'AndroidPointerProperties')}(id: $id, toolType: $toolType)';
351 }
352}
353
354/// Position information for an Android pointer.
355///
356/// A Dart version of Android's [MotionEvent.PointerCoords](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords).
357class AndroidPointerCoords {
358 /// Creates an AndroidPointerCoords.
359 const AndroidPointerCoords({
360 required this.orientation,
361 required this.pressure,
362 required this.size,
363 required this.toolMajor,
364 required this.toolMinor,
365 required this.touchMajor,
366 required this.touchMinor,
367 required this.x,
368 required this.y,
369 });
370
371 /// The orientation of the touch area and tool area in radians clockwise from vertical.
372 ///
373 /// See Android's [MotionEvent.PointerCoords#orientation](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#orientation).
374 final double orientation;
375
376 /// A normalized value that describes the pressure applied to the device by a finger or other tool.
377 ///
378 /// See Android's [MotionEvent.PointerCoords#pressure](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#pressure).
379 final double pressure;
380
381 /// A normalized value that describes the approximate size of the pointer touch area in relation to the maximum detectable size of the device.
382 ///
383 /// See Android's [MotionEvent.PointerCoords#size](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#size).
384 final double size;
385
386 /// See Android's [MotionEvent.PointerCoords#toolMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMajor).
387 final double toolMajor;
388
389 /// See Android's [MotionEvent.PointerCoords#toolMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMinor).
390 final double toolMinor;
391
392 /// See Android's [MotionEvent.PointerCoords#touchMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMajor).
393 final double touchMajor;
394
395 /// See Android's [MotionEvent.PointerCoords#touchMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMinor).
396 final double touchMinor;
397
398 /// The X component of the pointer movement.
399 ///
400 /// See Android's [MotionEvent.PointerCoords#x](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#x).
401 final double x;
402
403 /// The Y component of the pointer movement.
404 ///
405 /// See Android's [MotionEvent.PointerCoords#y](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#y).
406 final double y;
407
408 List<double> _asList() {
409 return <double>[
410 orientation,
411 pressure,
412 size,
413 toolMajor,
414 toolMinor,
415 touchMajor,
416 touchMinor,
417 x,
418 y,
419 ];
420 }
421
422 @override
423 String toString() {
424 return '${objectRuntimeType(this, 'AndroidPointerCoords')}(orientation: $orientation, pressure: $pressure, size: $size, toolMajor: $toolMajor, toolMinor: $toolMinor, touchMajor: $touchMajor, touchMinor: $touchMinor, x: $x, y: $y)';
425 }
426}
427
428/// A Dart version of Android's [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent).
429///
430/// This is used by [AndroidViewController] to describe pointer events that are forwarded to a platform view
431/// when Flutter receives an event that it determines is to be handled by that platform view rather than by
432/// another Flutter widget.
433///
434/// See also:
435///
436/// * [AndroidViewController.sendMotionEvent], which can be used to send an [AndroidMotionEvent] explicitly.
437class AndroidMotionEvent {
438 /// Creates an AndroidMotionEvent.
439 AndroidMotionEvent({
440 required this.downTime,
441 required this.eventTime,
442 required this.action,
443 required this.pointerCount,
444 required this.pointerProperties,
445 required this.pointerCoords,
446 required this.metaState,
447 required this.buttonState,
448 required this.xPrecision,
449 required this.yPrecision,
450 required this.deviceId,
451 required this.edgeFlags,
452 required this.source,
453 required this.flags,
454 required this.motionEventId,
455 }) : assert(pointerProperties.length == pointerCount),
456 assert(pointerCoords.length == pointerCount);
457
458 /// The time (in ms) when the user originally pressed down to start a stream of position events,
459 /// relative to an arbitrary timeline.
460 ///
461 /// See Android's [MotionEvent#getDownTime](https://developer.android.com/reference/android/view/MotionEvent.html#getDownTime()).
462 final int downTime;
463
464 /// The time this event occurred, relative to an arbitrary timeline.
465 ///
466 /// See Android's [MotionEvent#getEventTime](https://developer.android.com/reference/android/view/MotionEvent.html#getEventTime()).
467 final int eventTime;
468
469 /// A value representing the kind of action being performed.
470 ///
471 /// See Android's [MotionEvent#getAction](https://developer.android.com/reference/android/view/MotionEvent.html#getAction()).
472 final int action;
473
474 /// The number of pointers that are part of this event.
475 /// This must be equivalent to the length of `pointerProperties` and `pointerCoords`.
476 ///
477 /// See Android's [MotionEvent#getPointerCount](https://developer.android.com/reference/android/view/MotionEvent.html#getPointerCount()).
478 final int pointerCount;
479
480 /// List of [AndroidPointerProperties] for each pointer that is part of this event.
481 final List<AndroidPointerProperties> pointerProperties;
482
483 /// List of [AndroidPointerCoords] for each pointer that is part of this event.
484 final List<AndroidPointerCoords> pointerCoords;
485
486 /// The state of any meta / modifier keys that were in effect when the event was generated.
487 ///
488 /// See Android's [MotionEvent#getMetaState](https://developer.android.com/reference/android/view/MotionEvent.html#getMetaState()).
489 final int metaState;
490
491 /// The state of all buttons that are pressed such as a mouse or stylus button.
492 ///
493 /// See Android's [MotionEvent#getButtonState](https://developer.android.com/reference/android/view/MotionEvent.html#getButtonState()).
494 final int buttonState;
495
496 /// The precision of the X coordinates being reported, in physical pixels.
497 ///
498 /// See Android's [MotionEvent#getXPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getXPrecision()).
499 final double xPrecision;
500
501 /// The precision of the Y coordinates being reported, in physical pixels.
502 ///
503 /// See Android's [MotionEvent#getYPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getYPrecision()).
504 final double yPrecision;
505
506 /// See Android's [MotionEvent#getDeviceId](https://developer.android.com/reference/android/view/MotionEvent.html#getDeviceId()).
507 final int deviceId;
508
509 /// A bit field indicating which edges, if any, were touched by this MotionEvent.
510 ///
511 /// See Android's [MotionEvent#getEdgeFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getEdgeFlags()).
512 final int edgeFlags;
513
514 /// The source of this event (e.g a touchpad or stylus).
515 ///
516 /// See Android's [MotionEvent#getSource](https://developer.android.com/reference/android/view/MotionEvent.html#getSource()).
517 final int source;
518
519 /// See Android's [MotionEvent#getFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getFlags()).
520 final int flags;
521
522 /// Used to identify this [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent.html) uniquely in the Flutter Engine.
523 final int motionEventId;
524
525 List<dynamic> _asList(int viewId) {
526 return <dynamic>[
527 viewId,
528 downTime,
529 eventTime,
530 action,
531 pointerCount,
532 pointerProperties.map<List<int>>((AndroidPointerProperties p) => p._asList()).toList(),
533 pointerCoords.map<List<double>>((AndroidPointerCoords p) => p._asList()).toList(),
534 metaState,
535 buttonState,
536 xPrecision,
537 yPrecision,
538 deviceId,
539 edgeFlags,
540 source,
541 flags,
542 motionEventId,
543 ];
544 }
545
546 @override
547 String toString() {
548 return 'AndroidPointerEvent(downTime: $downTime, eventTime: $eventTime, action: $action, pointerCount: $pointerCount, pointerProperties: $pointerProperties, pointerCoords: $pointerCoords, metaState: $metaState, buttonState: $buttonState, xPrecision: $xPrecision, yPrecision: $yPrecision, deviceId: $deviceId, edgeFlags: $edgeFlags, source: $source, flags: $flags, motionEventId: $motionEventId)';
549 }
550}
551
552enum _AndroidViewState { waitingForSize, creating, created, disposed }
553
554// Helper for converting PointerEvents into AndroidMotionEvents.
555class _AndroidMotionEventConverter {
556 _AndroidMotionEventConverter();
557
558 final Map<int, AndroidPointerCoords> pointerPositions = <int, AndroidPointerCoords>{};
559 final Map<int, AndroidPointerProperties> pointerProperties = <int, AndroidPointerProperties>{};
560 final Set<int> usedAndroidPointerIds = <int>{};
561
562 late PointTransformer pointTransformer;
563
564 int? downTimeMillis;
565
566 void handlePointerDownEvent(PointerDownEvent event) {
567 if (pointerProperties.isEmpty) {
568 downTimeMillis = event.timeStamp.inMilliseconds;
569 }
570 int androidPointerId = 0;
571 while (usedAndroidPointerIds.contains(androidPointerId)) {
572 androidPointerId++;
573 }
574 usedAndroidPointerIds.add(androidPointerId);
575 pointerProperties[event.pointer] = propertiesFor(event, androidPointerId);
576 }
577
578 void updatePointerPositions(PointerEvent event) {
579 final Offset position = pointTransformer(event.position);
580 pointerPositions[event.pointer] = AndroidPointerCoords(
581 orientation: event.orientation,
582 pressure: event.pressure,
583 size: event.size,
584 toolMajor: event.radiusMajor,
585 toolMinor: event.radiusMinor,
586 touchMajor: event.radiusMajor,
587 touchMinor: event.radiusMinor,
588 x: position.dx,
589 y: position.dy,
590 );
591 }
592
593 void _remove(int pointer) {
594 pointerPositions.remove(pointer);
595 usedAndroidPointerIds.remove(pointerProperties[pointer]!.id);
596 pointerProperties.remove(pointer);
597 if (pointerProperties.isEmpty) {
598 downTimeMillis = null;
599 }
600 }
601
602 void handlePointerUpEvent(PointerUpEvent event) {
603 _remove(event.pointer);
604 }
605
606 void handlePointerCancelEvent(PointerCancelEvent event) {
607 // The pointer cancel event is handled like pointer up. Normally,
608 // the difference is that pointer cancel doesn't perform any action,
609 // but in this case neither up or cancel perform any action.
610 _remove(event.pointer);
611 }
612
613 AndroidMotionEvent? toAndroidMotionEvent(PointerEvent event) {
614 final List<int> pointers = pointerPositions.keys.toList();
615 final int pointerIdx = pointers.indexOf(event.pointer);
616 final int numPointers = pointers.length;
617
618 // This value must match the value in engine's FlutterView.java.
619 // This flag indicates whether the original Android pointer events were batched together.
620 const int kPointerDataFlagBatched = 1;
621
622 // Android MotionEvent objects can batch information on multiple pointers.
623 // Flutter breaks these such batched events into multiple PointerEvent objects.
624 // When there are multiple active pointers we accumulate the information for all pointers
625 // as we get PointerEvents, and only send it to the embedded Android view when
626 // we see the last pointer. This way we achieve the same batching as Android.
627 if (event.platformData == kPointerDataFlagBatched ||
628 (isSinglePointerAction(event) && pointerIdx < numPointers - 1)) {
629 return null;
630 }
631
632 final int? action = switch (event) {
633 PointerDownEvent() when numPointers == 1 => AndroidViewController.kActionDown,
634 PointerUpEvent() when numPointers == 1 => AndroidViewController.kActionUp,
635 PointerDownEvent() => AndroidViewController.pointerAction(
636 pointerIdx,
637 AndroidViewController.kActionPointerDown,
638 ),
639 PointerUpEvent() => AndroidViewController.pointerAction(
640 pointerIdx,
641 AndroidViewController.kActionPointerUp,
642 ),
643 PointerMoveEvent() => AndroidViewController.kActionMove,
644 PointerCancelEvent() => AndroidViewController.kActionCancel,
645 _ => null,
646 };
647 if (action == null) {
648 return null;
649 }
650
651 return AndroidMotionEvent(
652 downTime: downTimeMillis!,
653 eventTime: event.timeStamp.inMilliseconds,
654 action: action,
655 pointerCount: pointerPositions.length,
656 pointerProperties:
657 pointers.map<AndroidPointerProperties>((int i) => pointerProperties[i]!).toList(),
658 pointerCoords: pointers.map<AndroidPointerCoords>((int i) => pointerPositions[i]!).toList(),
659 metaState: 0,
660 buttonState: 0,
661 xPrecision: 1.0,
662 yPrecision: 1.0,
663 deviceId: 0,
664 edgeFlags: 0,
665 source: _AndroidMotionEventConverter.sourceFor(event),
666 flags: 0,
667 motionEventId: event.embedderId,
668 );
669 }
670
671 static int sourceFor(PointerEvent event) {
672 return switch (event.kind) {
673 PointerDeviceKind.touch => AndroidViewController.kInputDeviceSourceTouchScreen,
674 PointerDeviceKind.trackpad => AndroidViewController.kInputDeviceSourceTouchPad,
675 PointerDeviceKind.mouse => AndroidViewController.kInputDeviceSourceMouse,
676 PointerDeviceKind.stylus => AndroidViewController.kInputDeviceSourceStylus,
677 PointerDeviceKind.invertedStylus => AndroidViewController.kInputDeviceSourceStylus,
678 PointerDeviceKind.unknown => AndroidViewController.kInputDeviceSourceUnknown,
679 };
680 }
681
682 AndroidPointerProperties propertiesFor(PointerEvent event, int pointerId) {
683 return AndroidPointerProperties(
684 id: pointerId,
685 toolType: switch (event.kind) {
686 PointerDeviceKind.touch => AndroidPointerProperties.kToolTypeFinger,
687 PointerDeviceKind.trackpad => AndroidPointerProperties.kToolTypeFinger,
688 PointerDeviceKind.mouse => AndroidPointerProperties.kToolTypeMouse,
689 PointerDeviceKind.stylus => AndroidPointerProperties.kToolTypeStylus,
690 PointerDeviceKind.invertedStylus => AndroidPointerProperties.kToolTypeEraser,
691 PointerDeviceKind.unknown => AndroidPointerProperties.kToolTypeUnknown,
692 },
693 );
694 }
695
696 bool isSinglePointerAction(PointerEvent event) =>
697 event is! PointerDownEvent && event is! PointerUpEvent;
698}
699
700class _CreationParams {
701 const _CreationParams(this.data, this.codec);
702 final dynamic data;
703 final MessageCodec<dynamic> codec;
704}
705
706/// Controls an Android view that is composed using a GL texture.
707///
708/// Typically created with [PlatformViewsService.initAndroidView].
709// TODO(bparrishMines): Remove abstract methods that are not required by all subclasses.
710abstract class AndroidViewController extends PlatformViewController {
711 AndroidViewController._({
712 required this.viewId,
713 required String viewType,
714 required TextDirection layoutDirection,
715 dynamic creationParams,
716 MessageCodec<dynamic>? creationParamsCodec,
717 }) : assert(creationParams == null || creationParamsCodec != null),
718 _viewType = viewType,
719 _layoutDirection = layoutDirection,
720 _creationParams =
721 creationParams == null ? null : _CreationParams(creationParams, creationParamsCodec!);
722
723 /// Action code for when a primary pointer touched the screen.
724 ///
725 /// Android's [MotionEvent.ACTION_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN)
726 static const int kActionDown = 0;
727
728 /// Action code for when a primary pointer stopped touching the screen.
729 ///
730 /// Android's [MotionEvent.ACTION_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_UP)
731 static const int kActionUp = 1;
732
733 /// Action code for when the event only includes information about pointer movement.
734 ///
735 /// Android's [MotionEvent.ACTION_MOVE](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE)
736 static const int kActionMove = 2;
737
738 /// Action code for when a motion event has been canceled.
739 ///
740 /// Android's [MotionEvent.ACTION_CANCEL](https://developer.android.com/reference/android/view/MotionEvent#ACTION_CANCEL)
741 static const int kActionCancel = 3;
742
743 /// Action code for when a secondary pointer touched the screen.
744 ///
745 /// Android's [MotionEvent.ACTION_POINTER_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_DOWN)
746 static const int kActionPointerDown = 5;
747
748 /// Action code for when a secondary pointer stopped touching the screen.
749 ///
750 /// Android's [MotionEvent.ACTION_POINTER_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_UP)
751 static const int kActionPointerUp = 6;
752
753 /// Android's [View.LAYOUT_DIRECTION_LTR](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_LTR) value.
754 static const int kAndroidLayoutDirectionLtr = 0;
755
756 /// Android's [View.LAYOUT_DIRECTION_RTL](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_RTL) value.
757 static const int kAndroidLayoutDirectionRtl = 1;
758
759 /// Android's [InputDevice.SOURCE_UNKNOWN](https://developer.android.com/reference/android/view/InputDevice#SOURCE_UNKNOWN)
760 static const int kInputDeviceSourceUnknown = 0;
761
762 /// Android's [InputDevice.SOURCE_TOUCHSCREEN](https://developer.android.com/reference/android/view/InputDevice#SOURCE_TOUCHSCREEN)
763 static const int kInputDeviceSourceTouchScreen = 4098;
764
765 /// Android's [InputDevice.SOURCE_MOUSE](https://developer.android.com/reference/android/view/InputDevice#SOURCE_MOUSE)
766 static const int kInputDeviceSourceMouse = 8194;
767
768 /// Android's [InputDevice.SOURCE_STYLUS](https://developer.android.com/reference/android/view/InputDevice#SOURCE_STYLUS)
769 static const int kInputDeviceSourceStylus = 16386;
770
771 /// Android's [InputDevice.SOURCE_TOUCHPAD](https://developer.android.com/reference/android/view/InputDevice#SOURCE_TOUCHPAD)
772 static const int kInputDeviceSourceTouchPad = 1048584;
773
774 /// The unique identifier of the Android view controlled by this controller.
775 @override
776 final int viewId;
777
778 final String _viewType;
779
780 // Helps convert PointerEvents to AndroidMotionEvents.
781 final _AndroidMotionEventConverter _motionEventConverter = _AndroidMotionEventConverter();
782
783 TextDirection _layoutDirection;
784
785 _AndroidViewState _state = _AndroidViewState.waitingForSize;
786
787 final _CreationParams? _creationParams;
788
789 final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks =
790 <PlatformViewCreatedCallback>[];
791
792 static int _getAndroidDirection(TextDirection direction) {
793 return switch (direction) {
794 TextDirection.ltr => kAndroidLayoutDirectionLtr,
795 TextDirection.rtl => kAndroidLayoutDirectionRtl,
796 };
797 }
798
799 /// Creates a masked Android MotionEvent action value for an indexed pointer.
800 static int pointerAction(int pointerId, int action) {
801 return ((pointerId << 8) & 0xff00) | (action & 0xff);
802 }
803
804 /// Sends the message to dispose the platform view.
805 Future<void> _sendDisposeMessage();
806
807 /// True if [_sendCreateMessage] can only be called with a non-null size.
808 bool get _createRequiresSize;
809
810 /// Sends the message to create the platform view with an initial [size].
811 ///
812 /// If [_createRequiresSize] is true, `size` is non-nullable, and the call
813 /// should instead be deferred until the size is available.
814 Future<void> _sendCreateMessage({required covariant Size? size, Offset? position});
815
816 /// Sends the message to resize the platform view to [size].
817 Future<Size> _sendResizeMessage(Size size);
818
819 @override
820 bool get awaitingCreation => _state == _AndroidViewState.waitingForSize;
821
822 @override
823 Future<void> create({Size? size, Offset? position}) async {
824 assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
825 assert(
826 _state == _AndroidViewState.waitingForSize,
827 'Android view is already sized. View id: $viewId',
828 );
829
830 if (_createRequiresSize && size == null) {
831 // Wait for a setSize call.
832 return;
833 }
834
835 _state = _AndroidViewState.creating;
836 await _sendCreateMessage(size: size, position: position);
837 _state = _AndroidViewState.created;
838
839 for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
840 callback(viewId);
841 }
842 }
843
844 /// Sizes the Android View.
845 ///
846 /// [size] is the view's new size in logical pixel. It must be greater than
847 /// zero.
848 ///
849 /// The first time a size is set triggers the creation of the Android view.
850 ///
851 /// Returns the buffer size in logical pixel that backs the texture where the platform
852 /// view pixels are written to.
853 ///
854 /// The buffer size may or may not be the same as [size].
855 ///
856 /// As a result, consumers are expected to clip the texture using [size], while using
857 /// the return value to size the texture.
858 Future<Size> setSize(Size size) async {
859 assert(_state != _AndroidViewState.disposed, 'Android view is disposed. View id: $viewId');
860 if (_state == _AndroidViewState.waitingForSize) {
861 // Either `create` hasn't been called, or it couldn't run due to missing
862 // size information, so create the view now.
863 await create(size: size);
864 return size;
865 } else {
866 return _sendResizeMessage(size);
867 }
868 }
869
870 /// Sets the offset of the platform view.
871 ///
872 /// [off] is the view's new offset in logical pixel.
873 ///
874 /// On Android, this allows the Android native view to draw the a11y highlights in the same
875 /// location on the screen as the platform view widget in the Flutter framework.
876 Future<void> setOffset(Offset off);
877
878 /// Returns the texture entry id that the Android view is rendering into.
879 ///
880 /// Returns null if the Android view has not been successfully created, if it has been
881 /// disposed, or if the implementation does not use textures.
882 int? get textureId;
883
884 /// True if the view requires native view composition rather than using a
885 /// texture to render.
886 ///
887 /// This value may change during [create], but will not change after that
888 /// call's future has completed.
889 bool get requiresViewComposition => false;
890
891 /// Sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
892 /// to the view.
893 ///
894 /// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)).
895 /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int))
896 /// for description of the parameters.
897 ///
898 /// See [AndroidViewController.dispatchPointerEvent] for sending a
899 /// [PointerEvent].
900 Future<void> sendMotionEvent(AndroidMotionEvent event) async {
901 await SystemChannels.platform_views.invokeMethod<dynamic>('touch', event._asList(viewId));
902 }
903
904 /// Converts a given point from the global coordinate system in logical pixels
905 /// to the local coordinate system for this box.
906 ///
907 /// This is required to convert a [PointerEvent] to an [AndroidMotionEvent].
908 /// It is typically provided by using [RenderBox.globalToLocal].
909 PointTransformer get pointTransformer => _motionEventConverter.pointTransformer;
910 set pointTransformer(PointTransformer transformer) {
911 _motionEventConverter.pointTransformer = transformer;
912 }
913
914 /// Whether the platform view has already been created.
915 bool get isCreated => _state == _AndroidViewState.created;
916
917 /// Adds a callback that will get invoke after the platform view has been
918 /// created.
919 void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
920 assert(_state != _AndroidViewState.disposed);
921 _platformViewCreatedCallbacks.add(listener);
922 }
923
924 /// Removes a callback added with [addOnPlatformViewCreatedListener].
925 void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
926 assert(_state != _AndroidViewState.disposed);
927 _platformViewCreatedCallbacks.remove(listener);
928 }
929
930 /// The created callbacks that are invoked after the platform view has been
931 /// created.
932 @visibleForTesting
933 List<PlatformViewCreatedCallback> get createdCallbacks => _platformViewCreatedCallbacks;
934
935 /// Sets the layout direction for the Android view.
936 Future<void> setLayoutDirection(TextDirection layoutDirection) async {
937 assert(
938 _state != _AndroidViewState.disposed,
939 'trying to set a layout direction for a disposed Android view. View id: $viewId',
940 );
941
942 if (layoutDirection == _layoutDirection) {
943 return;
944 }
945
946 _layoutDirection = layoutDirection;
947
948 // If the view was not yet created we just update _layoutDirection and return, as the new
949 // direction will be used in _create.
950 if (_state == _AndroidViewState.waitingForSize) {
951 return;
952 }
953
954 await SystemChannels.platform_views.invokeMethod<void>('setDirection', <String, dynamic>{
955 'id': viewId,
956 'direction': _getAndroidDirection(layoutDirection),
957 });
958 }
959
960 /// Converts the [PointerEvent] and sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
961 /// to the view.
962 ///
963 /// This method can only be used if a [PointTransformer] is provided to
964 /// [AndroidViewController.pointTransformer]. Otherwise, an [AssertionError]
965 /// is thrown. See [AndroidViewController.sendMotionEvent] for sending a
966 /// `MotionEvent` without a [PointTransformer].
967 ///
968 /// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)).
969 /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int))
970 /// for description of the parameters.
971 @override
972 Future<void> dispatchPointerEvent(PointerEvent event) async {
973 if (event is PointerHoverEvent) {
974 return;
975 }
976
977 if (event is PointerDownEvent) {
978 _motionEventConverter.handlePointerDownEvent(event);
979 }
980
981 _motionEventConverter.updatePointerPositions(event);
982
983 final AndroidMotionEvent? androidEvent = _motionEventConverter.toAndroidMotionEvent(event);
984
985 if (event is PointerUpEvent) {
986 _motionEventConverter.handlePointerUpEvent(event);
987 } else if (event is PointerCancelEvent) {
988 _motionEventConverter.handlePointerCancelEvent(event);
989 }
990
991 if (androidEvent != null) {
992 await sendMotionEvent(androidEvent);
993 }
994 }
995
996 /// Clears the focus from the Android View if it is focused.
997 @override
998 Future<void> clearFocus() {
999 if (_state != _AndroidViewState.created) {
1000 return Future<void>.value();
1001 }
1002 return SystemChannels.platform_views.invokeMethod<void>('clearFocus', viewId);
1003 }
1004
1005 /// Disposes the Android view.
1006 ///
1007 /// The [AndroidViewController] object is unusable after calling this.
1008 /// The identifier of the platform view cannot be reused after the view is
1009 /// disposed.
1010 @override
1011 Future<void> dispose() async {
1012 final _AndroidViewState state = _state;
1013 _state = _AndroidViewState.disposed;
1014 _platformViewCreatedCallbacks.clear();
1015 PlatformViewsService._instance._focusCallbacks.remove(viewId);
1016 if (state == _AndroidViewState.creating || state == _AndroidViewState.created) {
1017 await _sendDisposeMessage();
1018 }
1019 }
1020}
1021
1022/// Controls an Android view that is composed using a GL texture.
1023/// This controller is created from the [PlatformViewsService.initSurfaceAndroidView] factory,
1024/// and is defined for backward compatibility.
1025class SurfaceAndroidViewController extends AndroidViewController {
1026 SurfaceAndroidViewController._({
1027 required super.viewId,
1028 required super.viewType,
1029 required super.layoutDirection,
1030 super.creationParams,
1031 super.creationParamsCodec,
1032 }) : super._();
1033
1034 // By default, assume the implementation will be texture-based.
1035 _AndroidViewControllerInternals _internals = _TextureAndroidViewControllerInternals();
1036
1037 @override
1038 bool get _createRequiresSize => true;
1039
1040 @override
1041 Future<bool> _sendCreateMessage({required Size size, Offset? position}) async {
1042 assert(
1043 !size.isEmpty,
1044 'trying to create $TextureAndroidViewController without setting a valid size.',
1045 );
1046
1047 final dynamic response = await _AndroidViewControllerInternals.sendCreateMessage(
1048 viewId: viewId,
1049 viewType: _viewType,
1050 hybrid: false,
1051 hybridFallback: true,
1052 layoutDirection: _layoutDirection,
1053 creationParams: _creationParams,
1054 size: size,
1055 position: position,
1056 );
1057 if (response is int) {
1058 (_internals as _TextureAndroidViewControllerInternals).textureId = response;
1059 } else {
1060 // A null response indicates fallback to Hybrid Composition, so swap out
1061 // the implementation.
1062 _internals = _HybridAndroidViewControllerInternals();
1063 }
1064 return true;
1065 }
1066
1067 @override
1068 int? get textureId {
1069 return _internals.textureId;
1070 }
1071
1072 @override
1073 bool get requiresViewComposition {
1074 return _internals.requiresViewComposition;
1075 }
1076
1077 @override
1078 Future<void> _sendDisposeMessage() {
1079 return _internals.sendDisposeMessage(viewId: viewId);
1080 }
1081
1082 @override
1083 Future<Size> _sendResizeMessage(Size size) {
1084 return _internals.setSize(size, viewId: viewId, viewState: _state);
1085 }
1086
1087 @override
1088 Future<void> setOffset(Offset off) {
1089 return _internals.setOffset(off, viewId: viewId, viewState: _state);
1090 }
1091}
1092
1093/// Controls an Android view that is composed using the Android view hierarchy.
1094/// This controller is created from the [PlatformViewsService.initExpensiveAndroidView] factory.
1095class ExpensiveAndroidViewController extends AndroidViewController {
1096 ExpensiveAndroidViewController._({
1097 required super.viewId,
1098 required super.viewType,
1099 required super.layoutDirection,
1100 super.creationParams,
1101 super.creationParamsCodec,
1102 }) : super._();
1103
1104 final _AndroidViewControllerInternals _internals = _HybridAndroidViewControllerInternals();
1105
1106 @override
1107 bool get _createRequiresSize => false;
1108
1109 @override
1110 Future<void> _sendCreateMessage({required Size? size, Offset? position}) async {
1111 await _AndroidViewControllerInternals.sendCreateMessage(
1112 viewId: viewId,
1113 viewType: _viewType,
1114 hybrid: true,
1115 layoutDirection: _layoutDirection,
1116 creationParams: _creationParams,
1117 position: position,
1118 );
1119 }
1120
1121 @override
1122 int? get textureId {
1123 return _internals.textureId;
1124 }
1125
1126 @override
1127 bool get requiresViewComposition {
1128 return _internals.requiresViewComposition;
1129 }
1130
1131 @override
1132 Future<void> _sendDisposeMessage() {
1133 return _internals.sendDisposeMessage(viewId: viewId);
1134 }
1135
1136 @override
1137 Future<Size> _sendResizeMessage(Size size) {
1138 return _internals.setSize(size, viewId: viewId, viewState: _state);
1139 }
1140
1141 @override
1142 Future<void> setOffset(Offset off) {
1143 return _internals.setOffset(off, viewId: viewId, viewState: _state);
1144 }
1145}
1146
1147/// Controls an Android view that is composed using the Android view hierarchy.
1148/// This controller is created from the [PlatformViewsService.initExpensiveAndroidView] factory.
1149class HybridAndroidViewController extends AndroidViewController {
1150 HybridAndroidViewController._({
1151 required super.viewId,
1152 required super.viewType,
1153 required super.layoutDirection,
1154 super.creationParams,
1155 super.creationParamsCodec,
1156 }) : super._();
1157
1158 final _AndroidViewControllerInternals _internals = _Hybrid2AndroidViewControllerInternals();
1159
1160 /// Perform a runtime check to determine if HCPP mode is supported on the
1161 /// current device.
1162 static Future<bool> checkIfSupported() =>
1163 _Hybrid2AndroidViewControllerInternals.checkIfSurfaceControlEnabled();
1164
1165 @override
1166 bool get _createRequiresSize => false;
1167
1168 @override
1169 Future<void> _sendCreateMessage({required Size? size, Offset? position}) async {
1170 await _AndroidViewControllerInternals.sendCreateMessage(
1171 viewId: viewId,
1172 viewType: _viewType,
1173 hybrid: true,
1174 layoutDirection: _layoutDirection,
1175 creationParams: _creationParams,
1176 position: position,
1177 useNewController: true,
1178 );
1179 }
1180
1181 @override
1182 int? get textureId {
1183 return _internals.textureId;
1184 }
1185
1186 @override
1187 bool get requiresViewComposition {
1188 return _internals.requiresViewComposition;
1189 }
1190
1191 @override
1192 Future<void> _sendDisposeMessage() {
1193 return _internals.sendDisposeMessage(viewId: viewId);
1194 }
1195
1196 @override
1197 Future<Size> _sendResizeMessage(Size size) {
1198 return _internals.setSize(size, viewId: viewId, viewState: _state);
1199 }
1200
1201 @override
1202 Future<void> setOffset(Offset off) {
1203 return _internals.setOffset(off, viewId: viewId, viewState: _state);
1204 }
1205
1206 @override
1207 Future<void> sendMotionEvent(AndroidMotionEvent event) async {
1208 await SystemChannels.platform_views_2.invokeMethod<dynamic>('touch', event._asList(viewId));
1209 }
1210}
1211
1212/// Controls an Android view that is rendered as a texture.
1213/// This is typically used by [AndroidView] to display a View in the Android view hierarchy.
1214///
1215/// The platform view is created by calling [create] with an initial size.
1216///
1217/// The controller is typically created with [PlatformViewsService.initAndroidView].
1218class TextureAndroidViewController extends AndroidViewController {
1219 TextureAndroidViewController._({
1220 required super.viewId,
1221 required super.viewType,
1222 required super.layoutDirection,
1223 super.creationParams,
1224 super.creationParamsCodec,
1225 }) : super._();
1226
1227 final _TextureAndroidViewControllerInternals _internals =
1228 _TextureAndroidViewControllerInternals();
1229
1230 @override
1231 bool get _createRequiresSize => true;
1232
1233 @override
1234 Future<void> _sendCreateMessage({required Size size, Offset? position}) async {
1235 assert(
1236 !size.isEmpty,
1237 'trying to create $TextureAndroidViewController without setting a valid size.',
1238 );
1239
1240 _internals.textureId =
1241 await _AndroidViewControllerInternals.sendCreateMessage(
1242 viewId: viewId,
1243 viewType: _viewType,
1244 hybrid: false,
1245 layoutDirection: _layoutDirection,
1246 creationParams: _creationParams,
1247 size: size,
1248 position: position,
1249 )
1250 as int;
1251 }
1252
1253 @override
1254 int? get textureId {
1255 return _internals.textureId;
1256 }
1257
1258 @override
1259 bool get requiresViewComposition {
1260 return _internals.requiresViewComposition;
1261 }
1262
1263 @override
1264 Future<void> _sendDisposeMessage() {
1265 return _internals.sendDisposeMessage(viewId: viewId);
1266 }
1267
1268 @override
1269 Future<Size> _sendResizeMessage(Size size) {
1270 return _internals.setSize(size, viewId: viewId, viewState: _state);
1271 }
1272
1273 @override
1274 Future<void> setOffset(Offset off) {
1275 return _internals.setOffset(off, viewId: viewId, viewState: _state);
1276 }
1277}
1278
1279// The base class for an implementation of AndroidViewController.
1280//
1281// Subclasses should correspond to different rendering modes for platform
1282// views, and match different mode logic on the engine side.
1283abstract class _AndroidViewControllerInternals {
1284 // Sends a create message with the given parameters, and returns the result
1285 // if any.
1286 //
1287 // This uses a dynamic return because depending on the mode that is selected
1288 // on the native side, the return type is different. Callers should cast
1289 // depending on the possible return types for their arguments.
1290 static Future<dynamic> sendCreateMessage({
1291 required int viewId,
1292 required String viewType,
1293 required TextDirection layoutDirection,
1294 required bool hybrid,
1295 bool hybridFallback = false,
1296 bool useNewController = false,
1297 _CreationParams? creationParams,
1298 Size? size,
1299 Offset? position,
1300 }) {
1301 final Map<String, dynamic> args = <String, dynamic>{
1302 'id': viewId,
1303 'viewType': viewType,
1304 'direction': AndroidViewController._getAndroidDirection(layoutDirection),
1305 if (hybrid) 'hybrid': hybrid,
1306 if (size != null) 'width': size.width,
1307 if (size != null) 'height': size.height,
1308 if (hybridFallback) 'hybridFallback': hybridFallback,
1309 if (position != null) 'left': position.dx,
1310 if (position != null) 'top': position.dy,
1311 };
1312 if (creationParams != null) {
1313 final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!;
1314 args['params'] = Uint8List.view(paramsByteData.buffer, 0, paramsByteData.lengthInBytes);
1315 }
1316 if (useNewController) {
1317 return SystemChannels.platform_views_2.invokeMethod<dynamic>('create', args);
1318 }
1319 return SystemChannels.platform_views.invokeMethod<dynamic>('create', args);
1320 }
1321
1322 int? get textureId;
1323
1324 bool get requiresViewComposition;
1325
1326 Future<Size> setSize(Size size, {required int viewId, required _AndroidViewState viewState});
1327
1328 Future<void> setOffset(
1329 Offset offset, {
1330 required int viewId,
1331 required _AndroidViewState viewState,
1332 });
1333
1334 Future<void> sendDisposeMessage({required int viewId});
1335}
1336
1337// An AndroidViewController implementation for views whose contents are
1338// displayed via a texture rather than directly in a native view.
1339//
1340// This is used for both Virtual Display and Texture Layer Hybrid Composition.
1341class _TextureAndroidViewControllerInternals extends _AndroidViewControllerInternals {
1342 _TextureAndroidViewControllerInternals();
1343
1344 /// The current offset of the platform view.
1345 Offset _offset = Offset.zero;
1346
1347 @override
1348 int? textureId;
1349
1350 @override
1351 bool get requiresViewComposition => false;
1352
1353 @override
1354 Future<Size> setSize(
1355 Size size, {
1356 required int viewId,
1357 required _AndroidViewState viewState,
1358 }) async {
1359 assert(
1360 viewState != _AndroidViewState.waitingForSize,
1361 'Android view must have an initial size. View id: $viewId',
1362 );
1363 assert(!size.isEmpty);
1364
1365 final Map<Object?, Object?>? meta = await SystemChannels.platform_views
1366 .invokeMapMethod<Object?, Object?>('resize', <String, dynamic>{
1367 'id': viewId,
1368 'width': size.width,
1369 'height': size.height,
1370 });
1371 assert(meta != null);
1372 assert(meta!.containsKey('width'));
1373 assert(meta!.containsKey('height'));
1374 return Size(meta!['width']! as double, meta['height']! as double);
1375 }
1376
1377 @override
1378 Future<void> setOffset(
1379 Offset offset, {
1380 required int viewId,
1381 required _AndroidViewState viewState,
1382 }) async {
1383 if (offset == _offset) {
1384 return;
1385 }
1386
1387 // Don't set the offset unless the Android view has been created.
1388 // The implementation of this method channel throws if the Android view for this viewId
1389 // isn't addressable.
1390 if (viewState != _AndroidViewState.created) {
1391 return;
1392 }
1393
1394 _offset = offset;
1395
1396 await SystemChannels.platform_views.invokeMethod<void>('offset', <String, dynamic>{
1397 'id': viewId,
1398 'top': offset.dy,
1399 'left': offset.dx,
1400 });
1401 }
1402
1403 @override
1404 Future<void> sendDisposeMessage({required int viewId}) {
1405 return SystemChannels.platform_views.invokeMethod<void>('dispose', <String, dynamic>{
1406 'id': viewId,
1407 'hybrid': false,
1408 });
1409 }
1410}
1411
1412// An AndroidViewController implementation for views whose contents are
1413// displayed directly in a native view.
1414//
1415// This is used for Hybrid Composition.
1416class _HybridAndroidViewControllerInternals extends _AndroidViewControllerInternals {
1417 @override
1418 int get textureId {
1419 throw UnimplementedError('Not supported for hybrid composition.');
1420 }
1421
1422 @override
1423 bool get requiresViewComposition => true;
1424
1425 @override
1426 Future<Size> setSize(Size size, {required int viewId, required _AndroidViewState viewState}) {
1427 throw UnimplementedError('Not supported for hybrid composition.');
1428 }
1429
1430 @override
1431 Future<void> setOffset(
1432 Offset offset, {
1433 required int viewId,
1434 required _AndroidViewState viewState,
1435 }) {
1436 throw UnimplementedError('Not supported for hybrid composition.');
1437 }
1438
1439 @override
1440 Future<void> sendDisposeMessage({required int viewId}) {
1441 return SystemChannels.platform_views.invokeMethod<void>('dispose', <String, dynamic>{
1442 'id': viewId,
1443 'hybrid': true,
1444 });
1445 }
1446}
1447
1448// The HCPP platform view controller.
1449//
1450// This is only supported via an opt in on Impeller Android.
1451class _Hybrid2AndroidViewControllerInternals extends _AndroidViewControllerInternals {
1452 // Determine if HCPP can be used.
1453 static Future<bool> checkIfSurfaceControlEnabled() async {
1454 return (await SystemChannels.platform_views_2.invokeMethod<bool>(
1455 'isSurfaceControlEnabled',
1456 <String, Object?>{},
1457 ))!;
1458 }
1459
1460 @override
1461 int get textureId {
1462 throw UnimplementedError('Not supported for hybrid composition.');
1463 }
1464
1465 @override
1466 bool get requiresViewComposition => true;
1467
1468 @override
1469 Future<Size> setSize(Size size, {required int viewId, required _AndroidViewState viewState}) {
1470 throw UnimplementedError('Not supported for hybrid composition.');
1471 }
1472
1473 @override
1474 Future<void> setOffset(
1475 Offset offset, {
1476 required int viewId,
1477 required _AndroidViewState viewState,
1478 }) {
1479 throw UnimplementedError('Not supported for hybrid composition.');
1480 }
1481
1482 @override
1483 Future<void> sendDisposeMessage({required int viewId}) {
1484 return SystemChannels.platform_views_2.invokeMethod<void>('dispose', <String, dynamic>{
1485 'id': viewId,
1486 'hybrid': true,
1487 });
1488 }
1489}
1490
1491/// Base class for iOS and macOS view controllers.
1492///
1493/// View controllers are used to create and interact with the UIView or NSView
1494/// underlying a platform view.
1495abstract class DarwinPlatformViewController {
1496 /// Public default for subclasses to override.
1497 DarwinPlatformViewController(this.id, TextDirection layoutDirection)
1498 : _layoutDirection = layoutDirection;
1499
1500 /// The unique identifier of the iOS view controlled by this controller.
1501 ///
1502 /// This identifier is typically generated by
1503 /// [PlatformViewsRegistry.getNextPlatformViewId].
1504 final int id;
1505
1506 bool _debugDisposed = false;
1507
1508 TextDirection _layoutDirection;
1509
1510 /// Sets the layout direction for the iOS UIView.
1511 Future<void> setLayoutDirection(TextDirection layoutDirection) async {
1512 assert(
1513 !_debugDisposed,
1514 'trying to set a layout direction for a disposed iOS UIView. View id: $id',
1515 );
1516
1517 if (layoutDirection == _layoutDirection) {
1518 return;
1519 }
1520
1521 _layoutDirection = layoutDirection;
1522
1523 // TODO(amirh): invoke the iOS platform views channel direction method once available.
1524 }
1525
1526 /// Accept an active gesture.
1527 ///
1528 /// When a touch sequence is happening on the embedded UIView all touch events are delayed.
1529 /// Calling this method releases the delayed events to the embedded UIView and makes it consume
1530 /// any following touch events for the pointers involved in the active gesture.
1531 Future<void> acceptGesture() {
1532 final Map<String, dynamic> args = <String, dynamic>{'id': id};
1533 return SystemChannels.platform_views.invokeMethod('acceptGesture', args);
1534 }
1535
1536 /// Rejects an active gesture.
1537 ///
1538 /// When a touch sequence is happening on the embedded UIView all touch events are delayed.
1539 /// Calling this method drops the buffered touch events and prevents any future touch events for
1540 /// the pointers that are part of the active touch sequence from arriving to the embedded view.
1541 Future<void> rejectGesture() {
1542 final Map<String, dynamic> args = <String, dynamic>{'id': id};
1543 return SystemChannels.platform_views.invokeMethod('rejectGesture', args);
1544 }
1545
1546 /// Disposes the view.
1547 ///
1548 /// The [UiKitViewController] object is unusable after calling this.
1549 /// The `id` of the platform view cannot be reused after the view is
1550 /// disposed.
1551 Future<void> dispose() async {
1552 _debugDisposed = true;
1553 await SystemChannels.platform_views.invokeMethod<void>('dispose', id);
1554 PlatformViewsService._instance._focusCallbacks.remove(id);
1555 }
1556}
1557
1558/// Controller for an iOS platform view.
1559///
1560/// View controllers create and interact with the underlying UIView.
1561///
1562/// Typically created with [PlatformViewsService.initUiKitView].
1563class UiKitViewController extends DarwinPlatformViewController {
1564 UiKitViewController._(super.id, super.layoutDirection);
1565}
1566
1567/// Controller for a macOS platform view.
1568class AppKitViewController extends DarwinPlatformViewController {
1569 AppKitViewController._(super.id, super.layoutDirection);
1570}
1571
1572/// An interface for controlling a single platform view.
1573///
1574/// Used by [PlatformViewSurface] to interface with the platform view it embeds.
1575abstract class PlatformViewController {
1576 /// The viewId associated with this controller.
1577 ///
1578 /// The viewId should always be unique and non-negative.
1579 ///
1580 /// See also:
1581 ///
1582 /// * [PlatformViewsRegistry], which is a helper for managing platform view IDs.
1583 int get viewId;
1584
1585 /// True if [create] has not been successfully called the platform view.
1586 ///
1587 /// This can indicate either that [create] was never called, or that [create]
1588 /// was deferred for implementation-specific reasons.
1589 ///
1590 /// A `false` return value does not necessarily indicate that the [Future]
1591 /// returned by [create] has completed, only that creation has been started.
1592 bool get awaitingCreation => false;
1593
1594 /// Dispatches the `event` to the platform view.
1595 Future<void> dispatchPointerEvent(PointerEvent event);
1596
1597 /// Creates the platform view with the initial [size].
1598 ///
1599 /// [size] is the view's initial size in logical pixel.
1600 /// [size] can be omitted if the concrete implementation doesn't require an initial size
1601 /// to create the platform view.
1602 ///
1603 /// [position] is the view's initial position in logical pixels.
1604 /// [position] can be omitted if the concrete implementation doesn't require
1605 /// an initial position.
1606 Future<void> create({Size? size, Offset? position}) async {}
1607
1608 /// Disposes the platform view.
1609 ///
1610 /// The [PlatformViewController] is unusable after calling dispose.
1611 Future<void> dispose();
1612
1613 /// Clears the view's focus on the platform side.
1614 Future<void> clearFocus();
1615}
1616

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com