blob: ebbe0312f868870647a91392dd803e7e1a0f0467 [file] [log] [blame]
// Copyright 2012 The Chromium 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 org.chromium.weblayer_private;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.os.SystemClock;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.ValueCallback;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.IntDef;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.task.PostTask;
import org.chromium.components.browser_ui.widget.InsetObserverView;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.display.DisplayAndroid;
import org.chromium.ui.resources.ResourceManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
* This class manages the chromium compositor and the Surface that is used by
* the chromium compositor. Note it can be used to display only one WebContents.
* This allows switching between SurfaceView and TextureView as the source of
* the Surface used by chromium compositor, and attempts to make the switch
* visually seamless.
*/
@JNINamespace("weblayer")
public class ContentViewRenderView extends RelativeLayout {
@Retention(RetentionPolicy.SOURCE)
@IntDef({MODE_SURFACE_VIEW, MODE_TEXTURE_VIEW})
public @interface Mode {}
public static final int MODE_SURFACE_VIEW = 0;
public static final int MODE_TEXTURE_VIEW = 1;
private static final int CONFIG_TIMEOUT_MS = 1000;
// A child view of this class. Parent of SurfaceView/TextureView.
// Needed to support not resizing the surface when soft keyboard is showing.
private final SurfaceParent mSurfaceParent;
// This is mode that is requested by client.
private SurfaceData mRequested;
// This is the mode that last supplied the Surface to the compositor.
// This should generally be equal to |mRequested| except during transitions.
private SurfaceData mCurrent;
// The native side of this object.
private long mNativeContentViewRenderView;
// An invisible view that notifies observers of changes to window insets and safe area.
private InsetObserverView mInsetObserverView;
private WindowAndroid mWindowAndroid;
private WebContents mWebContents;
private int mBackgroundColor;
// This is the size of the surfaces, so the "physical" size for the compositor.
// This is the size of the |mSurfaceParent| view, which is the immediate parent
// of the SurfaceView/TextureView. Note this does not always match the size of
// this ContentViewRenderView; when the soft keyboard is displayed,
// ContentViewRenderView will shrink in height, but |mSurfaceParent| will not.
private int mPhysicalWidth;
private int mPhysicalHeight;
private int mWebContentsHeightDelta;
private boolean mCompositorHasSurface;
private DisplayAndroid.DisplayAndroidObserver mDisplayAndroidObserver;
// The time stamp when a configuration was detected (if any).
// This is used along with a timeout to determine if a resize surface resize
// is due to screen rotation.
private long mConfigurationChangedTimestamp;
// Common interface to listen to surface related events.
private interface SurfaceEventListener {
void surfaceCreated();
void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format,
int width, int height);
// |cacheBackBuffer| will delay destroying the EGLSurface until after the next swap.
void surfaceDestroyed(boolean cacheBackBuffer);
void surfaceRedrawNeededAsync(Runnable drawingFinished);
}
private final ArrayList<TrackedRunnable> mPendingRunnables = new ArrayList<>();
// Runnables posted via View.postOnAnimation may not run after the view is detached,
// if nothing else causes animation. However a pending runnable may held by a GC root
// from the thread itself, and thus can cause leaks. This class here is so ensure that
// on destroy, all pending tasks are run immediately so they do not lead to leaks.
private abstract class TrackedRunnable implements Runnable {
private boolean mHasRun;
public TrackedRunnable() {
mPendingRunnables.add(this);
}
@Override
public final void run() {
// View.removeCallbacks is not always reliable, and may run the callback even
// after it has been removed.
if (mHasRun) return;
assert mPendingRunnables.contains(this);
mPendingRunnables.remove(this);
mHasRun = true;
doRun();
}
protected abstract void doRun();
}
// Non-static implementation of SurfaceEventListener that forward calls to native Compositor.
// It is also responsible for updating |mRequested| and |mCurrent|.
private class SurfaceEventListenerImpl implements SurfaceEventListener {
private SurfaceData mSurfaceData;
public void setRequestData(SurfaceData surfaceData) {
assert mSurfaceData == null;
mSurfaceData = surfaceData;
}
@Override
public void surfaceCreated() {
assert mNativeContentViewRenderView != 0;
assert mSurfaceData == ContentViewRenderView.this.mRequested
|| mSurfaceData == ContentViewRenderView.this.mCurrent;
if (ContentViewRenderView.this.mCurrent != null
&& ContentViewRenderView.this.mCurrent != mSurfaceData) {
ContentViewRenderView.this.mCurrent.markForDestroy(true /* hasNextSurface */);
mSurfaceData.setSurfaceDataNeedsDestroy(ContentViewRenderView.this.mCurrent);
}
ContentViewRenderView.this.mCurrent = mSurfaceData;
ContentViewRenderViewJni.get().surfaceCreated(mNativeContentViewRenderView);
}
@Override
public void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format,
int width, int height) {
assert mNativeContentViewRenderView != 0;
assert mSurfaceData == ContentViewRenderView.this.mCurrent;
ContentViewRenderViewJni.get().surfaceChanged(mNativeContentViewRenderView,
canBeUsedWithSurfaceControl, format, width, height, surface);
mCompositorHasSurface = surface != null;
maybeUpdatePhysicalBackingSize(width, height);
}
@Override
public void surfaceDestroyed(boolean cacheBackBuffer) {
assert mNativeContentViewRenderView != 0;
assert mSurfaceData == ContentViewRenderView.this.mCurrent;
ContentViewRenderViewJni.get().surfaceDestroyed(
mNativeContentViewRenderView, cacheBackBuffer);
mCompositorHasSurface = false;
}
@Override
public void surfaceRedrawNeededAsync(Runnable drawingFinished) {
assert false; // NOTREACHED.
}
}
// Abstract differences between SurfaceView and TextureView behind this class.
// Also responsible for holding and calling callbacks.
private class SurfaceData implements SurfaceEventListener {
private class TextureViewWithInvalidate extends TextureView {
public TextureViewWithInvalidate(Context context) {
super(context);
}
@Override
public void invalidate() {
// TextureView is invalidated when it receives a new frame from its SurfaceTexture.
// This is a safe place to indicate that this TextureView now has content and is
// ready to be shown.
super.invalidate();
destroyPreviousData();
}
}
@Mode
private final int mMode;
private final SurfaceEventListener mListener;
private final FrameLayout mParent;
private final Runnable mEvict;
private boolean mRanCallbacks;
private boolean mMarkedForDestroy;
private boolean mCachedSurfaceNeedsEviction;
private boolean mNeedsOnSurfaceDestroyed;
// During transitioning between two SurfaceData, there is a complicated series of calls to
// avoid visual artifacts.
// 1) Allocate new SurfaceData, and insert it into view hierarchy below the existing
// SurfaceData, so it is not yet showing.
// 2) When Surface is allocated by new View, swap chromium compositor to the
// new Surface. |markForDestroy| is called on the previous SurfaceData, and the two
// SurfaceDatas are linked through these two variables.
// Note at this point the existing view is still visible.
// 3) Wait until new SurfaceData decides that it has content and is ready to be shown
// * For TextureView, wait until TextureView.invalidate is called
// * For SurfaceView, wait for two swaps from the chromium compositor
// 4) New SurfaceData calls |destroy| on previous SurfaceData.
// * For TextureView, it is simply detached immediately from the view tree
// * For SurfaceView, to avoid flicker, move it to the back first before and wait
// two frames before detaching.
// 5) Previous SurfaceData runs callbacks on the new SurfaceData to signal the completion
// of the transition.
private SurfaceData mPrevSurfaceDataNeedsDestroy;
private SurfaceData mNextSurfaceDataNeedsRunCallback;
private final SurfaceHolderCallback mSurfaceCallback;
private final SurfaceView mSurfaceView;
private int mNumSurfaceViewSwapsUntilVisible;
private final TextureView mTextureView;
private final TextureViewSurfaceTextureListener mSurfaceTextureListener;
private final ArrayList<ValueCallback<Boolean>> mModeCallbacks = new ArrayList<>();
private ArrayList<Runnable> mSurfaceRedrawNeededCallbacks;
public SurfaceData(@Mode int mode, FrameLayout parent, SurfaceEventListener listener,
int backgroundColor, Runnable evict) {
mMode = mode;
mListener = listener;
mParent = parent;
mEvict = evict;
if (mode == MODE_SURFACE_VIEW) {
mSurfaceView = new SurfaceView(parent.getContext());
mSurfaceView.setZOrderMediaOverlay(true);
mSurfaceView.setBackgroundColor(backgroundColor);
mSurfaceCallback = new SurfaceHolderCallback(this);
mSurfaceView.getHolder().addCallback(mSurfaceCallback);
mSurfaceView.setVisibility(View.VISIBLE);
// TODO(boliu): This is only needed when video is lifted into a separate surface.
// Keeping this constantly will use one more byte per pixel constantly.
mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
mTextureView = null;
mSurfaceTextureListener = null;
} else if (mode == MODE_TEXTURE_VIEW) {
mTextureView = new TextureViewWithInvalidate(parent.getContext());
mSurfaceTextureListener = new TextureViewSurfaceTextureListener(this);
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
mTextureView.setVisibility(VISIBLE);
mSurfaceView = null;
mSurfaceCallback = null;
} else {
throw new RuntimeException("Illegal mode: " + mode);
}
// This postOnAnimation is to avoid manipulating the view tree inside layout or draw.
parent.postOnAnimation(new TrackedRunnable() {
@Override
protected void doRun() {
if (mMarkedForDestroy) return;
View view = (mMode == MODE_SURFACE_VIEW) ? mSurfaceView : mTextureView;
assert view != null;
// Always insert view for new surface below the existing view to avoid artifacts
// during surface swaps. Index 0 is the lowest child.
mParent.addView(view, 0,
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
mParent.invalidate();
}
});
}
public void setSurfaceDataNeedsDestroy(SurfaceData surfaceData) {
assert !mMarkedForDestroy;
assert mPrevSurfaceDataNeedsDestroy == null;
mPrevSurfaceDataNeedsDestroy = surfaceData;
mPrevSurfaceDataNeedsDestroy.mNextSurfaceDataNeedsRunCallback = this;
}
public @Mode int getMode() {
return mMode;
}
public void addCallback(ValueCallback<Boolean> callback) {
assert !mMarkedForDestroy;
mModeCallbacks.add(callback);
if (mRanCallbacks) runCallbacks();
}
// Tearing down is separated into markForDestroy and destroy. After markForDestroy
// this class will is guaranteed to not issue any calls to its SurfaceEventListener.
public void markForDestroy(boolean hasNextSurface) {
if (mMarkedForDestroy) return;
mMarkedForDestroy = true;
if (mNeedsOnSurfaceDestroyed) {
// SurfaceView being used with SurfaceControl need to cache the back buffer
// (EGLSurface). Otherwise the surface is destroyed immediate before the
// SurfaceView is detached.
mCachedSurfaceNeedsEviction = hasNextSurface && mMode == MODE_SURFACE_VIEW;
mListener.surfaceDestroyed(mCachedSurfaceNeedsEviction);
mNeedsOnSurfaceDestroyed = false;
}
runSurfaceRedrawNeededCallbacks();
if (mMode == MODE_SURFACE_VIEW) {
mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
} else if (mMode == MODE_TEXTURE_VIEW) {
mTextureView.setSurfaceTextureListener(null);
} else {
assert false;
}
}
// Remove view from parent hierarchy.
public void destroy() {
assert mMarkedForDestroy;
runCallbacks();
// This postOnAnimation is to avoid manipulating the view tree inside layout or draw.
mParent.postOnAnimation(new TrackedRunnable() {
@Override
protected void doRun() {
if (mMode == MODE_SURFACE_VIEW) {
// Detaching a SurfaceView causes a flicker because the SurfaceView tears
// down the Surface in SurfaceFlinger before removing its hole in the view
// tree. This is a complicated heuristics to avoid this. It first moves the
// SurfaceView behind the new View. Then wait two frames before detaching
// the SurfaceView. Waiting for a single frame still causes flickers on
// high end devices like Pixel 3.
moveChildToBackWithoutDetach(mParent, mSurfaceView);
TrackedRunnable inner = new TrackedRunnable() {
@Override
public void doRun() {
mParent.removeView(mSurfaceView);
mParent.invalidate();
if (mCachedSurfaceNeedsEviction) {
mEvict.run();
mCachedSurfaceNeedsEviction = false;
}
runCallbackOnNextSurfaceData();
}
};
TrackedRunnable outer = new TrackedRunnable() {
@Override
public void doRun() {
mParent.postOnAnimation(inner);
}
};
mParent.postOnAnimation(outer);
} else if (mMode == MODE_TEXTURE_VIEW) {
mParent.removeView(mTextureView);
runCallbackOnNextSurfaceData();
} else {
assert false;
}
}
});
}
private void moveChildToBackWithoutDetach(ViewGroup parent, View child) {
final int numberOfChildren = parent.getChildCount();
final int childIndex = parent.indexOfChild(child);
if (childIndex <= 0) return;
for (int i = 0; i < childIndex; ++i) {
parent.bringChildToFront(parent.getChildAt(0));
}
assert parent.indexOfChild(child) == 0;
for (int i = 0; i < numberOfChildren - childIndex - 1; ++i) {
parent.bringChildToFront(parent.getChildAt(1));
}
parent.invalidate();
}
public void setBackgroundColor(int color) {
assert !mMarkedForDestroy;
if (mMode == MODE_SURFACE_VIEW) {
mSurfaceView.setBackgroundColor(color);
}
}
/** @return true if should keep swapping frames */
public boolean didSwapFrame() {
if (mSurfaceView != null && mSurfaceView.getBackground() != null) {
mSurfaceView.post(new Runnable() {
@Override
public void run() {
if (mSurfaceView != null) mSurfaceView.setBackgroundResource(0);
}
});
}
if (mMode == MODE_SURFACE_VIEW) {
// We have no reliable signal for when to show a SurfaceView. This is a heuristic
// (used by chrome as well) is to wait for 2 swaps from the chromium comopsitor
// as a signal that the SurfaceView has content and is ready to be displayed.
if (mNumSurfaceViewSwapsUntilVisible > 0) {
mNumSurfaceViewSwapsUntilVisible--;
}
if (mNumSurfaceViewSwapsUntilVisible == 0) {
destroyPreviousData();
}
return mNumSurfaceViewSwapsUntilVisible > 0;
}
return false;
}
public void runSurfaceRedrawNeededCallbacks() {
ArrayList<Runnable> callbacks = mSurfaceRedrawNeededCallbacks;
mSurfaceRedrawNeededCallbacks = null;
if (callbacks == null) return;
for (Runnable r : callbacks) {
r.run();
}
}
private void destroyPreviousData() {
if (mPrevSurfaceDataNeedsDestroy != null) {
mPrevSurfaceDataNeedsDestroy.destroy();
mPrevSurfaceDataNeedsDestroy = null;
}
}
@Override
public void surfaceCreated() {
if (mMarkedForDestroy) return;
// On pre-M Android, layers start in the hidden state until a relayout happens.
// There is a bug that manifests itself when entering overlay mode on pre-M devices,
// where a relayout never happens. This bug is out of Chromium's control, but can be
// worked around by forcibly re-setting the visibility of the surface view.
// Otherwise, the screen stays black, and some tests fail.
if (mSurfaceView != null) {
mSurfaceView.setVisibility(mSurfaceView.getVisibility());
}
mListener.surfaceCreated();
if (!mRanCallbacks && mPrevSurfaceDataNeedsDestroy == null) {
runCallbacks();
}
mNeedsOnSurfaceDestroyed = true;
}
@Override
public void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format,
int width, int height) {
if (mMarkedForDestroy) return;
mListener.surfaceChanged(surface, canBeUsedWithSurfaceControl, format, width, height);
mNumSurfaceViewSwapsUntilVisible = 2;
}
@Override
public void surfaceDestroyed(boolean cacheBackBuffer) {
if (mMarkedForDestroy) return;
assert mNeedsOnSurfaceDestroyed;
mListener.surfaceDestroyed(cacheBackBuffer);
mNeedsOnSurfaceDestroyed = false;
runSurfaceRedrawNeededCallbacks();
}
@Override
public void surfaceRedrawNeededAsync(Runnable drawingFinished) {
if (mMarkedForDestroy) {
drawingFinished.run();
return;
}
assert mNativeContentViewRenderView != 0;
assert this == ContentViewRenderView.this.mCurrent;
if (mSurfaceRedrawNeededCallbacks == null) {
mSurfaceRedrawNeededCallbacks = new ArrayList<>();
}
mSurfaceRedrawNeededCallbacks.add(drawingFinished);
ContentViewRenderViewJni.get().setNeedsRedraw(mNativeContentViewRenderView);
}
private void runCallbacks() {
mRanCallbacks = true;
if (mModeCallbacks.isEmpty()) return;
// PostTask to avoid possible reentrancy problems with embedder code.
PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
ArrayList<ValueCallback<Boolean>> clone =
(ArrayList<ValueCallback<Boolean>>) mModeCallbacks.clone();
mModeCallbacks.clear();
for (ValueCallback<Boolean> run : clone) {
run.onReceiveValue(!mMarkedForDestroy);
}
});
}
private void runCallbackOnNextSurfaceData() {
if (mNextSurfaceDataNeedsRunCallback != null) {
mNextSurfaceDataNeedsRunCallback.runCallbacks();
mNextSurfaceDataNeedsRunCallback = null;
}
}
}
// Adapter for SurfaceHoolder.Callback.
private static class SurfaceHolderCallback implements SurfaceHolder.Callback2 {
private final SurfaceEventListener mListener;
public SurfaceHolderCallback(SurfaceEventListener listener) {
mListener = listener;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mListener.surfaceCreated();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mListener.surfaceChanged(holder.getSurface(), true, format, width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mListener.surfaceDestroyed(false /* cacheBackBuffer */);
}
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
// Intentionally not implemented.
}
@Override
public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) {
mListener.surfaceRedrawNeededAsync(drawingFinished);
}
}
// Adapter for TextureView.SurfaceTextureListener.
private static class TextureViewSurfaceTextureListener
implements TextureView.SurfaceTextureListener {
private final SurfaceEventListener mListener;
private SurfaceTexture mCurrentSurfaceTexture;
private Surface mCurrentSurface;
public TextureViewSurfaceTextureListener(SurfaceEventListener listener) {
mListener = listener;
}
@Override
public void onSurfaceTextureAvailable(
SurfaceTexture surfaceTexture, int width, int height) {
mListener.surfaceCreated();
onSurfaceTextureSizeChanged(surfaceTexture, width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
mListener.surfaceDestroyed(false /* cacheBackBuffer */);
return true;
}
@Override
public void onSurfaceTextureSizeChanged(
SurfaceTexture surfaceTexture, int width, int height) {
if (mCurrentSurfaceTexture != surfaceTexture) {
mCurrentSurfaceTexture = surfaceTexture;
mCurrentSurface = new Surface(mCurrentSurfaceTexture);
}
mListener.surfaceChanged(mCurrentSurface, false, PixelFormat.OPAQUE, width, height);
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
}
// This is a child of ContentViewRenderView and parent of SurfaceView/TextureView.
// This exists to avoid resizing SurfaceView/TextureView when the soft keyboard is displayed.
private class SurfaceParent extends FrameLayout {
public SurfaceParent(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int existingHeight = getMeasuredHeight();
// If width is the same and height shrinks, then check if we should
// avoid this resize for displaying the soft keyboard.
if (getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& existingHeight > MeasureSpec.getSize(heightMeasureSpec)
&& shouldAvoidSurfaceResizeForSoftKeyboard()) {
// Just set the height to the current height.
heightMeasureSpec =
MeasureSpec.makeMeasureSpec(existingHeight, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mPhysicalWidth = w;
mPhysicalHeight = h;
}
}
/**
* Constructs a new ContentViewRenderView.
* This should be called and the {@link ContentViewRenderView} should be added to the view
* hierarchy before the first draw to avoid a black flash that is seen every time a
* {@link SurfaceView} is added.
* @param context The context used to create this.
* @param recreateForConfigurationChange indicates that views are recreated after BrowserImpl
* is retained, but Activity is recreated, for a
* configuration change.
*/
public ContentViewRenderView(Context context, boolean recreateForConfigurationChange) {
super(context);
mSurfaceParent = new SurfaceParent(context);
addView(mSurfaceParent,
new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mInsetObserverView = InsetObserverView.create(context);
addView(mInsetObserverView);
mInsetObserverView.addObserver(new InsetObserverView.WindowInsetObserver() {
@Override
public void onInsetChanged(int left, int top, int right, int bottom) {
if (mWebContents != null && mWebContents.isFullscreenForCurrentTab()) {
updateWebContentsSize();
}
}
@Override
public void onSafeAreaChanged(Rect area) {}
});
if (recreateForConfigurationChange) updateConfigChangeTimeStamp();
}
/**
* Initialization that requires native libraries should be done here.
* Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer.
* @param rootWindow The {@link WindowAndroid} this render view should be linked to.
*/
public void onNativeLibraryLoaded(WindowAndroid rootWindow, @Mode int mode) {
assert rootWindow != null;
mNativeContentViewRenderView =
ContentViewRenderViewJni.get().init(ContentViewRenderView.this, rootWindow);
assert mNativeContentViewRenderView != 0;
mWindowAndroid = rootWindow;
requestMode(mode, (Boolean result) -> {});
mDisplayAndroidObserver = new DisplayAndroid.DisplayAndroidObserver() {
@Override
public void onRotationChanged(int rotation) {
updateConfigChangeTimeStamp();
}
};
mWindowAndroid.getDisplay().addObserver(mDisplayAndroidObserver);
updateBackgroundColor();
}
public void requestMode(@Mode int mode, ValueCallback<Boolean> callback) {
assert mode == MODE_SURFACE_VIEW || mode == MODE_TEXTURE_VIEW;
assert callback != null;
if (mRequested != null && mRequested.getMode() != mode) {
if (mRequested != mCurrent) {
mRequested.markForDestroy(false /* hasNextSurface */);
mRequested.destroy();
}
mRequested = null;
}
if (mRequested == null) {
SurfaceEventListenerImpl listener = new SurfaceEventListenerImpl();
mRequested = new SurfaceData(
mode, mSurfaceParent, listener, mBackgroundColor, this::evictCachedSurface);
listener.setRequestData(mRequested);
}
assert mRequested.getMode() == mode;
mRequested.addCallback(callback);
}
/**
* Sets how much to decrease the height of the WebContents by.
*/
public void setWebContentsHeightDelta(int delta) {
if (delta == mWebContentsHeightDelta) return;
mWebContentsHeightDelta = delta;
updateWebContentsSize();
}
private void updateWebContentsSize() {
if (mWebContents == null) return;
Size size = getViewportSize();
mWebContents.setSize(size.getWidth(), size.getHeight() - mWebContentsHeightDelta);
}
/** {@link CompositorViewHolder#getViewportSize()} for explanation. */
private Size getViewportSize() {
if (mWebContents.isFullscreenForCurrentTab()
&& mWindowAndroid.getKeyboardDelegate().isKeyboardShowing(getContext(), this)) {
Rect visibleRect = new Rect();
getWindowVisibleDisplayFrame(visibleRect);
return new Size(Math.min(visibleRect.width(), getWidth()),
Math.min(visibleRect.height(), getHeight()));
}
return new Size(getWidth(), getHeight());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateWebContentsSize();
}
/**
* View's method override to notify WindowAndroid about changes in its visibility.
*/
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (mWindowAndroid == null) return;
if (visibility == View.GONE) {
mWindowAndroid.onVisibilityChanged(false);
} else if (visibility == View.VISIBLE) {
mWindowAndroid.onVisibilityChanged(true);
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateBackgroundColor();
}
/**
* Sets the background color of the surface / texture view. This method is necessary because
* the background color of ContentViewRenderView itself is covered by the background of
* SurfaceView.
* @param color The color of the background.
*/
@Override
public void setBackgroundColor(int color) {
super.setBackgroundColor(color);
mBackgroundColor = color;
if (mRequested != null) {
mRequested.setBackgroundColor(color);
}
if (mCurrent != null) {
mCurrent.setBackgroundColor(color);
}
ContentViewRenderViewJni.get().updateBackgroundColor(mNativeContentViewRenderView);
}
public InsetObserverView getInsetObserverView() {
return mInsetObserverView;
}
/**
* Should be called when the ContentViewRenderView is not needed anymore so its associated
* native resource can be freed.
*/
public void destroy() {
if (mRequested != null) {
mRequested.markForDestroy(false /* hasNextSurface */);
mRequested.destroy();
if (mCurrent != null && mCurrent != mRequested) {
mCurrent.markForDestroy(false /* hasNextSurface */);
mCurrent.destroy();
}
}
mRequested = null;
mCurrent = null;
if (mDisplayAndroidObserver != null) {
mWindowAndroid.getDisplay().removeObserver(mDisplayAndroidObserver);
mDisplayAndroidObserver = null;
}
mWindowAndroid = null;
while (!mPendingRunnables.isEmpty()) {
TrackedRunnable runnable = mPendingRunnables.get(0);
removeCallbacks(runnable);
runnable.run();
assert !mPendingRunnables.contains(runnable);
}
ContentViewRenderViewJni.get().destroy(mNativeContentViewRenderView);
mNativeContentViewRenderView = 0;
}
public void setWebContents(WebContents webContents) {
assert mNativeContentViewRenderView != 0;
mWebContents = webContents;
if (webContents != null && getWidth() != 0 && getHeight() != 0) {
updateWebContentsSize();
maybeUpdatePhysicalBackingSize(mPhysicalWidth, mPhysicalHeight);
}
ContentViewRenderViewJni.get().setCurrentWebContents(
mNativeContentViewRenderView, webContents);
}
public ResourceManager getResourceManager() {
return ContentViewRenderViewJni.get().getResourceManager(mNativeContentViewRenderView);
}
public boolean hasSurface() {
return mCompositorHasSurface;
}
@CalledByNative
private boolean didSwapFrame() {
assert mCurrent != null;
return mCurrent.didSwapFrame();
}
@CalledByNative
private void didSwapBuffers(boolean sizeMatches) {
assert mCurrent != null;
if (!sizeMatches) return;
mCurrent.runSurfaceRedrawNeededCallbacks();
}
private void evictCachedSurface() {
if (mNativeContentViewRenderView == 0) return;
ContentViewRenderViewJni.get().evictCachedSurface(mNativeContentViewRenderView);
}
public long getNativeHandle() {
return mNativeContentViewRenderView;
}
private void updateBackgroundColor() {
int uiMode = getContext().getResources().getConfiguration().uiMode;
boolean darkThemeEnabled =
(uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
setBackgroundColor(darkThemeEnabled ? Color.BLACK : Color.WHITE);
}
@CalledByNative
private int getBackgroundColor() {
return mBackgroundColor;
}
private boolean shouldAvoidSurfaceResizeForSoftKeyboard() {
// TextureView is more common with embedding use cases that should lead to resize.
boolean usingSurfaceView = mCurrent != null && mCurrent.getMode() == MODE_SURFACE_VIEW;
if (!usingSurfaceView) return false;
boolean isFullWidth = isAttachedToWindow() && getWidth() == getRootView().getWidth();
if (!isFullWidth) return false;
InputMethodManager inputMethodManager =
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
return inputMethodManager.isActive();
}
private void updateConfigChangeTimeStamp() {
mConfigurationChangedTimestamp = SystemClock.uptimeMillis();
}
private void maybeUpdatePhysicalBackingSize(int width, int height) {
if (mWebContents == null) return;
boolean forConfigChange =
SystemClock.uptimeMillis() - mConfigurationChangedTimestamp < CONFIG_TIMEOUT_MS;
ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged(
mNativeContentViewRenderView, mWebContents, width, height, forConfigChange);
}
@NativeMethods
interface Natives {
long init(ContentViewRenderView caller, WindowAndroid rootWindow);
void destroy(long nativeContentViewRenderView);
void setCurrentWebContents(long nativeContentViewRenderView, WebContents webContents);
void onPhysicalBackingSizeChanged(long nativeContentViewRenderView, WebContents webContents,
int width, int height, boolean forConfigChange);
void surfaceCreated(long nativeContentViewRenderView);
void surfaceDestroyed(long nativeContentViewRenderView, boolean cacheBackBuffer);
void surfaceChanged(long nativeContentViewRenderView, boolean canBeUsedWithSurfaceControl,
int format, int width, int height, Surface surface);
void setNeedsRedraw(long nativeContentViewRenderView);
void evictCachedSurface(long nativeContentViewRenderView);
ResourceManager getResourceManager(long nativeContentViewRenderView);
void updateBackgroundColor(long nativeContentViewRenderView);
}
}