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'; |
8 | library; |
9 | |
10 | import 'dart:async'; |
11 | import 'dart:ui'; |
12 | |
13 | import 'package:flutter/foundation.dart'; |
14 | import 'package:flutter/gestures.dart'; |
15 | |
16 | import 'message_codec.dart'; |
17 | import 'system_channels.dart'; |
18 | |
19 | export 'dart:ui' show Offset, Size, TextDirection, VoidCallback; |
20 | |
21 | export 'package:flutter/gestures.dart' show PointerEvent; |
22 | |
23 | export '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]. |
29 | typedef PointTransformer = Offset Function(Offset position); |
30 | |
31 | /// The [PlatformViewsRegistry] responsible for generating unique identifiers for platform views. |
32 | final 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. |
38 | class 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. |
67 | typedef 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. |
72 | class 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). |
305 | class 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). |
345 | class 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. |
425 | class 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 | |
540 | enum _AndroidViewState { |
541 | waitingForSize, |
542 | creating, |
543 | created, |
544 | disposed, |
545 | } |
546 | |
547 | // Helper for converting PointerEvents into AndroidMotionEvents. |
548 | class _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 | |
690 | class _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. |
700 | abstract 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. |
1017 | class 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. |
1084 | class 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]. |
1142 | class 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. |
1201 | abstract 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. |
1262 | class _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. |
1340 | class _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. |
1380 | abstract 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]. |
1451 | class UiKitViewController extends DarwinPlatformViewController { |
1452 | UiKitViewController._( |
1453 | super.id, |
1454 | super.layoutDirection, |
1455 | ); |
1456 | } |
1457 | |
1458 | /// Controller for a macOS platform view. |
1459 | class 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. |
1469 | abstract 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 | |