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