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