blob: 2e8fba0709b366f185af5a76031929bcea3cb4e0 [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.
#include "chrome/browser/android/vr_shell/vr_shell.h"
#include <android/native_window_jni.h>
#include <string>
#include <utility>
#include "base/android/jni_string.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/android/vr_shell/ui_interface.h"
#include "chrome/browser/android/vr_shell/vr_compositor.h"
#include "chrome/browser/android/vr_shell/vr_gl_thread.h"
#include "chrome/browser/android/vr_shell/vr_input_manager.h"
#include "chrome/browser/android/vr_shell/vr_shell_delegate.h"
#include "chrome/browser/android/vr_shell/vr_shell_gl.h"
#include "chrome/browser/android/vr_shell/vr_usage_monitor.h"
#include "chrome/browser/android/vr_shell/vr_web_contents_observer.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer.h"
#include "device/vr/android/gvr/gvr_device.h"
#include "device/vr/android/gvr/gvr_device_provider.h"
#include "jni/VrShellImpl_jni.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "ui/android/view_android.h"
#include "ui/android/window_android.h"
#include "ui/base/page_transition_types.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/transform.h"
#include "ui/gfx/transform_util.h"
using base::android::JavaParamRef;
using base::android::JavaRef;
namespace vr_shell {
namespace {
vr_shell::VrShell* g_instance;
static const char kVrShellUIURL[] = "chrome://vr-shell-ui";
void SetIsInVR(content::WebContents* contents, bool is_in_vr) {
if (contents && contents->GetRenderWidgetHostView())
contents->GetRenderWidgetHostView()->SetIsInVR(is_in_vr);
}
} // namespace
VrShell::VrShell(JNIEnv* env,
jobject obj,
ui::WindowAndroid* content_window,
content::WebContents* ui_contents,
ui::WindowAndroid* ui_window,
bool for_web_vr,
VrShellDelegate* delegate,
gvr_context* gvr_api,
bool reprojected_rendering)
: WebContentsObserver(ui_contents),
vr_shell_enabled_(base::FeatureList::IsEnabled(features::kVrShell)),
content_window_(content_window),
content_compositor_(
base::MakeUnique<VrCompositor>(content_window_, false)),
ui_contents_(ui_contents),
ui_compositor_(base::MakeUnique<VrCompositor>(ui_window, true)),
delegate_provider_(delegate),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
reprojected_rendering_(reprojected_rendering),
gvr_api_(gvr_api),
weak_ptr_factory_(this) {
DCHECK(g_instance == nullptr);
g_instance = this;
j_vr_shell_.Reset(env, obj);
ui_input_manager_ = base::MakeUnique<VrInputManager>(ui_contents_);
ui_compositor_->SetLayer(ui_contents_);
gl_thread_ = base::MakeUnique<VrGLThread>(
weak_ptr_factory_.GetWeakPtr(), delegate_provider_->GetWeakPtr(),
main_thread_task_runner_, gvr_api, for_web_vr, reprojected_rendering_);
base::Thread::Options options(base::MessageLoop::TYPE_DEFAULT, 0);
options.priority = base::ThreadPriority::DISPLAY;
gl_thread_->StartWithOptions(options);
html_interface_ = base::MakeUnique<UiInterface>(
for_web_vr ? UiInterface::Mode::WEB_VR : UiInterface::Mode::STANDARD);
}
void VrShell::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
delete this;
}
void VrShell::SwapContents(JNIEnv* env, const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& web_contents) {
content::WebContents* contents =
content::WebContents::FromJavaWebContents(web_contents);
if (contents == main_contents_)
return;
SetIsInVR(main_contents_, false);
main_contents_ = contents;
content_compositor_->SetLayer(main_contents_);
SetIsInVR(main_contents_, true);
ContentFrameWasResized(false /* unused */);
SetUiState();
if (!main_contents_) {
content_input_manager_ = nullptr;
vr_web_contents_observer_ = nullptr;
metrics_helper_ = nullptr;
return;
}
content_input_manager_ = base::MakeUnique<VrInputManager>(main_contents_);
vr_web_contents_observer_ = base::MakeUnique<VrWebContentsObserver>(
main_contents_, html_interface_.get(), this);
// TODO(billorr): Make VrMetricsHelper tab-aware and able to track multiple
// tabs. crbug.com/684661
metrics_helper_ = base::MakeUnique<VrMetricsHelper>(main_contents_);
metrics_helper_->SetVRActive(true);
metrics_helper_->SetWebVREnabled(webvr_mode_);
}
void VrShell::SetUiState() {
if (!main_contents_) {
// TODO(mthiesse): Properly handle native page URLs.
html_interface_->SetURL(GURL());
html_interface_->SetLoading(false);
html_interface_->SetFullscreen(false);
} else {
html_interface_->SetURL(main_contents_->GetVisibleURL());
html_interface_->SetLoading(main_contents_->IsLoading());
html_interface_->SetFullscreen(main_contents_->IsFullscreen());
}
}
void VrShell::LoadUIContent(JNIEnv* env, const JavaParamRef<jobject>& obj) {
GURL url(kVrShellUIURL);
ui_contents_->GetController().LoadURL(
url, content::Referrer(),
ui::PageTransition::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string(""));
}
bool RegisterVrShell(JNIEnv* env) {
return RegisterNativesImpl(env);
}
VrShell::~VrShell() {
{
// The GvrLayout is, and must always be, used only on the UI thread, and the
// GvrApi used for rendering should only be used from the GL thread as it's
// not thread safe. However, the GvrLayout owns the GvrApi instance, and
// when it gets shut down it deletes the GvrApi instance with it. Therefore,
// we need to block shutting down the GvrLayout on stopping our GL thread
// from using the GvrApi instance.
// base::Thread::Stop, which is called when destroying the thread, asserts
// that IO is allowed to prevent jank, but there shouldn't be any concerns
// regarding jank in this case, because we're switching from 3D to 2D,
// adding/removing a bunch of Java views, and probably changing device
// orientation here.
base::ThreadRestrictions::ScopedAllowIO allow_io;
gl_thread_.reset();
}
delegate_provider_->RemoveDelegate();
g_instance = nullptr;
}
void VrShell::PostToGlThreadWhenReady(const base::Closure& task) {
// TODO(mthiesse): Remove this blocking wait. Queue up events if thread isn't
// finished starting?
gl_thread_->WaitUntilThreadStarted();
gl_thread_->task_runner()->PostTask(FROM_HERE, task);
}
void VrShell::SetContentPaused(bool paused) {
if (content_paused_ == paused)
return;
content_paused_ = paused;
if (!delegate_provider_->device_provider())
return;
// TODO(mthiesse): The page is no longer visible when in menu mode. We
// should unfocus or otherwise let it know it's hidden.
if (paused) {
delegate_provider_->device_provider()->Device()->OnBlur();
} else {
delegate_provider_->device_provider()->Device()->OnFocus();
}
}
void VrShell::OnTriggerEvent(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
gl_thread_->task_runner()->PostTask(
FROM_HERE,
base::Bind(&VrShellGl::OnTriggerEvent, gl_thread_->GetVrShellGl()));
}
void VrShell::OnPause(JNIEnv* env, const JavaParamRef<jobject>& obj) {
gl_thread_->task_runner()->PostTask(
FROM_HERE, base::Bind(&VrShellGl::OnPause, gl_thread_->GetVrShellGl()));
// exit vr session
if (metrics_helper_)
metrics_helper_->SetVRActive(false);
SetIsInVR(main_contents_, false);
}
void VrShell::OnResume(JNIEnv* env, const JavaParamRef<jobject>& obj) {
gl_thread_->task_runner()->PostTask(
FROM_HERE, base::Bind(&VrShellGl::OnResume, gl_thread_->GetVrShellGl()));
if (metrics_helper_)
metrics_helper_->SetVRActive(true);
SetIsInVR(main_contents_, true);
}
void VrShell::SetSurface(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& surface) {
CHECK(!reprojected_rendering_);
gfx::AcceleratedWidget window =
ANativeWindow_fromSurface(base::android::AttachCurrentThread(), surface);
PostToGlThreadWhenReady(base::Bind(&VrShellGl::InitializeGl,
gl_thread_->GetVrShellGl(),
base::Unretained(window)));
}
base::WeakPtr<VrShell> VrShell::GetWeakPtr(
const content::WebContents* web_contents) {
// Ensure that the WebContents requesting the VrShell instance is the one
// we created.
if (g_instance != nullptr && g_instance->ui_contents_ == web_contents)
return g_instance->weak_ptr_factory_.GetWeakPtr();
return base::WeakPtr<VrShell>(nullptr);
}
void VrShell::OnDomContentsLoaded() {
SetUiState();
html_interface_->OnDomContentsLoaded();
}
void VrShell::SetWebVrMode(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
bool enabled) {
webvr_mode_ = enabled;
if (metrics_helper_)
metrics_helper_->SetWebVREnabled(enabled);
PostToGlThreadWhenReady(base::Bind(&VrShellGl::SetWebVrMode,
gl_thread_->GetVrShellGl(), enabled));
html_interface_->SetMode(
enabled ? UiInterface::Mode::WEB_VR : UiInterface::Mode::STANDARD);
}
void VrShell::OnLoadProgressChanged(JNIEnv* env,
const JavaParamRef<jobject>& obj,
double progress) {
html_interface_->SetLoadProgress(progress);
}
void VrShell::OnTabListCreated(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jobjectArray tabs,
jobjectArray incognito_tabs) {
html_interface_->InitTabList();
ProcessTabArray(env, tabs, false);
ProcessTabArray(env, incognito_tabs, true);
html_interface_->FlushTabList();
}
void VrShell::ProcessTabArray(JNIEnv* env, jobjectArray tabs, bool incognito) {
size_t len = env->GetArrayLength(tabs);
for (size_t i = 0; i < len; ++i) {
jobject jtab = env->GetObjectArrayElement(tabs, i);
TabAndroid* tab =
TabAndroid::GetNativeTab(env, JavaParamRef<jobject>(env, jtab));
html_interface_->AppendToTabList(incognito, tab->GetAndroidId(),
tab->GetTitle());
}
}
void VrShell::OnTabUpdated(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jboolean incognito, jint id, jstring jtitle) {
std::string title;
base::android::ConvertJavaStringToUTF8(env, jtitle, &title);
html_interface_->UpdateTab(incognito, id, title);
}
void VrShell::OnTabRemoved(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jboolean incognito, jint id) {
html_interface_->RemoveTab(incognito, id);
}
void VrShell::SetWebVRSecureOrigin(bool secure_origin) {
// TODO(cjgrant): Align this state with the logic that drives the omnibox.
html_interface_->SetWebVRSecureOrigin(secure_origin);
}
void VrShell::SubmitWebVRFrame() {}
void VrShell::UpdateWebVRTextureBounds(int16_t frame_index,
const gvr::Rectf& left_bounds,
const gvr::Rectf& right_bounds) {
PostToGlThreadWhenReady(base::Bind(&VrShellGl::UpdateWebVRTextureBounds,
gl_thread_->GetVrShellGl(), frame_index,
left_bounds, right_bounds));
}
bool VrShell::SupportsPresentation() {
return true;
}
void VrShell::ResetPose() {
gl_thread_->task_runner()->PostTask(
FROM_HERE, base::Bind(&VrShellGl::ResetPose, gl_thread_->GetVrShellGl()));
}
void VrShell::CreateVRDisplayInfo(
const base::Callback<void(device::mojom::VRDisplayInfoPtr)>& callback,
uint32_t device_id) {
PostToGlThreadWhenReady(base::Bind(&VrShellGl::CreateVRDisplayInfo,
gl_thread_->GetVrShellGl(),
callback, device_id));
}
base::android::ScopedJavaGlobalRef<jobject> VrShell::TakeContentSurface(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
content_compositor_->SurfaceChanged(nullptr);
base::android::ScopedJavaGlobalRef<jobject> surface(env, content_surface_);
content_surface_ = nullptr;
return surface;
}
void VrShell::RestoreContentSurface(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
PostToGlThreadWhenReady(
base::Bind(&VrShellGl::CreateContentSurface, gl_thread_->GetVrShellGl()));
}
void VrShell::UiSurfaceChanged(jobject surface) {
ui_compositor_->SurfaceChanged(surface);
}
void VrShell::ContentSurfaceChanged(jobject surface) {
content_surface_ = surface;
content_compositor_->SurfaceChanged(surface);
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_contentSurfaceChanged(env, j_vr_shell_.obj());
}
void VrShell::GvrDelegateReady() {
delegate_provider_->SetDelegate(this, gvr_api_);
}
void VrShell::AppButtonPressed() {
if (vr_shell_enabled_)
html_interface_->HandleAppButtonClicked();
}
void VrShell::ContentPhysicalBoundsChanged(JNIEnv* env,
const JavaParamRef<jobject>& object,
jint width, jint height,
jfloat dpr) {
TRACE_EVENT0("gpu", "VrShell::ContentPhysicalBoundsChanged");
PostToGlThreadWhenReady(base::Bind(&VrShellGl::ContentPhysicalBoundsChanged,
gl_thread_->GetVrShellGl(), width,
height));
content_compositor_->SetWindowBounds(gfx::Size(width, height));
}
void VrShell::UIPhysicalBoundsChanged(JNIEnv* env,
const JavaParamRef<jobject>& object,
jint width, jint height, jfloat dpr) {
PostToGlThreadWhenReady(base::Bind(&VrShellGl::UIPhysicalBoundsChanged,
gl_thread_->GetVrShellGl(), width,
height));
ui_compositor_->SetWindowBounds(gfx::Size(width, height));
}
UiInterface* VrShell::GetUiInterface() {
return html_interface_.get();
}
void VrShell::UpdateScene(const base::ListValue* args) {
PostToGlThreadWhenReady(base::Bind(&VrShellGl::UpdateScene,
gl_thread_->GetVrShellGl(),
base::Passed(args->CreateDeepCopy())));
}
void VrShell::DoUiAction(const UiAction action,
const base::DictionaryValue* arguments) {
switch (action) {
case OMNIBOX_CONTENT:
html_interface_->HandleOmniboxInput(*arguments);
return;
case SET_CONTENT_PAUSED: {
bool paused;
CHECK(arguments->GetBoolean("paused", &paused));
SetContentPaused(paused);
return;
}
default:
break;
}
// TODO(mthiesse): Handles these in java through the Tab.
if (!main_contents_)
return;
content::NavigationController& controller = main_contents_->GetController();
switch (action) {
case HISTORY_BACK:
if (main_contents_->IsFullscreen()) {
main_contents_->ExitFullscreen(false);
} else if (controller.CanGoBack()) {
controller.GoBack();
}
break;
case HISTORY_FORWARD:
if (controller.CanGoForward())
controller.GoForward();
break;
case RELOAD:
controller.Reload(content::ReloadType::NORMAL, false);
break;
case LOAD_URL: {
std::string url_string;
CHECK(arguments->GetString("url", &url_string));
GURL url(url_string);
// TODO(crbug.com/683344): Sanitize the URL and prefix, and pass the
// proper transition type down from the UI.
controller.LoadURL(url, content::Referrer(),
ui::PageTransition::PAGE_TRANSITION_AUTO_TOPLEVEL,
std::string(""));
break;
}
#if defined(ENABLE_VR_SHELL_UI_DEV)
case RELOAD_UI:
ui_contents_->GetController().Reload(content::ReloadType::NORMAL, false);
html_interface_.reset(new UiInterface(UiInterface::Mode::STANDARD));
SetUiState();
vr_web_contents_observer_->SetUiInterface(html_interface_.get());
break;
#endif
case ZOOM_OUT: // Not handled yet.
case ZOOM_IN: // Not handled yet.
break;
default:
NOTREACHED();
}
}
void VrShell::RenderViewHostChanged(content::RenderViewHost* old_host,
content::RenderViewHost* new_host) {
new_host->GetWidget()->GetView()->SetBackgroundColor(SK_ColorTRANSPARENT);
}
void VrShell::MainFrameWasResized(bool width_changed) {
display::Display display = display::Screen::GetScreen()
->GetDisplayNearestWindow(ui_contents_->GetNativeView());
PostToGlThreadWhenReady(
base::Bind(&VrShellGl::UIBoundsChanged, gl_thread_->GetVrShellGl(),
display.size().width(), display.size().height()));
}
void VrShell::ContentFrameWasResized(bool width_changed) {
display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(content_window_);
PostToGlThreadWhenReady(
base::Bind(&VrShellGl::ContentBoundsChanged, gl_thread_->GetVrShellGl(),
display.size().width(), display.size().height()));
}
void VrShell::WebContentsDestroyed() {
ui_input_manager_.reset();
ui_contents_ = nullptr;
// TODO(mthiesse): Handle web contents being destroyed.
ForceExitVr();
}
void VrShell::ContentWebContentsDestroyed() {
content_input_manager_.reset();
main_contents_ = nullptr;
// TODO(mthiesse): Handle web contents being destroyed.
ForceExitVr();
}
void VrShell::ContentWasHidden() {
// Ensure we don't continue sending input to it.
content_input_manager_ = nullptr;
}
void VrShell::ContentWasShown() {
if (main_contents_)
content_input_manager_ = base::MakeUnique<VrInputManager>(main_contents_);
}
void VrShell::ForceExitVr() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_forceExitVr(env, j_vr_shell_.obj());
}
void VrShell::OnVRVsyncProviderRequest(
device::mojom::VRVSyncProviderRequest request) {
PostToGlThreadWhenReady(base::Bind(&VrShellGl::OnRequest,
gl_thread_->GetVrShellGl(),
base::Passed(&request)));
}
void VrShell::UpdateVSyncInterval(int64_t timebase_nanos,
double interval_seconds) {
PostToGlThreadWhenReady(
base::Bind(&VrShellGl::UpdateVSyncInterval,
gl_thread_->GetVrShellGl(), timebase_nanos, interval_seconds));
}
void VrShell::SetContentCssSize(float width, float height, float dpr) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_setContentCssSize(env, j_vr_shell_.obj(), width, height,
dpr);
}
void VrShell::SetUiCssSize(float width, float height, float dpr) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_setUiCssSize(env, j_vr_shell_.obj(), width, height, dpr);
}
void VrShell::ProcessUIGesture(std::unique_ptr<blink::WebInputEvent> event) {
if (ui_input_manager_) {
ui_input_manager_->ProcessUpdatedGesture(std::move(event));
}
}
void VrShell::ProcessContentGesture(
std::unique_ptr<blink::WebInputEvent> event) {
if (content_input_manager_) {
content_input_manager_->ProcessUpdatedGesture(std::move(event));
}
}
device::mojom::VRPosePtr VrShell::VRPosePtrFromGvrPose(gvr::Mat4f head_mat) {
device::mojom::VRPosePtr pose = device::mojom::VRPose::New();
pose->timestamp = base::Time::Now().ToJsTime();
pose->orientation.emplace(4);
gfx::Transform inv_transform(
head_mat.m[0][0], head_mat.m[0][1], head_mat.m[0][2], head_mat.m[0][3],
head_mat.m[1][0], head_mat.m[1][1], head_mat.m[1][2], head_mat.m[1][3],
head_mat.m[2][0], head_mat.m[2][1], head_mat.m[2][2], head_mat.m[2][3],
head_mat.m[3][0], head_mat.m[3][1], head_mat.m[3][2], head_mat.m[3][3]);
gfx::Transform transform;
if (inv_transform.GetInverse(&transform)) {
gfx::DecomposedTransform decomposed_transform;
gfx::DecomposeTransform(&decomposed_transform, transform);
pose->orientation.value()[0] = decomposed_transform.quaternion[0];
pose->orientation.value()[1] = decomposed_transform.quaternion[1];
pose->orientation.value()[2] = decomposed_transform.quaternion[2];
pose->orientation.value()[3] = decomposed_transform.quaternion[3];
pose->position.emplace(3);
pose->position.value()[0] = decomposed_transform.translate[0];
pose->position.value()[1] = decomposed_transform.translate[1];
pose->position.value()[2] = decomposed_transform.translate[2];
}
return pose;
}
device::mojom::VRDisplayInfoPtr VrShell::CreateVRDisplayInfo(
gvr::GvrApi* gvr_api, gvr::Sizei compositor_size, uint32_t device_id) {
TRACE_EVENT0("input", "GvrDevice::GetVRDevice");
device::mojom::VRDisplayInfoPtr device = device::mojom::VRDisplayInfo::New();
device->index = device_id;
device->capabilities = device::mojom::VRDisplayCapabilities::New();
device->capabilities->hasOrientation = true;
device->capabilities->hasPosition = false;
device->capabilities->hasExternalDisplay = false;
device->capabilities->canPresent = true;
std::string vendor = gvr_api->GetViewerVendor();
std::string model = gvr_api->GetViewerModel();
device->displayName = vendor + " " + model;
gvr::BufferViewportList gvr_buffer_viewports =
gvr_api->CreateEmptyBufferViewportList();
gvr_buffer_viewports.SetToRecommendedBufferViewports();
device->leftEye = device::mojom::VREyeParameters::New();
device->rightEye = device::mojom::VREyeParameters::New();
for (auto eye : {GVR_LEFT_EYE, GVR_RIGHT_EYE}) {
device::mojom::VREyeParametersPtr& eye_params =
(eye == GVR_LEFT_EYE) ? device->leftEye : device->rightEye;
eye_params->fieldOfView = device::mojom::VRFieldOfView::New();
eye_params->offset.resize(3);
eye_params->renderWidth = compositor_size.width / 2;
eye_params->renderHeight = compositor_size.height;
gvr::BufferViewport eye_viewport = gvr_api->CreateBufferViewport();
gvr_buffer_viewports.GetBufferViewport(eye, &eye_viewport);
gvr::Rectf eye_fov = eye_viewport.GetSourceFov();
eye_params->fieldOfView->upDegrees = eye_fov.top;
eye_params->fieldOfView->downDegrees = eye_fov.bottom;
eye_params->fieldOfView->leftDegrees = eye_fov.left;
eye_params->fieldOfView->rightDegrees = eye_fov.right;
gvr::Mat4f eye_mat = gvr_api->GetEyeFromHeadMatrix(eye);
eye_params->offset[0] = -eye_mat.m[0][3];
eye_params->offset[1] = -eye_mat.m[1][3];
eye_params->offset[2] = -eye_mat.m[2][3];
}
return device;
}
// ----------------------------------------------------------------------------
// Native JNI methods
// ----------------------------------------------------------------------------
jlong Init(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& ui_web_contents,
jlong content_window_android,
jlong ui_window_android,
jboolean for_web_vr,
const base::android::JavaParamRef<jobject>& delegate,
jlong gvr_api,
jboolean reprojected_rendering) {
return reinterpret_cast<intptr_t>(new VrShell(
env, obj, reinterpret_cast<ui::WindowAndroid*>(content_window_android),
content::WebContents::FromJavaWebContents(ui_web_contents),
reinterpret_cast<ui::WindowAndroid*>(ui_window_android), for_web_vr,
VrShellDelegate::GetNativeVrShellDelegate(env, delegate),
reinterpret_cast<gvr_context*>(gvr_api), reprojected_rendering));
}
} // namespace vr_shell