chromium/components/webxr/android/java/src/org/chromium/components/webxr/CardboardOverlayDelegate.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.app.Activity;
import android.content.res.Configuration;
import android.view.Gravity;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.PopupMenu;

import androidx.annotation.NonNull;

import org.chromium.base.Log;
import org.chromium.content_public.browser.LoadUrlParams;

/** Provides a fullscreen overlay for immersive Cardboard (VR) mode. */
public class CardboardOverlayDelegate
        implements XrImmersiveOverlay.Delegate, PopupMenu.OnMenuItemClickListener {
    private static final String TAG = "CardboardOverlay";
    private static final String PRODUCT_SAFETY_URL = "google.com/get/cardboard/product-safety";
    private static final boolean DEBUG_LOGS = false;
    static final int VR_SYSTEM_UI_FLAGS =
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;

    private Activity mActivity;
    private VrCompositorDelegate mCompositorDelegate;

    private View mCardboardView;

    public CardboardOverlayDelegate(
            VrCompositorDelegate compositorDelegate, @NonNull Activity activity) {
        if (DEBUG_LOGS) {
            Log.i(TAG, "constructor");
        }

        mActivity = activity;
        mCompositorDelegate = compositorDelegate;
    }

    private void setupWidgetsLayout() {
        mCardboardView = mActivity.getLayoutInflater().inflate(R.layout.cardboard_ui, null);

        // Close button.
        ImageButton closeButton = mCardboardView.findViewById(R.id.cardboard_ui_back_button);
        closeButton.setOnClickListener(
                new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        XrSessionCoordinator.endActiveSession();
                    }
                });

        // Settings button.
        ImageButton settingsButton = mCardboardView.findViewById(R.id.cardboard_ui_settings_button);
        settingsButton.setOnClickListener(
                new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        showSettings(v);
                    }
                });
    }

    /**
     * Returns the view which contains the Content and Controls of the underlying Chrome instance
     * which are hidden when in VR.
     */
    private View getContentView() {
        return mActivity.getWindow().findViewById(android.R.id.content);
    }

    /**
     * Returns the view that should be used as the parent root for the cardboard UI. Must not be
     * below the content view.
     */
    private ViewGroup getParentView() {
        return (ViewGroup) getContentView().getParent();
    }

    public void showSettings(View view) {
        PopupMenu popup =
                new PopupMenu(mActivity, view, Gravity.END, 0, R.style.CardboardSettingsPopupMenu);
        MenuInflater inflater = popup.getMenuInflater();
        inflater.inflate(R.menu.settings_menu, popup.getMenu());
        popup.setOnMenuItemClickListener(this);
        popup.show();
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        if (item.getItemId() == R.id.cardboard_menu_option_use_another_device) {
            XrSessionCoordinator.onActiveXrSessionButtonTouched();
            XrSessionCoordinator.endActiveSession();
            return true;
        } else if (item.getItemId() == R.id.cardboard_menu_option_product_safety) {
            LoadUrlParams url = new LoadUrlParams(PRODUCT_SAFETY_URL);
            // Storing this value in a new variable as the ending the active
            // session  could clear it otherwise.
            VrCompositorDelegate delegate = mCompositorDelegate;
            XrSessionCoordinator.endActiveSession();
            delegate.openNewTab(url);
            return true;
        }
        return false;
    }

    @Override
    public void prepareToCreateSurfaceView() {
        mCompositorDelegate.setOverlayImmersiveVrMode(true);

        setupWidgetsLayout();
        getParentView().addView(mCardboardView);
    }

    @Override
    public void configureSurfaceView(SurfaceView surfaceView) {}

    @Override
    public void parentAndShowSurfaceView(SurfaceView surfaceView) {
        if (DEBUG_LOGS) {
            Log.i(TAG, "Parenting Surface for AR");
        }

        // We need to hide the Content View from Accessibility while we are in VR, otherwise the
        // tools will try to go through all of the tab control and web content rather than just the
        // controls that are actually visible. Note that we also need to hide descendants.
        getContentView()
                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);

        int flags = mActivity.getWindow().getDecorView().getSystemUiVisibility();
        mActivity.getWindow().getDecorView().setSystemUiVisibility(flags | VR_SYSTEM_UI_FLAGS);

        FrameLayout surface_view_holder =
                (FrameLayout) mCardboardView.findViewById(R.id.surface_view_holder);
        surface_view_holder.addView(surfaceView);
    }

    @Override
    public void onStopUsingSurfaceView() {
        mCompositorDelegate.setOverlayImmersiveVrMode(false);
    }

    @Override
    public void removeAndHideSurfaceView(SurfaceView surfaceView) {
        if (surfaceView == null) {
            return;
        }

        // Restore the accessibility state of the underlying Chrome content when we stop covering it
        // with the Cardboard view.
        getContentView().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);

        int flags = mActivity.getWindow().getDecorView().getSystemUiVisibility();
        mActivity.getWindow().getDecorView().setSystemUiVisibility(flags & ~VR_SYSTEM_UI_FLAGS);

        ViewGroup parent = getParentView();
        if (parent != null) {
            parent.removeView(mCardboardView);
        }
    }

    @Override
    public void maybeForwardTouchEvent(MotionEvent ev) {}

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

    @Override
    public boolean useDisplaySizes() {
        // When in VR, it is expected to occupy only the safe area taking into account the notch
        // of the device.
        return false;
    }
}