chromium/components/webxr/android/java/src/org/chromium/components/webxr/ArOverlayDelegate.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.webxr;

import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;

import org.chromium.base.Log;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsAccessibility;

/** Provides a fullscreen overlay for immersive AR mode. */
public class ArOverlayDelegate implements XrImmersiveOverlay.Delegate {
    private static final String TAG = "ArOverlayDelegate";
    private static final boolean DEBUG_LOGS = false;

    private ArCompositorDelegate mArCompositorDelegate;
    private boolean mUseOverlay;
    private boolean mCanRenderDomContent;
    private boolean mDomSurfaceNeedsConfiguring;
    private WebContents mWebContents;

    public ArOverlayDelegate(
            @NonNull ArCompositorDelegate compositorDelegate,
            final WebContents webContents,
            boolean useOverlay,
            boolean canRenderDomContent) {
        if (DEBUG_LOGS) Log.i(TAG, "constructor");

        mArCompositorDelegate = compositorDelegate;
        mWebContents = webContents;

        mUseOverlay = useOverlay;
        mCanRenderDomContent = canRenderDomContent;
        mDomSurfaceNeedsConfiguring = mUseOverlay && !mCanRenderDomContent;
    }

    @Override
    public void prepareToCreateSurfaceView() {
        if (DEBUG_LOGS) {
            Log.i(TAG, "calling mArCompositorDelegate.setOverlayImmersiveArMode(true)");
        }

        // While it's fine to omit if the page does not use DOMOverlay, once the page does
        // use DOMOverlay, something appears to have changed such that it becomes required,
        // otherwise the DOM SurfaceView will be in front of the XR content. This is done before
        // the surface view is created so that it ends up on top if the Dom Surface did not need
        // configuring.
        mArCompositorDelegate.setOverlayImmersiveArMode(true, mDomSurfaceNeedsConfiguring);
    }

    @Override
    public void configureSurfaceView(SurfaceView surfaceView) {
        surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);

        // Exactly one surface view needs to call setZOrderMediaOverlay(true) otherwise the
        // resulting z-order is undefined. The DOM content's surface will set it to true if
        // |mDomSurfaceNeedsConfiguring| is set so we need to do the opposite here.
        surfaceView.setZOrderMediaOverlay(!mDomSurfaceNeedsConfiguring);

        if (!mUseOverlay) {
            WebContentsAccessibility.fromWebContents(mWebContents).setObscuredByAnotherView(true);
        }
    }

    @Override
    public void parentAndShowSurfaceView(SurfaceView surfaceView) {
        if (DEBUG_LOGS) Log.i(TAG, "Parenting Surface for AR");
        ViewGroup parent = mArCompositorDelegate.getArSurfaceParent();

        // If we need to toggle the parent's visibility, do it before we add the surfaceView.
        if (mArCompositorDelegate.shouldToggleArSurfaceParentVisibility()) {
            parent.setVisibility(View.VISIBLE);
        }

        parent.addView(surfaceView);
    }

    @Override
    public void onStopUsingSurfaceView() {
        mArCompositorDelegate.setOverlayImmersiveArMode(false, mDomSurfaceNeedsConfiguring);
    }

    @Override
    public void removeAndHideSurfaceView(SurfaceView surfaceView) {
        if (surfaceView == null) return;
        ViewGroup parent = (ViewGroup) surfaceView.getParent();

        if (!mUseOverlay) {
            WebContentsAccessibility.fromWebContents(mWebContents).setObscuredByAnotherView(false);
        }

        if (parent != null) {
            // Remove the surfaceView before changing the parent's visibility, so that we
            // don't trigger any duplicate destruction events.
            parent.removeView(surfaceView);

            if (mArCompositorDelegate.shouldToggleArSurfaceParentVisibility()) {
                parent.setVisibility(View.GONE);
            }
        }
    }

    @Override
    public void maybeForwardTouchEvent(MotionEvent ev) {
        // DOM Overlay mode needs to forward the touch to the content view so that its UI elements
        // keep working.
        if (mUseOverlay) {
            mArCompositorDelegate.dispatchTouchEvent(ev);
        }
    }

    @Override
    public int getDesiredOrientation() {
        return Configuration.ORIENTATION_UNDEFINED;
    }

    @Override
    public boolean useDisplaySizes() {
        // When in AR, it is expected to occupy the entire screen even if there is a notch.
        return true;
    }
}