blob: 3438db5dd648ce6b50de5f3776017782caf55486 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugin.platform;
import static android.view.View.OnFocusChangeListener;
import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import io.flutter.view.TextureRegistry;
@TargetApi(20)
class VirtualDisplayController {
private static String TAG = "VirtualDisplayController";
public static VirtualDisplayController create(
Context context,
AccessibilityEventsDelegate accessibilityEventsDelegate,
PlatformView view,
TextureRegistry.SurfaceTextureEntry textureEntry,
int width,
int height,
int viewId,
Object createParams,
OnFocusChangeListener focusChangeListener) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
if (width == 0 || height == 0) {
return null;
}
// Virtual Display crashes for some PlatformViews if the width or height is bigger
// than the physical screen size. We have tried to clamp or scale down the size to prevent
// the crash, but both solutions lead to unwanted behavior because the
// AndroidPlatformView(https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/platform_view.dart#L677) widget doesn't
// scale or clamp, which leads to a mismatch between the size of the widget and the size of
// virtual display.
// This mismatch leads to some test failures: https://github.com/flutter/flutter/issues/106750
// TODO(cyanglaz): find a way to prevent the crash without introducing size mistach betewen
// virtual display and AndroidPlatformView widget.
// https://github.com/flutter/flutter/issues/93115
textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
Surface surface = new Surface(textureEntry.surfaceTexture());
DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
int densityDpi = context.getResources().getDisplayMetrics().densityDpi;
VirtualDisplay virtualDisplay =
displayManager.createVirtualDisplay("flutter-vd", width, height, densityDpi, surface, 0);
if (virtualDisplay == null) {
return null;
}
VirtualDisplayController controller =
new VirtualDisplayController(
context,
accessibilityEventsDelegate,
virtualDisplay,
view,
surface,
textureEntry,
focusChangeListener,
viewId,
createParams);
controller.bufferWidth = width;
controller.bufferHeight = height;
return controller;
}
@VisibleForTesting SingleViewPresentation presentation;
private final Context context;
private final AccessibilityEventsDelegate accessibilityEventsDelegate;
private final int densityDpi;
private final TextureRegistry.SurfaceTextureEntry textureEntry;
private final OnFocusChangeListener focusChangeListener;
private final Surface surface;
private VirtualDisplay virtualDisplay;
private int bufferWidth;
private int bufferHeight;
private VirtualDisplayController(
Context context,
AccessibilityEventsDelegate accessibilityEventsDelegate,
VirtualDisplay virtualDisplay,
PlatformView view,
Surface surface,
TextureRegistry.SurfaceTextureEntry textureEntry,
OnFocusChangeListener focusChangeListener,
int viewId,
Object createParams) {
this.context = context;
this.accessibilityEventsDelegate = accessibilityEventsDelegate;
this.textureEntry = textureEntry;
this.focusChangeListener = focusChangeListener;
this.surface = surface;
this.virtualDisplay = virtualDisplay;
densityDpi = context.getResources().getDisplayMetrics().densityDpi;
presentation =
new SingleViewPresentation(
context,
this.virtualDisplay.getDisplay(),
view,
accessibilityEventsDelegate,
viewId,
focusChangeListener);
presentation.show();
}
public int getBufferWidth() {
return bufferWidth;
}
public int getBufferHeight() {
return bufferHeight;
}
public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) {
boolean isFocused = getView().isFocused();
final SingleViewPresentation.PresentationState presentationState = presentation.detachState();
// We detach the surface to prevent it being destroyed when releasing the vd.
//
// setSurface is only available starting API 20. We could support API 19 by re-creating a new
// SurfaceTexture here. This will require refactoring the TextureRegistry to allow recycling
// texture
// entry IDs.
virtualDisplay.setSurface(null);
virtualDisplay.release();
bufferWidth = width;
bufferHeight = height;
textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
virtualDisplay =
displayManager.createVirtualDisplay("flutter-vd", width, height, densityDpi, surface, 0);
final View embeddedView = getView();
// There's a bug in Android version older than O where view tree observer onDrawListeners don't
// get properly
// merged when attaching to window, as a workaround we register the on draw listener after the
// view is attached.
embeddedView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
OneTimeOnDrawListener.schedule(
embeddedView,
new Runnable() {
@Override
public void run() {
// We need some delay here until the frame propagates through the vd surface to
// the texture,
// 128ms was picked pretty arbitrarily based on trial and error.
// As long as we invoke the runnable after a new frame is available we avoid the
// scaling jank
// described in: https://github.com/flutter/flutter/issues/19572
// We should ideally run onNewSizeFrameAvailable ASAP to make the embedded view
// more responsive
// following a resize.
embeddedView.postDelayed(onNewSizeFrameAvailable, 128);
}
});
embeddedView.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {}
});
// Create a new SingleViewPresentation and show() it before we cancel() the existing
// presentation. Calling show() and cancel() in this order fixes
// https://github.com/flutter/flutter/issues/26345 and maintains seamless transition
// of the contents of the presentation.
SingleViewPresentation newPresentation =
new SingleViewPresentation(
context,
virtualDisplay.getDisplay(),
accessibilityEventsDelegate,
presentationState,
focusChangeListener,
isFocused);
newPresentation.show();
presentation.cancel();
presentation = newPresentation;
}
public void dispose() {
// Fix rare crash on HuaWei device described in: https://github.com/flutter/engine/pull/9192
presentation.cancel();
presentation.detachState();
virtualDisplay.release();
textureEntry.release();
}
/** See {@link PlatformView#onFlutterViewAttached(View)} */
/*package*/ void onFlutterViewAttached(@NonNull View flutterView) {
if (presentation == null || presentation.getView() == null) {
return;
}
presentation.getView().onFlutterViewAttached(flutterView);
}
/** See {@link PlatformView#onFlutterViewDetached()} */
/*package*/ void onFlutterViewDetached() {
if (presentation == null || presentation.getView() == null) {
return;
}
presentation.getView().onFlutterViewDetached();
}
/*package*/ void onInputConnectionLocked() {
if (presentation == null || presentation.getView() == null) {
return;
}
presentation.getView().onInputConnectionLocked();
}
/*package*/ void onInputConnectionUnlocked() {
if (presentation == null || presentation.getView() == null) {
return;
}
presentation.getView().onInputConnectionUnlocked();
}
public View getView() {
if (presentation == null) return null;
PlatformView platformView = presentation.getView();
return platformView.getView();
}
/** Dispatches a motion event to the presentation for this controller. */
public void dispatchTouchEvent(MotionEvent event) {
if (presentation == null) return;
presentation.dispatchTouchEvent(event);
}
static class OneTimeOnDrawListener implements ViewTreeObserver.OnDrawListener {
static void schedule(View view, Runnable runnable) {
OneTimeOnDrawListener listener = new OneTimeOnDrawListener(view, runnable);
view.getViewTreeObserver().addOnDrawListener(listener);
}
final View mView;
Runnable mOnDrawRunnable;
OneTimeOnDrawListener(View view, Runnable onDrawRunnable) {
this.mView = view;
this.mOnDrawRunnable = onDrawRunnable;
}
@Override
public void onDraw() {
if (mOnDrawRunnable == null) {
return;
}
mOnDrawRunnable.run();
mOnDrawRunnable = null;
mView.post(
new Runnable() {
@Override
public void run() {
mView.getViewTreeObserver().removeOnDrawListener(OneTimeOnDrawListener.this);
}
});
}
}
}