diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java index 89f25246679a..3b06b14ea636 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java @@ -237,9 +237,9 @@ protected void createContent(final Bundle savedInstanceState) { // Set up the animation placeholder to be the SurfaceView. This disables the // SurfaceView's 'hole' clipping during animations that are notified to the window. mWindowAndroid.setAnimationPlaceholderView( - mShellManager.getContentViewRenderView().getSurfaceView()); + mShellManager.getContentViewRenderView().getAnchorView()); mA11yHelper = - new CobaltA11yHelper(this, mShellManager.getContentViewRenderView().getSurfaceView()); + new CobaltA11yHelper(this, mShellManager.getContentViewRenderView().getAnchorView()); if (mStartupUrl == null || mStartupUrl.isEmpty()) { String[] args = getStarboardBridge().getArgs(); diff --git a/cobalt/shell/android/java/src/dev/cobalt/shell/ContentViewRenderView.java b/cobalt/shell/android/java/src/dev/cobalt/shell/ContentViewRenderView.java index 60667cc77476..86696c62a216 100644 --- a/cobalt/shell/android/java/src/dev/cobalt/shell/ContentViewRenderView.java +++ b/cobalt/shell/android/java/src/dev/cobalt/shell/ContentViewRenderView.java @@ -4,6 +4,7 @@ package dev.cobalt.shell; +import android.app.Activity; import android.content.Context; import android.graphics.PixelFormat; import android.view.Surface; @@ -11,8 +12,11 @@ import android.view.MotionEvent; import android.view.SurfaceView; import android.view.View; +import android.view.Window; import android.widget.FrameLayout; +import org.chromium.base.CommandLine; +import org.chromium.base.Log; import org.chromium.content_public.browser.WebContents; import org.chromium.ui.base.EventForwarder; import org.chromium.ui.base.WindowAndroid; @@ -28,6 +32,8 @@ */ @JNINamespace("cobalt") public class ContentViewRenderView extends FrameLayout { + private static final String TAG = "cobalt"; + // The native side of this object. private long mNativeContentViewRenderView; private WindowAndroid mWindowAndroid; @@ -53,7 +59,12 @@ public ContentViewRenderView(Context context) { } protected SurfaceBridge createSurfaceBridge() { - return new SurfaceBridge(); + if (CommandLine.getInstance().hasSwitch("use-window-surface-for-ui")) { + Log.i(TAG, "ContentViewRenderView: created using WindowSurfaceBridge"); + return new WindowSurfaceBridge(); + } + Log.i(TAG, "ContentViewRenderView: created with SurfaceView"); + return new SurfaceViewBridge(); } /** @@ -62,7 +73,8 @@ protected SurfaceBridge createSurfaceBridge() { * @param rootWindow The {@link WindowAndroid} this render view should be linked to. */ public void onNativeLibraryLoaded(WindowAndroid rootWindow) { - assert !getSurfaceView().getHolder().getSurface().isValid() + assert mSurfaceBridge.getSurfaceView() == null + || !mSurfaceBridge.getSurfaceView().getHolder().getSurface().isValid() : "Surface created before native library loaded."; assert rootWindow != null; mNativeContentViewRenderView = @@ -96,7 +108,10 @@ public void surfaceCreated(SurfaceHolder holder) { // 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. - getSurfaceView().setVisibility(getSurfaceView().getVisibility()); + SurfaceView surfaceView = mSurfaceBridge.getSurfaceView(); + if (surfaceView != null) { + surfaceView.setVisibility(surfaceView.getVisibility()); + } onReadyToRender(); } @@ -108,7 +123,7 @@ public void surfaceDestroyed(SurfaceHolder holder) { mNativeContentViewRenderView, ContentViewRenderView.this); } }; - mSurfaceBridge.connect(surfaceCallback); + mSurfaceBridge.connect(surfaceCallback, rootWindow); } @Override @@ -135,22 +150,12 @@ protected void onWindowVisibilityChanged(int visibility) { } /** - * Sets the background color of the surface 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. - */ - public void setSurfaceViewBackgroundColor(int color) { - if (getSurfaceView() != null) { - getSurfaceView().setBackgroundColor(color); - } - } - - /** - * Gets the SurfaceView for this ContentViewRenderView + * Gets the View used for layout anchoring, animation placeholder, or accessibility (child + * SurfaceView in SurfaceView mode, or this host View in Window Surface mode). */ - public SurfaceView getSurfaceView() { - return mSurfaceBridge.getSurfaceView(); + public View getAnchorView() { + SurfaceView surfaceView = mSurfaceBridge.getSurfaceView(); + return surfaceView != null ? surfaceView : this; } /** @@ -195,47 +200,72 @@ protected SurfaceView createSurfaceView(Context context) { return new SurfaceView(context); } - /** - * @return whether the surface view is initialized and ready to render. - */ - public boolean isInitialized() { - return getSurfaceView().getHolder().getSurface() != null; - } - /** * Enter or leave overlay video mode. * @param enabled Whether overlay mode is enabled. */ public void setOverlayVideoMode(boolean enabled) { int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; - getSurfaceView().getHolder().setFormat(format); + mSurfaceBridge.setFormat(format); ContentViewRenderViewJni.get().setOverlayVideoMode( mNativeContentViewRenderView, ContentViewRenderView.this, enabled); } @CalledByNative private void didSwapFrame() { - if (getSurfaceView().getBackground() != null) { + SurfaceView surfaceView = mSurfaceBridge.getSurfaceView(); + if (surfaceView == null) { + // In Window Surface mode, no child SurfaceView background to clear. + return; + } + + if (surfaceView.getBackground() != null) { post(new Runnable() { @Override public void run() { - getSurfaceView().setBackgroundResource(0); + surfaceView.setBackgroundResource(0); } }); } } /** - * Connecting class to hold a SurfaceView. + * Connecting class to hold a surface management strategy. */ - protected static class SurfaceBridge { + protected abstract static class SurfaceBridge { + protected abstract void initialize(ContentViewRenderView renderView); + protected abstract void connect(SurfaceHolder.Callback surfaceCallback, WindowAndroid windowAndroid); + protected abstract void disconnect(); + protected abstract SurfaceView getSurfaceView(); + protected abstract void setFormat(int format); + } + + /** + * SurfaceBridge implementation that uses a standard SurfaceView. + * This is used for the default rendering path where a child SurfaceView is embedded + * within the ContentViewRenderView. + * + * Lifetime: Bound to the lifetime of the outer ContentViewRenderView. + * Threading: Must be called on the UI thread. + */ + protected static class SurfaceViewBridge extends SurfaceBridge { private SurfaceView mSurfaceView; private SurfaceHolder.Callback mSurfaceCallback; + @Override protected SurfaceView getSurfaceView() { return mSurfaceView; } + @Override + protected void setFormat(int format) { + if (mSurfaceView == null) { + return; + } + mSurfaceView.getHolder().setFormat(format); + } + + @Override protected void initialize(ContentViewRenderView renderView) { mSurfaceView = renderView.createSurfaceView(renderView.getContext()); mSurfaceView.setZOrderMediaOverlay(true); @@ -246,17 +276,119 @@ protected void initialize(ContentViewRenderView renderView) { mSurfaceView.setVisibility(GONE); } - protected void connect(SurfaceHolder.Callback surfaceCallback) { + @Override + protected void connect(SurfaceHolder.Callback surfaceCallback, WindowAndroid windowAndroid) { mSurfaceCallback = surfaceCallback; mSurfaceView.getHolder().addCallback(mSurfaceCallback); mSurfaceView.setVisibility(VISIBLE); } + @Override protected void disconnect() { mSurfaceView.getHolder().removeCallback(mSurfaceCallback); } } + /** + * SurfaceBridge implementation that takes ownership of the Activity's Window surface. + * This allows direct rendering to the window surface instead of a child SurfaceView. + * + * Lifetime: Bound to the lifetime of the outer ContentViewRenderView and the associated Activity. + * Threading: Must be called on the UI thread. + */ + protected static class WindowSurfaceBridge extends SurfaceBridge { + private Window mWindow; + private SurfaceHolder mWindowSurfaceHolder; + + /** + * The last requested PixelFormat (e.g. TRANSLUCENT for overlay video mode, OPAQUE for normal). + * Preserved across surface destruction and recreation so newly created SurfaceHolders + * automatically inherit the desired format. + */ + private Integer mRequestedSurfaceFormat; + + private static Window getWindow(WindowAndroid windowAndroid) { + if (windowAndroid == null || windowAndroid.getActivity() == null) { + return null; + } + Activity activity = windowAndroid.getActivity().get(); + return activity != null ? activity.getWindow() : null; + } + + @Override + protected void initialize(ContentViewRenderView renderView) {} + + @Override + protected void connect( + SurfaceHolder.Callback surfaceCallback, WindowAndroid windowAndroid) { + mWindow = getWindow(windowAndroid); + if (mWindow == null) { + Log.w(TAG, "ContentViewRenderView: WindowSurfaceBridge connect failed: Activity or Window is null."); + return; + } + + mWindow.takeSurface(new SurfaceHolder.Callback2() { + @Override + public void surfaceCreated(SurfaceHolder holder) { + mWindowSurfaceHolder = holder; + if (mRequestedSurfaceFormat != null) { + Log.i(TAG, "ContentViewRenderView: Applying pending format"); + mWindowSurfaceHolder.setFormat(mRequestedSurfaceFormat); + } + surfaceCallback.surfaceCreated(holder); + } + + @Override + public void surfaceChanged( + SurfaceHolder holder, int format, int width, int height) { + mWindowSurfaceHolder = holder; + surfaceCallback.surfaceChanged(holder, format, width, height); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mWindowSurfaceHolder = null; + surfaceCallback.surfaceDestroyed(holder); + } + + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + if (!(surfaceCallback instanceof SurfaceHolder.Callback2)) { + return; + } + ((SurfaceHolder.Callback2) surfaceCallback).surfaceRedrawNeeded(holder); + } + }); + } + + @Override + protected void disconnect() { + if (mWindow != null) { + mWindow.takeSurface(null); + } else { + Log.w(TAG, "ContentViewRenderView: disconnect() is called w/o connect()."); + } + mWindow = null; + mWindowSurfaceHolder = null; + mRequestedSurfaceFormat = null; + } + + @Override + protected SurfaceView getSurfaceView() { + return null; + } + + @Override + protected void setFormat(int format) { + mRequestedSurfaceFormat = format; + if (mWindowSurfaceHolder == null) { + Log.i(TAG, "ContentViewRenderView: surface is not ready yet. Will apply format later"); + return; + } + mWindowSurfaceHolder.setFormat(format); + } + } + private EventForwarder getEventForwarder() { if (mWebContents == null || mWebContents.isDestroyed()) { return null;