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