// 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 android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.StrictMode;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.widget.FrameLayout;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.tab.Tab;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Manages interactions with the VR Shell.
*/
@JNINamespace("vr_shell")
public class VrShellDelegate {
private static final String TAG = "VrShellDelegate";
private ChromeTabbedActivity mActivity;
private boolean mVrShellEnabled;
private Class<? extends VrShellInterface> mVrShellClass;
private VrShellInterface mVrShell;
private boolean mInVr;
private int mRestoreSystemUiVisibilityFlag = -1;
private String mVrExtra;
private long mNativeVrShellDelegate;
public VrShellDelegate(ChromeTabbedActivity activity) {
mActivity = activity;
mVrShellClass = maybeFindVrShell();
if (mVrShellClass != null) {
mVrShellEnabled = true;
try {
mVrExtra = (String) mVrShellClass.getField("VR_EXTRA").get(null);
} catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException e) {
Log.e(TAG, "Unable to read VR_EXTRA field", e);
mVrShellEnabled = false;
}
}
}
/**
* Should be called once the native library is loaded so that the native portion of this
* class can be initialized.
*/
public void onNativeLibraryReady() {
if (mVrShellEnabled) {
mNativeVrShellDelegate = nativeInit();
}
}
@SuppressWarnings("unchecked")
private Class<? extends VrShellInterface> maybeFindVrShell() {
try {
return (Class<? extends VrShellInterface>) Class
.forName("org.chromium.chrome.browser.vr_shell.VrShell");
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Enters VR Shell, displaying browser UI and tab contents in VR.
*
* This function performs native initialization, and so must only be called after native
* libraries are ready.
* @param inWebVR If true should begin displaying WebVR content rather than the VrShell UI.
* @return Whether or not we are in VR when this function returns.
*/
@CalledByNative
public boolean enterVRIfNecessary(boolean inWebVR) {
if (!mVrShellEnabled || mNativeVrShellDelegate == 0) return false;
Tab tab = mActivity.getActivityTab();
// TODO(mthiesse): When we have VR UI for opening new tabs, etc., allow VR Shell to be
// entered without any current tabs.
if (tab == null || tab.getContentViewCore() == null) {
return false;
}
if (mInVr) return true;
// VrShell must be initialized in Landscape mode due to a bug in the GVR library.
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
if (!createVrShell()) {
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
return false;
}
addVrViews();
setupVrModeWindowFlags();
mVrShell.initializeNative(tab, this);
if (inWebVR) mVrShell.setWebVrModeEnabled(true);
mVrShell.setVrModeEnabled(true);
mInVr = true;
tab.updateFullscreenEnabledState();
return true;
}
@CalledByNative
private boolean exitWebVR() {
if (!mInVr) return false;
mVrShell.setWebVrModeEnabled(false);
// TODO(bajones): Once VR Shell can be invoked outside of WebVR this
// should no longer exit the shell outright. Need a way to determine
// how VrShell was created.
shutdownVR();
return true;
}
/**
* Resumes VR Shell.
*/
public void resumeVR() {
setupVrModeWindowFlags();
mVrShell.resume();
}
/**
* Pauses VR Shell.
*/
public void pauseVR() {
mVrShell.pause();
}
/**
* Exits the current VR mode (WebVR or VRShell)
* @return Whether or not we exited VR.
*/
public boolean exitVRIfNecessary() {
if (!mInVr) return false;
// If WebVR is presenting instruct it to exit. VR mode should not
// exit in this scenario, in case we want to return to the VrShell.
if (!nativeExitWebVRIfNecessary(mNativeVrShellDelegate)) {
// If WebVR was not presenting, shutdown VR mode entirely.
shutdownVR();
}
return true;
}
/**
* Exits VR Shell, performing all necessary cleanup.
*/
private void shutdownVR() {
if (!mInVr) return;
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
mVrShell.setVrModeEnabled(false);
mVrShell.pause();
removeVrViews();
clearVrModeWindowFlags();
destroyVrShell();
mInVr = false;
Tab tab = mActivity.getActivityTab();
if (tab != null) tab.updateFullscreenEnabledState();
}
private boolean createVrShell() {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
Constructor<?> vrShellConstructor = mVrShellClass.getConstructor(Activity.class);
mVrShell = (VrShellInterface) vrShellConstructor.newInstance(mActivity);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException e) {
Log.e(TAG, "Unable to instantiate VrShell", e);
return false;
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
return true;
}
private void addVrViews() {
FrameLayout decor = (FrameLayout) mActivity.getWindow().getDecorView();
LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
decor.addView(mVrShell.getContainer(), params);
mActivity.setUIVisibilityForVR(View.GONE);
}
private void removeVrViews() {
mActivity.setUIVisibilityForVR(View.VISIBLE);
FrameLayout decor = (FrameLayout) mActivity.getWindow().getDecorView();
decor.removeView(mVrShell.getContainer());
}
private void setupVrModeWindowFlags() {
if (mRestoreSystemUiVisibilityFlag == -1) {
mRestoreSystemUiVisibilityFlag = mActivity.getWindow().getDecorView()
.getSystemUiVisibility();
}
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mActivity.getWindow().getDecorView().setSystemUiVisibility(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 void clearVrModeWindowFlags() {
mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (mRestoreSystemUiVisibilityFlag != -1) {
mActivity.getWindow().getDecorView()
.setSystemUiVisibility(mRestoreSystemUiVisibilityFlag);
}
mRestoreSystemUiVisibilityFlag = -1;
}
/**
* Clean up VrShell, and associated native objects.
*/
public void destroyVrShell() {
if (mVrShell != null) {
mVrShell.teardown();
mVrShell = null;
}
}
/**
* Whether or not the intent is a Daydream VR Intent.
*/
public boolean isVrIntent(Intent intent) {
if (intent == null) return false;
return intent.getBooleanExtra(mVrExtra, false);
}
/**
* Whether or not we are currently in VR.
*/
public boolean isInVR() {
return mInVr;
}
/**
* @return Whether or not VR Shell is currently enabled.
*/
public boolean isVrShellEnabled() {
return mVrShellEnabled;
}
/**
* @return Pointer to the native VrShellDelegate object.
*/
@CalledByNative
private long getNativePointer() {
return mNativeVrShellDelegate;
}
private native long nativeInit();
private native boolean nativeExitWebVRIfNecessary(long nativeVrShellDelegate);
}