Flutter Engine
The Flutter Engine
PlatformViewsController.java
Go to the documentation of this file.
1// Copyright 2013 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
5package io.flutter.plugin.platform;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.annotation.TargetApi;
10import android.content.ComponentCallbacks2;
11import android.content.Context;
12import android.content.MutableContextWrapper;
13import android.os.Build;
14import android.util.SparseArray;
15import android.view.MotionEvent;
16import android.view.MotionEvent.PointerCoords;
17import android.view.MotionEvent.PointerProperties;
18import android.view.SurfaceView;
19import android.view.View;
20import android.view.ViewGroup;
21import android.widget.FrameLayout;
22import androidx.annotation.NonNull;
23import androidx.annotation.Nullable;
24import androidx.annotation.UiThread;
25import androidx.annotation.VisibleForTesting;
26import io.flutter.Log;
27import io.flutter.embedding.android.AndroidTouchProcessor;
28import io.flutter.embedding.android.FlutterView;
29import io.flutter.embedding.android.MotionEventTracker;
30import io.flutter.embedding.engine.FlutterOverlaySurface;
31import io.flutter.embedding.engine.dart.DartExecutor;
32import io.flutter.embedding.engine.mutatorsstack.*;
33import io.flutter.embedding.engine.renderer.FlutterRenderer;
34import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel;
35import io.flutter.plugin.editing.TextInputPlugin;
36import io.flutter.util.ViewUtils;
37import io.flutter.view.AccessibilityBridge;
38import io.flutter.view.TextureRegistry;
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.List;
43
44/**
45 * Manages platform views.
46 *
47 * <p>Each {@link io.flutter.embedding.engine.FlutterEngine} or {@link
48 * io.flutter.app.FlutterPluginRegistry} has a single platform views controller. A platform views
49 * controller can be attached to at most one Flutter view.
50 */
52 private static final String TAG = "PlatformViewsController";
53
54 // These view types allow out-of-band drawing commands that don't notify the Android view
55 // hierarchy.
56 // To support these cases, Flutter hosts the embedded view in a VirtualDisplay,
57 // and binds the VirtualDisplay to a GL texture that is then composed by the engine.
58 // However, there are a few issues with Virtual Displays. For example, they don't fully support
59 // accessibility due to https://github.com/flutter/flutter/issues/29717,
60 // and keyboard interactions may have non-deterministic behavior.
61 // Views that issue out-of-band drawing commands that aren't included in this array are
62 // required to call `View#invalidate()` to notify Flutter about the update.
63 // This isn't ideal, but given all the other limitations it's a reasonable tradeoff.
64 // Related issue: https://github.com/flutter/flutter/issues/103630
65 private static Class[] VIEW_TYPES_REQUIRE_VIRTUAL_DISPLAY = {SurfaceView.class};
66
67 private final PlatformViewRegistryImpl registry;
68
69 private AndroidTouchProcessor androidTouchProcessor;
70
71 // The context of the Activity or Fragment hosting the render target for the Flutter engine.
72 private Context context;
73
74 // The View currently rendering the Flutter UI associated with these platform views.
75 private FlutterView flutterView;
76
77 // The texture registry maintaining the textures into which the embedded views will be rendered.
78 @Nullable private TextureRegistry textureRegistry;
79
80 @Nullable private TextInputPlugin textInputPlugin;
81
82 // The system channel used to communicate with the framework about platform views.
83 private PlatformViewsChannel platformViewsChannel;
84
85 // The accessibility bridge to which accessibility events form the platform views will be
86 // dispatched.
87 private final AccessibilityEventsDelegate accessibilityEventsDelegate;
88
89 // TODO(mattcarroll): Refactor overall platform views to facilitate testing and then make
90 // this private. This is visible as a hack to facilitate testing. This was deemed the least
91 // bad option at the time of writing.
92 @VisibleForTesting /* package */ final HashMap<Integer, VirtualDisplayController> vdControllers;
93
94 // Maps a virtual display's context to the embedded view hosted in this virtual display.
95 // Since each virtual display has it's unique context this allows associating any view with the
96 // platform view that
97 // it is associated with(e.g if a platform view creates other views in the same virtual display.
98 @VisibleForTesting /* package */ final HashMap<Context, View> contextToEmbeddedView;
99
100 // The platform views.
101 private final SparseArray<PlatformView> platformViews;
102
103 // The platform view wrappers that are appended to FlutterView.
104 //
105 // These platform views use a PlatformViewLayer in the framework. This is different than
106 // the platform views that use a TextureLayer.
107 //
108 // This distinction is necessary because a PlatformViewLayer allows to embed Android's
109 // SurfaceViews in a Flutter app whereas the texture layer is unable to support such native views.
110 //
111 // If an entry in `platformViews` doesn't have an entry in this array, the platform view isn't
112 // in the view hierarchy.
113 //
114 // This view provides a wrapper that applies scene builder operations to the platform view.
115 // For example, a transform matrix, or setting opacity on the platform view layer.
116 private final SparseArray<FlutterMutatorView> platformViewParent;
117
118 // Map of unique IDs to views that render overlay layers.
119 private final SparseArray<PlatformOverlayView> overlayLayerViews;
120
121 // The platform view wrappers that are appended to FlutterView.
122 //
123 // These platform views use a TextureLayer in the framework. This is different than
124 // the platform views that use a PlatformViewLayer.
125 //
126 // This is the default mode, and recommended for better performance.
127 private final SparseArray<PlatformViewWrapper> viewWrappers;
128
129 // Next available unique ID for use in overlayLayerViews.
130 private int nextOverlayLayerId = 0;
131
132 // Tracks whether the flutterView has been converted to use a FlutterImageView.
133 private boolean flutterViewConvertedToImageView = false;
134
135 // When adding platform views using Hybrid Composition, the engine converts the render surface
136 // to a FlutterImageView to help improve animation synchronization on Android. This flag allows
137 // disabling this conversion through the PlatformView platform channel.
138 private boolean synchronizeToNativeViewHierarchy = true;
139
140 // Overlay layer IDs that were displayed since the start of the current frame.
141 private final HashSet<Integer> currentFrameUsedOverlayLayerIds;
142
143 // Platform view IDs that were displayed since the start of the current frame.
144 private final HashSet<Integer> currentFrameUsedPlatformViewIds;
145
146 // Used to acquire the original motion events using the motionEventIds.
147 private final MotionEventTracker motionEventTracker;
148
149 // Whether software rendering is used.
150 private boolean usesSoftwareRendering = false;
151
152 private static boolean enableImageRenderTarget = true;
153
154 private static boolean enableSurfaceProducerRenderTarget = true;
155
156 private final PlatformViewsChannel.PlatformViewsHandler channelHandler =
158
159 @Override
160 // TODO(egarciad): Remove the need for this.
161 // https://github.com/flutter/flutter/issues/96679
162 public void createForPlatformViewLayer(
164 // API level 19 is required for `android.graphics.ImageReader`.
165 enforceMinimumAndroidApiVersion(19);
166 ensureValidRequest(request);
167
168 final PlatformView platformView = createPlatformView(request, false);
169
170 configureForHybridComposition(platformView, request);
171 // New code should be added to configureForHybridComposition, not here, unless it is
172 // not applicable to fallback from TLHC to HC.
173 }
174
175 @Override
176 public long createForTextureLayer(
178 ensureValidRequest(request);
179 final int viewId = request.viewId;
180 if (viewWrappers.get(viewId) != null) {
181 throw new IllegalStateException(
182 "Trying to create an already created platform view, view id: " + viewId);
183 }
184 if (textureRegistry == null) {
185 throw new IllegalStateException(
186 "Texture registry is null. This means that platform views controller was detached,"
187 + " view id: "
188 + viewId);
189 }
190 if (flutterView == null) {
191 throw new IllegalStateException(
192 "Flutter view is null. This means the platform views controller doesn't have an"
193 + " attached view, view id: "
194 + viewId);
195 }
196
197 final PlatformView platformView = createPlatformView(request, true);
198
199 final View embeddedView = platformView.getView();
200 if (embeddedView.getParent() != null) {
201 throw new IllegalStateException(
202 "The Android view returned from PlatformView#getView() was already added to a"
203 + " parent view.");
204 }
205
206 // The newer Texture Layer Hybrid Composition mode isn't suppported if any of the
207 // following are true:
208 // - The embedded view contains any of the VIEW_TYPES_REQUIRE_VIRTUAL_DISPLAY view types.
209 // These views allow out-of-band graphics operations that aren't notified to the Android
210 // view hierarchy via callbacks such as ViewParent#onDescendantInvalidated().
211 // - The API level is <23, due to TLHC implementation API requirements.
212 final boolean supportsTextureLayerMode =
213 Build.VERSION.SDK_INT >= API_LEVELS.API_23
215 embeddedView, VIEW_TYPES_REQUIRE_VIRTUAL_DISPLAY);
216
217 // Fall back to Hybrid Composition or Virtual Display when necessary, depending on which
218 // fallback mode is requested.
219 if (!supportsTextureLayerMode) {
220 if (request.displayMode
223 configureForHybridComposition(platformView, request);
225 } else if (!usesSoftwareRendering) { // Virtual Display doesn't support software mode.
226 return configureForVirtualDisplay(platformView, request);
227 }
228 // TODO(stuartmorgan): Consider throwing a specific exception here as a breaking change.
229 // For now, preserve the 3.0 behavior of falling through to Texture Layer mode even
230 // though it won't work correctly.
231 }
232 return configureForTextureLayerComposition(platformView, request);
233 }
234
235 @Override
236 public void dispose(int viewId) {
237 final PlatformView platformView = platformViews.get(viewId);
238 if (platformView == null) {
239 Log.e(TAG, "Disposing unknown platform view with id: " + viewId);
240 return;
241 }
242 if (platformView.getView() != null) {
243 final View embeddedView = platformView.getView();
244 final ViewGroup pvParent = (ViewGroup) embeddedView.getParent();
245 if (pvParent != null) {
246 // Eagerly remove the embedded view from the PlatformViewWrapper.
247 // Without this call, we see some crashes because removing the view
248 // is used as a signal to stop processing.
249 pvParent.removeView(embeddedView);
250 }
251 }
252 platformViews.remove(viewId);
253 try {
254 platformView.dispose();
255 } catch (RuntimeException exception) {
256 Log.e(TAG, "Disposing platform view threw an exception", exception);
257 }
258 if (usesVirtualDisplay(viewId)) {
259 final VirtualDisplayController vdController = vdControllers.get(viewId);
260 final View embeddedView = vdController.getView();
261 if (embeddedView != null) {
262 contextToEmbeddedView.remove(embeddedView.getContext());
263 }
264 vdController.dispose();
265 vdControllers.remove(viewId);
266 return;
267 }
268 // The platform view is displayed using a TextureLayer and is inserted in the view
269 // hierarchy.
270 final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId);
271 if (viewWrapper != null) {
272 viewWrapper.removeAllViews();
273 viewWrapper.release();
275
276 final ViewGroup wrapperParent = (ViewGroup) viewWrapper.getParent();
277 if (wrapperParent != null) {
278 wrapperParent.removeView(viewWrapper);
279 }
280 viewWrappers.remove(viewId);
281 return;
282 }
283 // The platform view is displayed using a PlatformViewLayer.
284 final FlutterMutatorView parentView = platformViewParent.get(viewId);
285 if (parentView != null) {
286 parentView.removeAllViews();
288
289 final ViewGroup mutatorViewParent = (ViewGroup) parentView.getParent();
290 if (mutatorViewParent != null) {
291 mutatorViewParent.removeView(parentView);
292 }
293 platformViewParent.remove(viewId);
294 }
295 }
296
297 @Override
298 public void offset(int viewId, double top, double left) {
299 if (usesVirtualDisplay(viewId)) {
300 // Virtual displays don't need an accessibility offset.
301 return;
302 }
303 // For platform views that use TextureView and are in the view hierarchy, set
304 // an offset to the wrapper view.
305 // This ensures that the accessibility highlights are drawn in the expected position on
306 // screen.
307 // This offset doesn't affect the position of the embeded view by itself since the GL
308 // texture is positioned by the Flutter engine, which knows where to position different
309 // types of layers.
310 final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId);
311 if (viewWrapper == null) {
312 Log.e(TAG, "Setting offset for unknown platform view with id: " + viewId);
313 return;
314 }
315 final int physicalTop = toPhysicalPixels(top);
316 final int physicalLeft = toPhysicalPixels(left);
317 final FrameLayout.LayoutParams layoutParams =
318 (FrameLayout.LayoutParams) viewWrapper.getLayoutParams();
319 layoutParams.topMargin = physicalTop;
320 layoutParams.leftMargin = physicalLeft;
321 viewWrapper.setLayoutParams(layoutParams);
322 }
323
324 @Override
325 public void resize(
328 final int physicalWidth = toPhysicalPixels(request.newLogicalWidth);
329 final int physicalHeight = toPhysicalPixels(request.newLogicalHeight);
330 final int viewId = request.viewId;
331
332 if (usesVirtualDisplay(viewId)) {
333 final float originalDisplayDensity = getDisplayDensity();
334 final VirtualDisplayController vdController = vdControllers.get(viewId);
335 // Resizing involved moving the platform view to a new virtual display. Doing so
336 // potentially results in losing an active input connection. To make sure we preserve
337 // the input connection when resizing we lock it here and unlock after the resize is
338 // complete.
339 lockInputConnection(vdController);
340 vdController.resize(
341 physicalWidth,
342 physicalHeight,
343 () -> {
344 unlockInputConnection(vdController);
345 // Converting back to logic pixels requires a context, which may no longer be
346 // available. If that happens, assume the same logic/physical relationship as
347 // was present when the request arrived.
348 final float displayDensity =
349 context == null ? originalDisplayDensity : getDisplayDensity();
350 onComplete.run(
352 toLogicalPixels(vdController.getRenderTargetWidth(), displayDensity),
353 toLogicalPixels(vdController.getRenderTargetHeight(), displayDensity)));
354 });
355 return;
356 }
357
358 final PlatformView platformView = platformViews.get(viewId);
359 final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId);
360 if (platformView == null || viewWrapper == null) {
361 Log.e(TAG, "Resizing unknown platform view with id: " + viewId);
362 return;
363 }
364 // Resize the buffer only when the current buffer size is smaller than the new size.
365 // This is required to prevent a situation when smooth keyboard animation
366 // resizes the texture too often, such that the GPU and the platform thread don't agree on
367 // the timing of the new size.
368 // Resizing the texture causes pixel stretching since the size of the GL texture used in
369 // the engine is set by the framework, but the texture buffer size is set by the
370 // platform down below.
371 if (physicalWidth > viewWrapper.getRenderTargetWidth()
372 || physicalHeight > viewWrapper.getRenderTargetHeight()) {
373 viewWrapper.resizeRenderTarget(physicalWidth, physicalHeight);
374 }
375
376 final ViewGroup.LayoutParams viewWrapperLayoutParams = viewWrapper.getLayoutParams();
377 viewWrapperLayoutParams.width = physicalWidth;
378 viewWrapperLayoutParams.height = physicalHeight;
379 viewWrapper.setLayoutParams(viewWrapperLayoutParams);
380
381 final View embeddedView = platformView.getView();
382 if (embeddedView != null) {
383 final ViewGroup.LayoutParams embeddedViewLayoutParams = embeddedView.getLayoutParams();
384 embeddedViewLayoutParams.width = physicalWidth;
385 embeddedViewLayoutParams.height = physicalHeight;
386 embeddedView.setLayoutParams(embeddedViewLayoutParams);
387 }
388 onComplete.run(
390 toLogicalPixels(viewWrapper.getRenderTargetWidth()),
391 toLogicalPixels(viewWrapper.getRenderTargetHeight())));
392 }
393
394 @Override
395 public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) {
396 final int viewId = touch.viewId;
397 final float density = context.getResources().getDisplayMetrics().density;
398
399 if (usesVirtualDisplay(viewId)) {
400 final VirtualDisplayController vdController = vdControllers.get(viewId);
401 final MotionEvent event = toMotionEvent(density, touch, true);
402 vdController.dispatchTouchEvent(event);
403 return;
404 }
405
406 final PlatformView platformView = platformViews.get(viewId);
407 if (platformView == null) {
408 Log.e(TAG, "Sending touch to an unknown view with id: " + viewId);
409 return;
410 }
411 final View view = platformView.getView();
412 if (view == null) {
413 Log.e(TAG, "Sending touch to a null view with id: " + viewId);
414 return;
415 }
416 final MotionEvent event = toMotionEvent(density, touch, false);
417 view.dispatchTouchEvent(event);
418 }
419
420 @Override
421 public void setDirection(int viewId, int direction) {
422 if (!validateDirection(direction)) {
423 throw new IllegalStateException(
424 "Trying to set unknown direction value: "
425 + direction
426 + "(view id: "
427 + viewId
428 + ")");
429 }
430
431 View embeddedView;
432
433 if (usesVirtualDisplay(viewId)) {
434 final VirtualDisplayController controller = vdControllers.get(viewId);
435 embeddedView = controller.getView();
436 } else {
437 final PlatformView platformView = platformViews.get(viewId);
438 if (platformView == null) {
439 Log.e(TAG, "Setting direction to an unknown view with id: " + viewId);
440 return;
441 }
442 embeddedView = platformView.getView();
443 }
444 if (embeddedView == null) {
445 Log.e(TAG, "Setting direction to a null view with id: " + viewId);
446 return;
447 }
448 embeddedView.setLayoutDirection(direction);
449 }
450
451 @Override
452 public void clearFocus(int viewId) {
453 View embeddedView;
454
455 if (usesVirtualDisplay(viewId)) {
456 final VirtualDisplayController controller = vdControllers.get(viewId);
457 embeddedView = controller.getView();
458 } else {
459 final PlatformView platformView = platformViews.get(viewId);
460 if (platformView == null) {
461 Log.e(TAG, "Clearing focus on an unknown view with id: " + viewId);
462 return;
463 }
464 embeddedView = platformView.getView();
465 }
466 if (embeddedView == null) {
467 Log.e(TAG, "Clearing focus on a null view with id: " + viewId);
468 return;
469 }
470 embeddedView.clearFocus();
471 }
472
473 @Override
474 public void synchronizeToNativeViewHierarchy(boolean yes) {
475 synchronizeToNativeViewHierarchy = yes;
476 }
477 };
478
479 /// Throws an exception if the SDK version is below minSdkVersion.
480 private void enforceMinimumAndroidApiVersion(int minSdkVersion) {
481 if (Build.VERSION.SDK_INT < minSdkVersion) {
482 throw new IllegalStateException(
483 "Trying to use platform views with API "
484 + Build.VERSION.SDK_INT
485 + ", required API level is: "
486 + minSdkVersion);
487 }
488 }
489
490 private void ensureValidRequest(
491 @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
492 if (!validateDirection(request.direction)) {
493 throw new IllegalStateException(
494 "Trying to create a view with unknown direction value: "
495 + request.direction
496 + "(view id: "
497 + request.viewId
498 + ")");
499 }
500 }
501
502 // Creates a platform view based on `request`, performs configuration that's common to
503 // all display modes, and adds it to `platformViews`.
504 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
506 @NonNull PlatformViewsChannel.PlatformViewCreationRequest request, boolean wrapContext) {
507 final PlatformViewFactory viewFactory = registry.getFactory(request.viewType);
508 if (viewFactory == null) {
509 throw new IllegalStateException(
510 "Trying to create a platform view of unregistered type: " + request.viewType);
511 }
512
513 Object createParams = null;
514 if (request.params != null) {
515 createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params);
516 }
517
518 // In some display modes, the context needs to be modified during display.
519 // TODO(stuartmorgan): Make this wrapping unconditional if possible; for context see
520 // https://github.com/flutter/flutter/issues/113449
521 final Context mutableContext = wrapContext ? new MutableContextWrapper(context) : context;
522 final PlatformView platformView =
523 viewFactory.create(mutableContext, request.viewId, createParams);
524
525 // Configure the view to match the requested layout direction.
526 final View embeddedView = platformView.getView();
527 if (embeddedView == null) {
528 throw new IllegalStateException(
529 "PlatformView#getView() returned null, but an Android view reference was expected.");
530 }
531 embeddedView.setLayoutDirection(request.direction);
532 platformViews.put(request.viewId, platformView);
533 maybeInvokeOnFlutterViewAttached(platformView);
534 return platformView;
535 }
536
537 // Configures the view for Hybrid Composition mode.
538 private void configureForHybridComposition(
539 @NonNull PlatformView platformView,
541 enforceMinimumAndroidApiVersion(19);
542 Log.i(TAG, "Using hybrid composition for platform view: " + request.viewId);
543 }
544
545 // Configures the view for Virtual Display mode, returning the associated texture ID.
546 private long configureForVirtualDisplay(
547 @NonNull PlatformView platformView,
548 @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
549 // This mode adds the view to a virtual display, which is wired up to a GL texture that
550 // is composed by the Flutter engine.
551
552 // API level 20 is required to use VirtualDisplay#setSurface.
553 enforceMinimumAndroidApiVersion(20);
554
555 Log.i(TAG, "Hosting view in a virtual display for platform view: " + request.viewId);
556
557 final PlatformViewRenderTarget renderTarget = makePlatformViewRenderTarget(textureRegistry);
558 final int physicalWidth = toPhysicalPixels(request.logicalWidth);
559 final int physicalHeight = toPhysicalPixels(request.logicalHeight);
560 final VirtualDisplayController vdController =
561 VirtualDisplayController.create(
562 context,
563 accessibilityEventsDelegate,
564 platformView,
565 renderTarget,
566 physicalWidth,
567 physicalHeight,
568 request.viewId,
569 null,
570 (view, hasFocus) -> {
571 if (hasFocus) {
572 platformViewsChannel.invokeViewFocused(request.viewId);
573 }
574 });
575
576 if (vdController == null) {
577 throw new IllegalStateException(
578 "Failed creating virtual display for a "
579 + request.viewType
580 + " with id: "
581 + request.viewId);
582 }
583
584 // The embedded view doesn't need to be sized in Virtual Display mode because the
585 // virtual display itself is sized.
586
587 vdControllers.put(request.viewId, vdController);
588 final View embeddedView = platformView.getView();
589 contextToEmbeddedView.put(embeddedView.getContext(), embeddedView);
590
591 return renderTarget.getId();
592 }
593
594 // Configures the view for Texture Layer Hybrid Composition mode, returning the associated
595 // texture ID.
596 @TargetApi(API_LEVELS.API_23)
597 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
598 public long configureForTextureLayerComposition(
599 @NonNull PlatformView platformView,
600 @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
601 // This mode attaches the view to the Android view hierarchy and record its drawing
602 // operations, so they can be forwarded to a GL texture that is composed by the
603 // Flutter engine.
604
605 // API level 23 is required to use Surface#lockHardwareCanvas().
606 enforceMinimumAndroidApiVersion(23);
607 Log.i(TAG, "Hosting view in view hierarchy for platform view: " + request.viewId);
608
609 final int physicalWidth = toPhysicalPixels(request.logicalWidth);
610 final int physicalHeight = toPhysicalPixels(request.logicalHeight);
611 PlatformViewWrapper viewWrapper;
612 long textureId;
613 if (usesSoftwareRendering) {
614 viewWrapper = new PlatformViewWrapper(context);
615 textureId = -1;
616 } else {
617 final PlatformViewRenderTarget renderTarget = makePlatformViewRenderTarget(textureRegistry);
618 viewWrapper = new PlatformViewWrapper(context, renderTarget);
619 textureId = renderTarget.getId();
620 }
621 viewWrapper.setTouchProcessor(androidTouchProcessor);
622 viewWrapper.resizeRenderTarget(physicalWidth, physicalHeight);
623
624 final FrameLayout.LayoutParams viewWrapperLayoutParams =
625 new FrameLayout.LayoutParams(physicalWidth, physicalHeight);
626
627 // Size and position the view wrapper.
628 final int physicalTop = toPhysicalPixels(request.logicalTop);
629 final int physicalLeft = toPhysicalPixels(request.logicalLeft);
630 viewWrapperLayoutParams.topMargin = physicalTop;
631 viewWrapperLayoutParams.leftMargin = physicalLeft;
632 viewWrapper.setLayoutParams(viewWrapperLayoutParams);
633
634 // Size the embedded view.
635 final View embeddedView = platformView.getView();
636 embeddedView.setLayoutParams(new FrameLayout.LayoutParams(physicalWidth, physicalHeight));
637
638 // Accessibility in the embedded view is initially disabled because if a Flutter app
639 // disabled accessibility in the first frame, the embedding won't receive an update to
640 // disable accessibility since the embedding never received an update to enable it.
641 // The AccessibilityBridge keeps track of the accessibility nodes, and handles the deltas
642 // when the framework sends a new a11y tree to the embedding.
643 // To prevent races, the framework populate the SemanticsNode after the platform view has
644 // been created.
645 embeddedView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
646
647 // Add the embedded view to the wrapper.
648 viewWrapper.addView(embeddedView);
649
650 // Listen for focus changed in any subview, so the framework is notified when the platform
651 // view is focused.
653 (v, hasFocus) -> {
654 if (hasFocus) {
655 platformViewsChannel.invokeViewFocused(request.viewId);
656 } else if (textInputPlugin != null) {
657 textInputPlugin.clearPlatformViewClient(request.viewId);
658 }
659 });
660
661 flutterView.addView(viewWrapper);
662 viewWrappers.append(request.viewId, viewWrapper);
663
664 maybeInvokeOnFlutterViewAttached(platformView);
665
666 return textureId;
667 }
668
669 /**
670 * Translates an original touch event to have the same locations as the ones that Flutter
671 * calculates (because original + flutter's - original = flutter's).
672 *
673 * @param originalEvent The saved original input event.
674 * @param pointerCoords The coordinates that Flutter thinks the touch is happening at.
675 */
676 private static void translateMotionEvent(
677 MotionEvent originalEvent, PointerCoords[] pointerCoords) {
678 if (pointerCoords.length < 1) {
679 return;
680 }
681
682 float xOffset = pointerCoords[0].x - originalEvent.getX();
683 float yOffset = pointerCoords[0].y - originalEvent.getY();
684
685 originalEvent.offsetLocation(xOffset, yOffset);
686 }
687
688 @VisibleForTesting
689 public MotionEvent toMotionEvent(
690 float density, PlatformViewsChannel.PlatformViewTouch touch, boolean usingVirtualDiplay) {
691 MotionEventTracker.MotionEventId motionEventId =
692 MotionEventTracker.MotionEventId.from(touch.motionEventId);
693 MotionEvent trackedEvent = motionEventTracker.pop(motionEventId);
694
695 // Pointer coordinates in the tracked events are global to FlutterView
696 // The framework converts them to be local to a widget, given that
697 // motion events operate on local coords, we need to replace these in the tracked
698 // event with their local counterparts.
699 // Compute this early so it can be used as input to translateNonVirtualDisplayMotionEvent.
700 PointerCoords[] pointerCoords =
701 parsePointerCoordsList(touch.rawPointerCoords, density)
702 .toArray(new PointerCoords[touch.pointerCount]);
703
704 if (!usingVirtualDiplay && trackedEvent != null) {
705 // We have the original event, deliver it after offsetting as it will pass the verifiable
706 // input check.
707 translateMotionEvent(trackedEvent, pointerCoords);
708 return trackedEvent;
709 }
710 // We are in virtual display mode or don't have a reference to the original MotionEvent.
711 // In this case we manually recreate a MotionEvent to be delivered. This MotionEvent
712 // will fail the verifiable input check.
713 PointerProperties[] pointerProperties =
714 parsePointerPropertiesList(touch.rawPointerPropertiesList)
715 .toArray(new PointerProperties[touch.pointerCount]);
716
717 // TODO (kaushikiska) : warn that we are potentially using an untracked
718 // event in the platform views.
719 return MotionEvent.obtain(
720 touch.downTime.longValue(),
721 touch.eventTime.longValue(),
722 touch.action,
723 touch.pointerCount,
724 pointerProperties,
725 pointerCoords,
726 touch.metaState,
727 touch.buttonState,
728 touch.xPrecision,
729 touch.yPrecision,
730 touch.deviceId,
731 touch.edgeFlags,
732 touch.source,
733 touch.flags);
734 }
735
737 registry = new PlatformViewRegistryImpl();
738 vdControllers = new HashMap<>();
739 accessibilityEventsDelegate = new AccessibilityEventsDelegate();
740 contextToEmbeddedView = new HashMap<>();
741 overlayLayerViews = new SparseArray<>();
742 currentFrameUsedOverlayLayerIds = new HashSet<>();
743 currentFrameUsedPlatformViewIds = new HashSet<>();
744 viewWrappers = new SparseArray<>();
745 platformViews = new SparseArray<>();
746 platformViewParent = new SparseArray<>();
747
748 motionEventTracker = MotionEventTracker.getInstance();
749 }
750
751 /**
752 * Attaches this platform views controller to its input and output channels.
753 *
754 * @param context The base context that will be passed to embedded views created by this
755 * controller. This should be the context of the Activity hosting the Flutter application.
756 * @param textureRegistry The texture registry which provides the output textures into which the
757 * embedded views will be rendered.
758 * @param dartExecutor The dart execution context, which is used to set up a system channel.
759 */
760 public void attach(
761 @Nullable Context context,
762 @NonNull TextureRegistry textureRegistry,
763 @NonNull DartExecutor dartExecutor) {
764 if (this.context != null) {
765 throw new AssertionError(
766 "A PlatformViewsController can only be attached to a single output target.\n"
767 + "attach was called while the PlatformViewsController was already attached.");
768 }
769 this.context = context;
770 this.textureRegistry = textureRegistry;
771 platformViewsChannel = new PlatformViewsChannel(dartExecutor);
772 platformViewsChannel.setPlatformViewsHandler(channelHandler);
773 }
774
775 /**
776 * Sets whether Flutter uses software rendering.
777 *
778 * <p>When software rendering is used, no GL context is available on the raster thread. When this
779 * is set to true, there's no Flutter composition of Android views and Flutter widgets since GL
780 * textures cannot be used.
781 *
782 * <p>Software rendering is only used for testing in emulators, and it should never be set to true
783 * in a production environment.
784 *
785 * @param useSoftwareRendering Whether software rendering is used.
786 */
787 public void setSoftwareRendering(boolean useSoftwareRendering) {
788 usesSoftwareRendering = useSoftwareRendering;
789 }
790
791 /**
792 * Detaches this platform views controller.
793 *
794 * <p>This is typically called when a Flutter applications moves to run in the background, or is
795 * destroyed. After calling this the platform views controller will no longer listen to it's
796 * previous messenger, and will not maintain references to the texture registry, context, and
797 * messenger passed to the previous attach call.
798 */
799 @UiThread
800 public void detach() {
801 if (platformViewsChannel != null) {
802 platformViewsChannel.setPlatformViewsHandler(null);
803 }
804 destroyOverlaySurfaces();
805 platformViewsChannel = null;
806 context = null;
807 textureRegistry = null;
808 }
809
810 /**
811 * Attaches the controller to a {@link FlutterView}.
812 *
813 * <p>When {@link io.flutter.embedding.android.FlutterFragment} is used, this method is called
814 * after the device rotates since the FlutterView is recreated after a rotation.
815 */
816 public void attachToView(@NonNull FlutterView newFlutterView) {
817 flutterView = newFlutterView;
818 // Add wrapper for platform views that use GL texture.
819 for (int index = 0; index < viewWrappers.size(); index++) {
820 final PlatformViewWrapper view = viewWrappers.valueAt(index);
821 flutterView.addView(view);
822 }
823 // Add wrapper for platform views that are composed at the view hierarchy level.
824 for (int index = 0; index < platformViewParent.size(); index++) {
825 final FlutterMutatorView view = platformViewParent.valueAt(index);
826 flutterView.addView(view);
827 }
828 // Notify platform views that they are now attached to a FlutterView.
829 for (int index = 0; index < platformViews.size(); index++) {
830 final PlatformView view = platformViews.valueAt(index);
831 view.onFlutterViewAttached(flutterView);
832 }
833 }
834
835 /**
836 * Detaches the controller from {@link FlutterView}.
837 *
838 * <p>When {@link io.flutter.embedding.android.FlutterFragment} is used, this method is called
839 * when the device rotates since the FlutterView is detached from the fragment. The next time the
840 * fragment needs to be displayed, a new Flutter view is created, so attachToView is called again.
841 */
842 public void detachFromView() {
843 // Remove wrapper for platform views that use GL texture.
844 for (int index = 0; index < viewWrappers.size(); index++) {
845 final PlatformViewWrapper view = viewWrappers.valueAt(index);
846 flutterView.removeView(view);
847 }
848 // Remove wrapper for platform views that are composed at the view hierarchy level.
849 for (int index = 0; index < platformViewParent.size(); index++) {
850 final FlutterMutatorView view = platformViewParent.valueAt(index);
851 flutterView.removeView(view);
852 }
853
854 destroyOverlaySurfaces();
855 removeOverlaySurfaces();
856 flutterView = null;
857 flutterViewConvertedToImageView = false;
858
859 // Notify that the platform view have been detached from FlutterView.
860 for (int index = 0; index < platformViews.size(); index++) {
861 final PlatformView view = platformViews.valueAt(index);
863 }
864 }
865
866 private void maybeInvokeOnFlutterViewAttached(PlatformView view) {
867 if (flutterView == null) {
868 Log.i(TAG, "null flutterView");
869 // There is currently no FlutterView that we are attached to.
870 return;
871 }
872 view.onFlutterViewAttached(flutterView);
873 }
874
875 @Override
876 public void attachAccessibilityBridge(@NonNull AccessibilityBridge accessibilityBridge) {
877 accessibilityEventsDelegate.setAccessibilityBridge(accessibilityBridge);
878 }
879
880 @Override
882 accessibilityEventsDelegate.setAccessibilityBridge(null);
883 }
884
885 /**
886 * Attaches this controller to a text input plugin.
887 *
888 * <p>While a text input plugin is available, the platform views controller interacts with it to
889 * facilitate delegation of text input connections to platform views.
890 *
891 * <p>A platform views controller should be attached to a text input plugin whenever it is
892 * possible for the Flutter framework to receive text input.
893 */
895 this.textInputPlugin = textInputPlugin;
896 }
897
898 /** Detaches this controller from the currently attached text input plugin. */
899 public void detachTextInputPlugin() {
900 textInputPlugin = null;
901 }
902
903 /**
904 * Returns true if Flutter should perform input connection proxying for the view.
905 *
906 * <p>If the view is a platform view managed by this platform views controller returns true. Else
907 * if the view was created in a platform view's VD, delegates the decision to the platform view's
908 * {@link View#checkInputConnectionProxy(View)} method. Else returns false.
909 */
910 public boolean checkInputConnectionProxy(@Nullable View view) {
911 // View can be null on some devices
912 // See: https://github.com/flutter/flutter/issues/36517
913 if (view == null) {
914 return false;
915 }
916 if (!contextToEmbeddedView.containsKey(view.getContext())) {
917 return false;
918 }
919 View platformView = contextToEmbeddedView.get(view.getContext());
920 if (platformView == view) {
921 return true;
922 }
923 return platformView.checkInputConnectionProxy(view);
924 }
925
927 return registry;
928 }
929
930 /**
931 * Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link
932 * PlatformViewsController} attaches to JNI.
933 */
934 public void onAttachedToJNI() {
935 // Currently no action needs to be taken after JNI attachment.
936 }
937
938 /**
939 * Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link
940 * PlatformViewsController} detaches from JNI.
941 */
942 public void onDetachedFromJNI() {
943 diposeAllViews();
944 }
945
946 public void onPreEngineRestart() {
947 diposeAllViews();
948 }
949
950 @Override
951 @Nullable
952 public View getPlatformViewById(int viewId) {
953 if (usesVirtualDisplay(viewId)) {
954 final VirtualDisplayController controller = vdControllers.get(viewId);
955 return controller.getView();
956 }
957
958 final PlatformView platformView = platformViews.get(viewId);
959 if (platformView == null) {
960 return null;
961 }
962 return platformView.getView();
963 }
964
965 @Override
966 public boolean usesVirtualDisplay(int id) {
967 return vdControllers.containsKey(id);
968 }
969
970 private void lockInputConnection(@NonNull VirtualDisplayController controller) {
971 if (textInputPlugin == null) {
972 return;
973 }
974 textInputPlugin.lockPlatformViewInputConnection();
975 controller.onInputConnectionLocked();
976 }
977
978 private void unlockInputConnection(@NonNull VirtualDisplayController controller) {
979 if (textInputPlugin == null) {
980 return;
981 }
982 textInputPlugin.unlockPlatformViewInputConnection();
983 controller.onInputConnectionUnlocked();
984 }
985
986 private static PlatformViewRenderTarget makePlatformViewRenderTarget(
987 TextureRegistry textureRegistry) {
988 if (enableSurfaceProducerRenderTarget && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
989 final TextureRegistry.SurfaceProducer textureEntry = textureRegistry.createSurfaceProducer();
990 Log.i(TAG, "PlatformView is using SurfaceProducer backend");
991 return new SurfaceProducerPlatformViewRenderTarget(textureEntry);
992 }
993 if (enableImageRenderTarget && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
994 final TextureRegistry.ImageTextureEntry textureEntry = textureRegistry.createImageTexture();
995 Log.i(TAG, "PlatformView is using ImageReader backend");
996 return new ImageReaderPlatformViewRenderTarget(textureEntry);
997 }
998 final TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture();
999 Log.i(TAG, "PlatformView is using SurfaceTexture backend");
1000 return new SurfaceTexturePlatformViewRenderTarget(textureEntry);
1001 }
1002
1003 private static boolean validateDirection(int direction) {
1004 return direction == View.LAYOUT_DIRECTION_LTR || direction == View.LAYOUT_DIRECTION_RTL;
1005 }
1006
1007 @SuppressWarnings("unchecked")
1008 private static List<PointerProperties> parsePointerPropertiesList(Object rawPropertiesList) {
1009 List<Object> rawProperties = (List<Object>) rawPropertiesList;
1010 List<PointerProperties> pointerProperties = new ArrayList<>();
1011 for (Object o : rawProperties) {
1012 pointerProperties.add(parsePointerProperties(o));
1013 }
1014 return pointerProperties;
1015 }
1016
1017 @SuppressWarnings("unchecked")
1018 private static PointerProperties parsePointerProperties(Object rawProperties) {
1019 List<Object> propertiesList = (List<Object>) rawProperties;
1020 PointerProperties properties = new MotionEvent.PointerProperties();
1021 properties.id = (int) propertiesList.get(0);
1022 properties.toolType = (int) propertiesList.get(1);
1023 return properties;
1024 }
1025
1026 @SuppressWarnings("unchecked")
1027 private static List<PointerCoords> parsePointerCoordsList(Object rawCoordsList, float density) {
1028 List<Object> rawCoords = (List<Object>) rawCoordsList;
1029 List<PointerCoords> pointerCoords = new ArrayList<>();
1030 for (Object o : rawCoords) {
1031 pointerCoords.add(parsePointerCoords(o, density));
1032 }
1033 return pointerCoords;
1034 }
1035
1036 @SuppressWarnings("unchecked")
1037 private static PointerCoords parsePointerCoords(Object rawCoords, float density) {
1038 List<Object> coordsList = (List<Object>) rawCoords;
1039 PointerCoords coords = new MotionEvent.PointerCoords();
1040 coords.orientation = (float) (double) coordsList.get(0);
1041 coords.pressure = (float) (double) coordsList.get(1);
1042 coords.size = (float) (double) coordsList.get(2);
1043 coords.toolMajor = (float) ((double) coordsList.get(3) * density);
1044 coords.toolMinor = (float) ((double) coordsList.get(4) * density);
1045 coords.touchMajor = (float) ((double) coordsList.get(5) * density);
1046 coords.touchMinor = (float) ((double) coordsList.get(6) * density);
1047 coords.x = (float) ((double) coordsList.get(7) * density);
1048 coords.y = (float) ((double) coordsList.get(8) * density);
1049 return coords;
1050 }
1051
1052 private float getDisplayDensity() {
1053 return context.getResources().getDisplayMetrics().density;
1054 }
1055
1056 private int toPhysicalPixels(double logicalPixels) {
1057 return (int) Math.round(logicalPixels * getDisplayDensity());
1058 }
1059
1060 private int toLogicalPixels(double physicalPixels, float displayDensity) {
1061 return (int) Math.round(physicalPixels / displayDensity);
1062 }
1063
1064 private int toLogicalPixels(double physicalPixels) {
1065 return toLogicalPixels(physicalPixels, getDisplayDensity());
1066 }
1067
1068 private void diposeAllViews() {
1069 while (platformViews.size() > 0) {
1070 final int viewId = platformViews.keyAt(0);
1071 // Dispose deletes the entry from platformViews and clears associated resources.
1072 channelHandler.dispose(viewId);
1073 }
1074 }
1075
1076 // Invoked when the Android system is requesting we reduce memory usage.
1077 public void onTrimMemory(int level) {
1078 if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
1079 return;
1080 }
1081 for (VirtualDisplayController vdc : vdControllers.values()) {
1082 vdc.clearSurface();
1083 }
1084 }
1085
1086 // Called after the application has been resumed.
1087 // This is where we undo whatever may have been done in onTrimMemory.
1088 public void onResume() {
1089 for (VirtualDisplayController vdc : vdControllers.values()) {
1090 vdc.resetSurface();
1091 }
1092 }
1093
1094 /**
1095 * Disposes a single
1096 *
1097 * @param viewId the PlatformView ID.
1098 */
1099 @VisibleForTesting
1100 public void disposePlatformView(int viewId) {
1101 channelHandler.dispose(viewId);
1102 }
1103
1104 private void initializeRootImageViewIfNeeded() {
1105 if (synchronizeToNativeViewHierarchy && !flutterViewConvertedToImageView) {
1106 flutterView.convertToImageView();
1107 flutterViewConvertedToImageView = true;
1108 }
1109 }
1110
1111 /**
1112 * Initializes a platform view and adds it to the view hierarchy.
1113 *
1114 * @param viewId The view ID. This member is not intended for public use, and is only visible for
1115 * testing.
1116 */
1117 @VisibleForTesting
1119 final PlatformView platformView = platformViews.get(viewId);
1120 if (platformView == null) {
1121 throw new IllegalStateException(
1122 "Platform view hasn't been initialized from the platform view channel.");
1123 }
1124 if (platformViewParent.get(viewId) != null) {
1125 return;
1126 }
1127 final View embeddedView = platformView.getView();
1128 if (embeddedView == null) {
1129 throw new IllegalStateException(
1130 "PlatformView#getView() returned null, but an Android view reference was expected.");
1131 }
1132 if (embeddedView.getParent() != null) {
1133 throw new IllegalStateException(
1134 "The Android view returned from PlatformView#getView() was already added to a parent"
1135 + " view.");
1136 }
1137 final FlutterMutatorView parentView =
1139 context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);
1140
1142 (view, hasFocus) -> {
1143 if (hasFocus) {
1144 platformViewsChannel.invokeViewFocused(viewId);
1145 } else if (textInputPlugin != null) {
1146 textInputPlugin.clearPlatformViewClient(viewId);
1147 }
1148 });
1149
1150 platformViewParent.put(viewId, parentView);
1151
1152 // Accessibility in the embedded view is initially disabled because if a Flutter app disabled
1153 // accessibility in the first frame, the embedding won't receive an update to disable
1154 // accessibility since the embedding never received an update to enable it.
1155 // The AccessibilityBridge keeps track of the accessibility nodes, and handles the deltas when
1156 // the framework sends a new a11y tree to the embedding.
1157 // To prevent races, the framework populate the SemanticsNode after the platform view has been
1158 // created.
1159 embeddedView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
1160
1161 parentView.addView(embeddedView);
1162 flutterView.addView(parentView);
1163 }
1164
1165 public void attachToFlutterRenderer(@NonNull FlutterRenderer flutterRenderer) {
1166 androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ true);
1167 }
1168
1169 /**
1170 * Called when a platform view id displayed in the current frame.
1171 *
1172 * @param viewId The ID of the platform view.
1173 * @param x The left position relative to {@code FlutterView}.
1174 * @param y The top position relative to {@code FlutterView}.
1175 * @param width The width of the platform view.
1176 * @param height The height of the platform view.
1177 * @param viewWidth The original width of the platform view before applying the mutator stack.
1178 * @param viewHeight The original height of the platform view before applying the mutator stack.
1179 * @param mutatorsStack The mutator stack. This member is not intended for public use, and is only
1180 * visible for testing.
1181 */
1183 int viewId,
1184 int x,
1185 int y,
1186 int width,
1187 int height,
1188 int viewWidth,
1189 int viewHeight,
1190 @NonNull FlutterMutatorsStack mutatorsStack) {
1191 initializeRootImageViewIfNeeded();
1192 initializePlatformViewIfNeeded(viewId);
1193
1194 final FlutterMutatorView parentView = platformViewParent.get(viewId);
1195 parentView.readyToDisplay(mutatorsStack, x, y, width, height);
1196 parentView.setVisibility(View.VISIBLE);
1197 parentView.bringToFront();
1198
1199 final FrameLayout.LayoutParams layoutParams =
1200 new FrameLayout.LayoutParams(viewWidth, viewHeight);
1201 final View view = platformViews.get(viewId).getView();
1202 if (view != null) {
1203 view.setLayoutParams(layoutParams);
1204 view.bringToFront();
1205 }
1206 currentFrameUsedPlatformViewIds.add(viewId);
1207 }
1208
1209 /**
1210 * Called when an overlay surface is displayed in the current frame.
1211 *
1212 * @param id The ID of the surface.
1213 * @param x The left position relative to {@code FlutterView}.
1214 * @param y The top position relative to {@code FlutterView}.
1215 * @param width The width of the surface.
1216 * @param height The height of the surface. This member is not intended for public use, and is
1217 * only visible for testing.
1218 */
1219 public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) {
1220 if (overlayLayerViews.get(id) == null) {
1221 throw new IllegalStateException("The overlay surface (id:" + id + ") doesn't exist");
1222 }
1223 initializeRootImageViewIfNeeded();
1224
1225 final PlatformOverlayView overlayView = overlayLayerViews.get(id);
1226 if (overlayView.getParent() == null) {
1227 flutterView.addView(overlayView);
1228 }
1229
1230 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams((int) width, (int) height);
1231 layoutParams.leftMargin = (int) x;
1232 layoutParams.topMargin = (int) y;
1233 overlayView.setLayoutParams(layoutParams);
1234 overlayView.setVisibility(View.VISIBLE);
1235 overlayView.bringToFront();
1236 currentFrameUsedOverlayLayerIds.add(id);
1237 }
1238
1239 public void onBeginFrame() {
1240 currentFrameUsedOverlayLayerIds.clear();
1241 currentFrameUsedPlatformViewIds.clear();
1242 }
1243
1244 /**
1245 * Called by {@code FlutterJNI} when the Flutter frame was submitted.
1246 *
1247 * <p>This member is not intended for public use, and is only visible for testing.
1248 */
1249 public void onEndFrame() {
1250 // If there are no platform views in the current frame,
1251 // then revert the image view surface and use the previous surface.
1252 //
1253 // Otherwise, acquire the latest image.
1254 if (flutterViewConvertedToImageView && currentFrameUsedPlatformViewIds.isEmpty()) {
1255 flutterViewConvertedToImageView = false;
1256 flutterView.revertImageView(
1257 () -> {
1258 // Destroy overlay surfaces once the surface reversion is completed.
1259 finishFrame(false);
1260 });
1261 return;
1262 }
1263 // Whether the current frame was rendered using ImageReaders.
1264 //
1265 // Since the image readers may not have images available at this point,
1266 // this becomes true if all the required surfaces have images available.
1267 //
1268 // This is used to decide if the platform views can be rendered in the current frame.
1269 // If one of the surfaces doesn't have an image, the frame may be incomplete and must be
1270 // dropped.
1271 // For example, a toolbar widget painted by Flutter may not be rendered.
1272 final boolean isFrameRenderedUsingImageReaders =
1273 flutterViewConvertedToImageView && flutterView.acquireLatestImageViewFrame();
1274 finishFrame(isFrameRenderedUsingImageReaders);
1275 }
1276
1277 private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
1278 for (int i = 0; i < overlayLayerViews.size(); i++) {
1279 final int overlayId = overlayLayerViews.keyAt(i);
1280 final PlatformOverlayView overlayView = overlayLayerViews.valueAt(i);
1281
1282 if (currentFrameUsedOverlayLayerIds.contains(overlayId)) {
1283 flutterView.attachOverlaySurfaceToRender(overlayView);
1284 final boolean didAcquireOverlaySurfaceImage = overlayView.acquireLatestImage();
1285 isFrameRenderedUsingImageReaders &= didAcquireOverlaySurfaceImage;
1286 } else {
1287 // If the background surface isn't rendered by the image view, then the
1288 // overlay surfaces can be detached from the rendered.
1289 // This releases resources used by the ImageReader.
1290 if (!flutterViewConvertedToImageView) {
1291 overlayView.detachFromRenderer();
1292 }
1293 // Hide overlay surfaces that aren't rendered in the current frame.
1294 overlayView.setVisibility(View.GONE);
1295 flutterView.removeView(overlayView);
1296 }
1297 }
1298
1299 for (int i = 0; i < platformViewParent.size(); i++) {
1300 final int viewId = platformViewParent.keyAt(i);
1301 final View parentView = platformViewParent.get(viewId);
1302
1303 // This should only show platform views that are rendered in this frame and either:
1304 // 1. Surface has images available in this frame or,
1305 // 2. Surface does not have images available in this frame because the render surface should
1306 // not be an ImageView.
1307 //
1308 // The platform view is appended to a mutator view.
1309 //
1310 // Otherwise, hide the platform view, but don't remove it from the view hierarchy yet as
1311 // they are removed when the framework disposes the platform view widget.
1312 if (currentFrameUsedPlatformViewIds.contains(viewId)
1313 && (isFrameRenderedUsingImageReaders || !synchronizeToNativeViewHierarchy)) {
1314 parentView.setVisibility(View.VISIBLE);
1315 } else {
1316 parentView.setVisibility(View.GONE);
1317 }
1318 }
1319 }
1320
1321 /**
1322 * Creates and tracks the overlay surface.
1323 *
1324 * @param imageView The surface that displays the overlay.
1325 * @return Wrapper object that provides the layer id and the surface. This member is not intended
1326 * for public use, and is only visible for testing.
1327 */
1328 @VisibleForTesting
1329 @NonNull
1331 final int id = nextOverlayLayerId++;
1332 overlayLayerViews.put(id, imageView);
1333 return new FlutterOverlaySurface(id, imageView.getSurface());
1334 }
1335
1336 /**
1337 * Creates an overlay surface while the Flutter view is rendered by {@code PlatformOverlayView}.
1338 *
1339 * <p>This method is invoked by {@code FlutterJNI} only.
1340 *
1341 * <p>This member is not intended for public use, and is only visible for testing.
1342 */
1343 @NonNull
1345 // Overlay surfaces have the same size as the background surface.
1346 //
1347 // This allows to reuse these surfaces in consecutive frames even
1348 // if the drawings they contain have a different tight bound.
1349 //
1350 // The final view size is determined when its frame is set.
1351 return createOverlaySurface(
1353 flutterView.getContext(),
1354 flutterView.getWidth(),
1355 flutterView.getHeight(),
1356 accessibilityEventsDelegate));
1357 }
1358
1359 /**
1360 * Destroys the overlay surfaces and removes them from the view hierarchy.
1361 *
1362 * <p>This method is used only internally by {@code FlutterJNI}.
1363 */
1365 for (int viewId = 0; viewId < overlayLayerViews.size(); viewId++) {
1366 final PlatformOverlayView overlayView = overlayLayerViews.valueAt(viewId);
1367 overlayView.detachFromRenderer();
1368 overlayView.closeImageReader();
1369 // Don't remove overlayView from the view hierarchy since this method can
1370 // be called while the Android framework is iterating over the array of views.
1371 // See ViewGroup#dispatchDetachedFromWindow(), and
1372 // https://github.com/flutter/flutter/issues/97679.
1373 }
1374 }
1375
1376 private void removeOverlaySurfaces() {
1377 if (flutterView == null) {
1378 Log.e(TAG, "removeOverlaySurfaces called while flutter view is null");
1379 return;
1380 }
1381 for (int viewId = 0; viewId < overlayLayerViews.size(); viewId++) {
1382 flutterView.removeView(overlayLayerViews.valueAt(viewId));
1383 }
1384 overlayLayerViews.clear();
1385 }
1386
1387 @VisibleForTesting
1388 public SparseArray<PlatformOverlayView> getOverlayLayerViews() {
1389 return overlayLayerViews;
1390 }
1391}
void add(sk_sp< SkIDChangeListener > listener) SK_EXCLUDES(fMutex)
static void e(@NonNull String tag, @NonNull String message)
Definition: Log.java:84
static void i(@NonNull String tag, @NonNull String message)
Definition: Log.java:52
void attachOverlaySurfaceToRender(@NonNull FlutterImageView view)
void revertImageView(@NonNull Runnable onDone)
MotionEvent pop(@NonNull MotionEventId eventId)
void readyToDisplay( @NonNull FlutterMutatorsStack mutatorsStack, int left, int top, int width, int height)
void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener)
void setPlatformViewsHandler(@Nullable PlatformViewsHandler handler)
void setAccessibilityBridge(@Nullable AccessibilityBridge accessibilityBridge)
abstract PlatformView create(Context context, int viewId, @Nullable Object args)
void setTouchProcessor(@Nullable AndroidTouchProcessor newTouchProcessor)
void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener)
void setLayoutParams(@NonNull FrameLayout.LayoutParams params)
void attachToFlutterRenderer(@NonNull FlutterRenderer flutterRenderer)
long configureForTextureLayerComposition( @NonNull PlatformView platformView, @NonNull PlatformViewsChannel.PlatformViewCreationRequest request)
MotionEvent toMotionEvent(float density, PlatformViewsChannel.PlatformViewTouch touch, boolean usingVirtualDiplay)
void onDisplayPlatformView(int viewId, int x, int y, int width, int height, int viewWidth, int viewHeight, @NonNull FlutterMutatorsStack mutatorsStack)
void attachTextInputPlugin(@NonNull TextInputPlugin textInputPlugin)
void attachToView(@NonNull FlutterView newFlutterView)
FlutterOverlaySurface createOverlaySurface(@NonNull PlatformOverlayView imageView)
void attach( @Nullable Context context, @NonNull TextureRegistry textureRegistry, @NonNull DartExecutor dartExecutor)
void attachAccessibilityBridge(@NonNull AccessibilityBridge accessibilityBridge)
void onDisplayOverlaySurface(int id, int x, int y, int width, int height)
PlatformView createPlatformView( @NonNull PlatformViewsChannel.PlatformViewCreationRequest request, boolean wrapContext)
final HashMap< Integer, VirtualDisplayController > vdControllers
void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable)
static boolean hasChildViewOfType(@Nullable View root, Class<? extends View >[] viewTypes)
Definition: ViewUtils.java:78
FlKeyEvent * event
T decodeMessage(@Nullable ByteBuffer message)
default void onFlutterViewAttached(@NonNull View flutterView)
FlutterTextInputPlugin * textInputPlugin
double y
double x
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition: TestRunner.cpp:137
def Build(configs, env, options)
Definition: build.py:232
#define TAG()
int32_t height
int32_t width
SeparatedVector2 offset