blob: e7e249988e4a093f026c62f900c65d0787419943 [file] [log] [blame]
// Copyright 2016 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.chrome.browser.vr_shell;
import static android.opengl.GLES20.GL_NEAREST;
import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glGenTextures;
import static android.opengl.GLES20.glTexParameteri;
import android.app.Activity;
import android.graphics.SurfaceTexture;
import android.graphics.SurfaceTexture.OnFrameAvailableListener;
import android.opengl.GLES11Ext;
import android.opengl.GLSurfaceView;
import android.os.StrictMode;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.google.vr.ndk.base.AndroidCompat;
import com.google.vr.ndk.base.GvrLayout;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.UsedByReflection;
import org.chromium.chrome.browser.ChromeVersionInfo;
import org.chromium.chrome.browser.WebContentsFactory;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content.browser.ContentView;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* This view extends from GvrLayout which wraps a GLSurfaceView that renders VR shell.
*/
@JNINamespace("vr_shell")
@UsedByReflection("VrShellDelegate.java")
public class VrShell extends GvrLayout implements GLSurfaceView.Renderer, VrShellInterface {
private static final String TAG = "VrShell";
@UsedByReflection("VrShellDelegate.java")
public static final String VR_EXTRA = com.google.vr.sdk.base.Constants.EXTRA_VR_LAUNCH;
private Activity mActivity;
private final GLSurfaceView mGlSurfaceView;
private long mNativeVrShell = 0;
private int mContentTextureHandle;
private int mUiTextureHandle;
private FrameListener mContentFrameListener;
private FrameListener mUiFrameListener;
private FrameLayout mContentViewCoreContainer;
// The tab that holds the main ContentViewCore.
private Tab mTab;
// The ContentViewCore for the main content rect in VR.
private ContentViewCore mContentCVC;
// The non-VR container view for mContentCVC.
private ViewGroup mOriginalContentViewParent;
// TODO(mthiesse): Instead of caching these values, make tab reparenting work for this case.
private int mOriginalContentViewIndex;
private ViewGroup.LayoutParams mOriginalLayoutParams;
private WindowAndroid mOriginalWindowAndroid;
private VrWindowAndroid mContentVrWindowAndroid;
private WebContents mUiContents;
private ContentViewCore mUiCVC;
private VrWindowAndroid mUiVrWindowAndroid;
@UsedByReflection("VrShellDelegate.java")
public VrShell(Activity activity) {
super(activity);
mActivity = activity;
mContentViewCoreContainer = new FrameLayout(getContext()) {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return true;
}
};
addView(mContentViewCoreContainer, 0, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
mGlSurfaceView = new GLSurfaceView(getContext());
mGlSurfaceView.setEGLContextClientVersion(2);
mGlSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 0, 0);
mGlSurfaceView.setPreserveEGLContextOnPause(true);
mGlSurfaceView.setRenderer(this);
setPresentationView(mGlSurfaceView);
if (setAsyncReprojectionEnabled(true)) {
AndroidCompat.setSustainedPerformanceMode(mActivity, true);
}
}
@Override
public void initializeNative(Tab currentTab, VrShellDelegate delegate) {
assert currentTab.getContentViewCore() != null;
mTab = currentTab;
mContentCVC = mTab.getContentViewCore();
mContentVrWindowAndroid = new VrWindowAndroid(mActivity);
mUiVrWindowAndroid = new VrWindowAndroid(mActivity);
mUiContents = WebContentsFactory.createWebContents(true, false);
mUiCVC = new ContentViewCore(mActivity, ChromeVersionInfo.getProductVersion());
ContentView uiContentView = ContentView.createContentView(mActivity, mUiCVC);
mUiCVC.initialize(ViewAndroidDelegate.createBasicDelegate(uiContentView),
uiContentView, mUiContents, mUiVrWindowAndroid);
mNativeVrShell = nativeInit(mContentCVC.getWebContents(),
mContentVrWindowAndroid.getNativePointer(),
mUiContents, mUiVrWindowAndroid.getNativePointer());
uiContentView.setVisibility(View.VISIBLE);
mUiCVC.onShow();
mContentViewCoreContainer.addView(uiContentView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
mUiCVC.setBottomControlsHeight(0);
mUiCVC.setTopControlsHeight(0, false);
mUiVrWindowAndroid.onVisibilityChanged(true);
nativeSetDelegate(mNativeVrShell, delegate);
reparentContentWindow();
nativeUpdateCompositorLayers(mNativeVrShell);
}
private void reparentContentWindow() {
mOriginalWindowAndroid = mContentCVC.getWindowAndroid();
// TODO(mthiesse): Update the WindowAndroid in ChromeActivity too?
mTab.updateWindowAndroid(null);
mTab.updateWindowAndroid(mContentVrWindowAndroid);
ViewGroup contentContentView = mContentCVC.getContainerView();
mOriginalContentViewParent = ((ViewGroup) contentContentView.getParent());
mOriginalContentViewIndex = mOriginalContentViewParent.indexOfChild(contentContentView);
mOriginalLayoutParams = contentContentView.getLayoutParams();
mOriginalContentViewParent.removeView(contentContentView);
mContentViewCoreContainer.addView(contentContentView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
}
private void restoreContentWindow() {
ViewGroup contentContentView = mContentCVC.getContainerView();
mTab.updateWindowAndroid(null);
mTab.updateWindowAndroid(mOriginalWindowAndroid);
mContentViewCoreContainer.removeView(contentContentView);
mOriginalContentViewParent.addView(contentContentView, mOriginalContentViewIndex,
mOriginalLayoutParams);
}
private static class FrameListener implements OnFrameAvailableListener {
final SurfaceTexture mSurfaceTexture;
final GLSurfaceView mGlSurfaceView;
boolean mFirstTex = true;
final Runnable mUpdateTexImage = new Runnable() {
@Override
public void run() {
try {
mSurfaceTexture.updateTexImage();
} catch (IllegalStateException e) {
}
}
};
public FrameListener(int textureId, GLSurfaceView glSurfaceView) {
mSurfaceTexture = new SurfaceTexture(textureId);
mSurfaceTexture.setOnFrameAvailableListener(this);
mGlSurfaceView = glSurfaceView;
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mFirstTex = false;
mGlSurfaceView.queueEvent(mUpdateTexImage);
}
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
final int width = mContentCVC.getContainerView().getWidth();
final int height = mContentCVC.getContainerView().getHeight();
mContentTextureHandle = createExternalTextureHandle();
mUiTextureHandle = createExternalTextureHandle();
mContentFrameListener = new FrameListener(mContentTextureHandle, mGlSurfaceView);
mUiFrameListener = new FrameListener(mUiTextureHandle, mGlSurfaceView);
mContentFrameListener.mSurfaceTexture.setDefaultBufferSize(width, height);
mUiFrameListener.mSurfaceTexture.setDefaultBufferSize(width, height);
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
nativeContentSurfaceChanged(mNativeVrShell, width, height,
new Surface(mContentFrameListener.mSurfaceTexture));
mContentCVC.onPhysicalBackingSizeChanged(width, height);
nativeUiSurfaceChanged(mNativeVrShell, width, height,
new Surface(mUiFrameListener.mSurfaceTexture));
mUiCVC.onPhysicalBackingSizeChanged(width, height);
}
});
nativeGvrInit(mNativeVrShell, getGvrApi().getNativeGvrContext());
nativeInitializeGl(mNativeVrShell, mContentTextureHandle, mUiTextureHandle);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {}
@Override
public void onDrawFrame(GL10 gl) {
// Make sure we've updated the texture at least once. We do this because onFrameAvailable
// isn't guaranteed to have fired after transitioning to VR. It only fires when the texture
// is updated either through scrolling, resizing, etc. - none of which we're guaranteed to
// have done on transition.
if (mUiFrameListener.mFirstTex) {
mUiFrameListener.mSurfaceTexture.updateTexImage();
mUiFrameListener.mFirstTex = false;
}
if (mContentFrameListener.mFirstTex) {
mContentFrameListener.mSurfaceTexture.updateTexImage();
mContentFrameListener.mFirstTex = false;
}
nativeDrawFrame(mNativeVrShell);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
nativeOnTriggerEvent(mNativeVrShell);
}
// Don't mark this as handled so that Daydream screen alignment still functions.
return false;
}
@Override
public void onResume() {
super.onResume();
if (mNativeVrShell != 0) {
// Refreshing the viewer profile accesses disk, so we need to temporarily allow disk
// reads. The GVR team promises this will be fixed when they launch.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
nativeOnResume(mNativeVrShell);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
}
@Override
public void onPause() {
super.onPause();
if (mNativeVrShell != 0) {
nativeOnPause(mNativeVrShell);
}
}
@Override
public void shutdown() {
super.shutdown();
if (mNativeVrShell != 0) {
nativeDestroy(mNativeVrShell);
}
if (mContentFrameListener != null && mContentFrameListener.mSurfaceTexture != null) {
mContentFrameListener.mSurfaceTexture.release();
}
if (mUiFrameListener != null && mUiFrameListener.mSurfaceTexture != null) {
mUiFrameListener.mSurfaceTexture.release();
}
restoreContentWindow();
}
@Override
public void pause() {
onPause();
}
@Override
public void resume() {
onResume();
}
@Override
public void teardown() {
shutdown();
}
@Override
public void setVrModeEnabled(boolean enabled) {
AndroidCompat.setVrModeEnabled(mActivity, enabled);
}
@Override
public void setWebVrModeEnabled(boolean enabled) {
nativeSetWebVrMode(mNativeVrShell, enabled);
}
@Override
public FrameLayout getContainer() {
return (FrameLayout) this;
}
/**
* Create a new GLES11Ext.GL_TEXTURE_EXTERNAL_OES texture handle.
* @return New texture handle.
*/
private int createExternalTextureHandle() {
int[] textureDataHandle = new int[1];
glGenTextures(1, textureDataHandle, 0);
if (textureDataHandle[0] != 0) {
glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureDataHandle[0]);
glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
return textureDataHandle[0];
} else {
throw new RuntimeException("Error generating texture handle.");
}
}
private native long nativeInit(WebContents contentWebContents, long nativeContentWindowAndroid,
WebContents uiWebContents, long nativeUiWindowAndroid);
private native void nativeSetDelegate(long nativeVrShell, VrShellDelegate delegate);
private native void nativeGvrInit(long nativeVrShell, long nativeGvrApi);
private native void nativeDestroy(long nativeVrShell);
private native void nativeInitializeGl(
long nativeVrShell, int contentTextureHandle, int uiTextureHandle);
private native void nativeDrawFrame(long nativeVrShell);
private native void nativeOnTriggerEvent(long nativeVrShell);
private native void nativeOnPause(long nativeVrShell);
private native void nativeOnResume(long nativeVrShell);
private native void nativeContentSurfaceChanged(
long nativeVrShell, int width, int height, Surface surface);
private native void nativeUiSurfaceChanged(
long nativeVrShell, int width, int height, Surface surface);
private native void nativeUpdateCompositorLayers(long nativeVrShell);
private native void nativeSetWebVrMode(long nativeVrShell, boolean enabled);
}