blob: 62a20494ef478e457be0c79104c43aab9f5b7816 [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/message_loop/message_loop.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/trace_event/trace_event.h"
#include "base/values.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/android/vr_shell/android_ui_gesture_target.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 "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
#include "chrome/browser/vr/toolbar_helper.h"
#include "chrome/browser/vr/ui_interface.h"
#include "chrome/browser/vr/ui_scene_manager.h"
#include "chrome/browser/vr/vr_tab_helper.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_iterator.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 "content/public/common/service_manager_connection.h"
#include "content/public/common/url_constants.h"
#include "device/geolocation/public/interfaces/geolocation_config.mojom.h"
#include "device/vr/android/gvr/cardboard_gamepad_data_fetcher.h"
#include "device/vr/android/gvr/gvr_gamepad_data_fetcher.h"
#include "device/vr/vr_device.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "jni/VrShellImpl_jni.h"
#include "services/service_manager/public/cpp/connector.h"
#include "third_party/WebKit/public/platform/WebInputEvent.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/android/java_bitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/native_widget_types.h"
#include "url/gurl.h"
using base::android::JavaParamRef;
namespace vr_shell {
namespace {
vr_shell::VrShell* g_instance;
constexpr base::TimeDelta poll_media_access_interval_ =
base::TimeDelta::FromSecondsD(0.2);
constexpr base::TimeDelta kExitVrDueToUnsupportedModeDelay =
base::TimeDelta::FromSeconds(5);
static constexpr float kInchesToMeters = 0.0254f;
// Screen pixel density of the Google Pixel phone in pixels per inch.
static constexpr float kPixelPpi = 441.0f;
// Screen pixel density of the Google Pixel phone in pixels per meter.
static constexpr float kPixelPpm = kPixelPpi / kInchesToMeters;
// Factor to adjust the legibility of the content. Making this factor smaller
// increases the text size.
static constexpr float kContentLegibilityFactor = 1.36f;
// This factor converts the physical width of the projected
// content quad into the required width of the virtual content window.
// TODO(tiborg): This value is calibrated for the Google Pixel. We should adjust
// this value dynamically based on the target device's pixel density in the
// future.
static constexpr float kContentBoundsMetersToWindowSize =
kPixelPpm * kContentLegibilityFactor;
// Factor by which the content's pixel amount is increased beyond what the
// projected content quad covers in screen real estate.
// This DPR factor works well on Pixel phones.
static constexpr float kContentDprFactor = 4.0f;
void SetIsInVR(content::WebContents* contents, bool is_in_vr) {
if (contents && contents->GetRenderWidgetHostView()) {
// TODO(asimjour) Contents should not be aware of VR mode. Instead, we
// should add a flag for disabling specific UI such as the keyboard (see
// VrTabHelper for details).
contents->GetRenderWidgetHostView()->SetIsInVR(is_in_vr);
vr::VrTabHelper* vr_tab_helper = vr::VrTabHelper::FromWebContents(contents);
DCHECK(vr_tab_helper);
vr_tab_helper->SetIsInVr(is_in_vr);
}
}
} // namespace
VrShell::VrShell(JNIEnv* env,
const JavaParamRef<jobject>& obj,
ui::WindowAndroid* window,
bool for_web_vr,
bool web_vr_autopresentation_expected,
bool in_cct,
VrShellDelegate* delegate,
gvr_context* gvr_api,
bool reprojected_rendering,
float display_width_meters,
float display_height_meters,
int display_width_pixels,
int display_height_pixels)
: vr_shell_enabled_(base::FeatureList::IsEnabled(features::kVrShell)),
window_(window),
compositor_(base::MakeUnique<VrCompositor>(window_)),
delegate_provider_(delegate),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
reprojected_rendering_(reprojected_rendering),
display_size_meters_(display_width_meters, display_height_meters),
display_size_pixels_(display_width_pixels, display_height_pixels),
weak_ptr_factory_(this) {
DVLOG(1) << __FUNCTION__ << "=" << this;
DCHECK(g_instance == nullptr);
g_instance = this;
j_vr_shell_.Reset(env, obj);
gl_thread_ = base::MakeUnique<VrGLThread>(
weak_ptr_factory_.GetWeakPtr(), main_thread_task_runner_, gvr_api,
for_web_vr, web_vr_autopresentation_expected, in_cct,
reprojected_rendering_, HasDaydreamSupport(env));
ui_ = gl_thread_.get();
toolbar_ = base::MakeUnique<vr::ToolbarHelper>(ui_, this);
gl_thread_->Start();
if (for_web_vr || web_vr_autopresentation_expected)
UMA_HISTOGRAM_BOOLEAN("VRAutopresentedWebVR", !for_web_vr);
}
void VrShell::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
delete this;
}
void VrShell::SwapContents(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& tab,
const JavaParamRef<jobject>& android_ui_gesture_target) {
DCHECK(tab.obj());
TabAndroid* active_tab =
TabAndroid::GetNativeTab(env, JavaParamRef<jobject>(env, tab));
DCHECK(active_tab);
content::WebContents* contents = active_tab->web_contents();
bool is_native_page = active_tab->IsNativePage();
AndroidUiGestureTarget* target = nullptr;
if (is_native_page) {
DCHECK(!android_ui_gesture_target.is_null());
target = AndroidUiGestureTarget::FromJavaObject(android_ui_gesture_target);
}
if (contents == web_contents_ && target == android_ui_gesture_target_.get()) {
return;
}
SetIsInVR(GetNonNativePageWebContents(), false);
web_contents_ = contents;
web_contents_is_native_page_ = is_native_page;
compositor_->SetLayer(GetNonNativePageWebContents());
SetIsInVR(GetNonNativePageWebContents(), true);
ContentFrameWasResized(false /* unused */);
SetUiState();
vr_web_contents_observer_ = base::MakeUnique<VrWebContentsObserver>(
web_contents_, this, ui_, toolbar_.get());
if (target) {
android_ui_gesture_target_.reset(target);
input_manager_ = nullptr;
metrics_helper_ = nullptr;
return;
}
input_manager_ =
base::MakeUnique<VrInputManager>(GetNonNativePageWebContents());
// TODO(billorr): Make VrMetricsHelper tab-aware and able to track multiple
// tabs. crbug.com/684661
metrics_helper_ =
base::MakeUnique<VrMetricsHelper>(GetNonNativePageWebContents());
metrics_helper_->SetVRActive(true);
metrics_helper_->SetWebVREnabled(webvr_mode_);
}
void VrShell::SetUiState() {
toolbar_->Update();
if (!GetNonNativePageWebContents()) {
ui_->SetLoading(false);
ui_->SetFullscreen(false);
} else {
ui_->SetLoading(GetNonNativePageWebContents()->IsLoading());
ui_->SetFullscreen(GetNonNativePageWebContents()->IsFullscreen());
}
if (web_contents_) {
ui_->SetIncognito(web_contents_->GetBrowserContext()->IsOffTheRecord());
} else {
ui_->SetIncognito(false);
}
}
VrShell::~VrShell() {
DVLOG(1) << __FUNCTION__ << "=" << this;
poll_capturing_media_task_.Cancel();
if (gvr_gamepad_source_active_) {
device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory(
device::GAMEPAD_SOURCE_GVR);
}
if (cardboard_gamepad_source_active_) {
device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory(
device::GAMEPAD_SOURCE_CARDBOARD);
}
delegate_provider_->RemoveDelegate();
{
// 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();
}
g_instance = nullptr;
}
void VrShell::PostToGlThread(const tracked_objects::Location& from_here,
const base::Closure& task) {
gl_thread_->message_loop()->task_runner()->PostTask(from_here, task);
}
void VrShell::OnContentPaused(bool paused) {
if (!vr_shell_enabled_)
return;
device::VRDevice* device = delegate_provider_->GetDevice();
if (!device)
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)
device->OnBlur();
else
device->OnFocus();
}
void VrShell::NavigateBack() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_navigateBack(env, j_vr_shell_);
}
void VrShell::ExitCct() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_exitCct(env, j_vr_shell_);
}
void VrShell::ToggleCardboardGamepad(bool enabled) {
// Enable/disable updating gamepad state.
if (cardboard_gamepad_source_active_ && !enabled) {
device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory(
device::GAMEPAD_SOURCE_CARDBOARD);
cardboard_gamepad_data_fetcher_ = nullptr;
cardboard_gamepad_source_active_ = false;
}
if (!cardboard_gamepad_source_active_ && enabled) {
device::VRDevice* device = delegate_provider_->GetDevice();
if (!device)
return;
device::GamepadDataFetcherManager::GetInstance()->AddFactory(
new device::CardboardGamepadDataFetcher::Factory(this, device->id()));
cardboard_gamepad_source_active_ = true;
if (pending_cardboard_trigger_) {
OnTriggerEvent(nullptr, JavaParamRef<jobject>(nullptr), true);
}
pending_cardboard_trigger_ = false;
}
}
void VrShell::ToggleGvrGamepad(bool enabled) {
// Enable/disable updating gamepad state.
if (enabled) {
DCHECK(!gvr_gamepad_source_active_);
device::VRDevice* device = delegate_provider_->GetDevice();
if (!device)
return;
device::GamepadDataFetcherManager::GetInstance()->AddFactory(
new device::GvrGamepadDataFetcher::Factory(this, device->id()));
gvr_gamepad_source_active_ = true;
} else {
DCHECK(gvr_gamepad_source_active_);
device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory(
device::GAMEPAD_SOURCE_GVR);
gvr_gamepad_data_fetcher_ = nullptr;
gvr_gamepad_source_active_ = false;
}
}
void VrShell::OnTriggerEvent(JNIEnv* env,
const JavaParamRef<jobject>& obj,
bool touched) {
// If we are running cardboard, update gamepad state.
if (cardboard_gamepad_source_active_) {
device::CardboardGamepadData pad;
pad.timestamp = cardboard_gamepad_timer_++;
pad.is_screen_touching = touched;
if (cardboard_gamepad_data_fetcher_) {
cardboard_gamepad_data_fetcher_->SetGamepadData(pad);
}
} else {
pending_cardboard_trigger_ = touched;
}
}
void VrShell::OnPause(JNIEnv* env, const JavaParamRef<jobject>& obj) {
PostToGlThread(FROM_HERE,
base::Bind(&VrShellGl::OnPause, gl_thread_->GetVrShellGl()));
// exit vr session
if (metrics_helper_)
metrics_helper_->SetVRActive(false);
SetIsInVR(GetNonNativePageWebContents(), false);
poll_capturing_media_task_.Cancel();
}
void VrShell::OnResume(JNIEnv* env, const JavaParamRef<jobject>& obj) {
PostToGlThread(FROM_HERE,
base::Bind(&VrShellGl::OnResume, gl_thread_->GetVrShellGl()));
if (metrics_helper_)
metrics_helper_->SetVRActive(true);
SetIsInVR(GetNonNativePageWebContents(), true);
PollMediaAccessFlag();
}
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);
PostToGlThread(FROM_HERE, base::Bind(&VrShellGl::InitializeGl,
gl_thread_->GetVrShellGl(),
base::Unretained(window)));
}
void VrShell::SetWebVrMode(JNIEnv* env,
const JavaParamRef<jobject>& obj,
bool enabled,
bool show_toast) {
webvr_mode_ = enabled;
if (metrics_helper_)
metrics_helper_->SetWebVREnabled(enabled);
PostToGlThread(FROM_HERE, base::Bind(&VrShellGl::SetWebVrMode,
gl_thread_->GetVrShellGl(), enabled));
ui_->SetWebVrMode(enabled, show_toast);
}
void VrShell::OnFullscreenChanged(bool enabled) {
ui_->SetFullscreen(enabled);
}
bool VrShell::GetWebVrMode(JNIEnv* env, const JavaParamRef<jobject>& obj) {
return webvr_mode_;
}
bool VrShell::IsDisplayingUrlForTesting(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
return ShouldDisplayURL();
}
void VrShell::OnLoadProgressChanged(JNIEnv* env,
const JavaParamRef<jobject>& obj,
double progress) {
ui_->SetLoadProgress(progress);
}
void VrShell::OnTabListCreated(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jobjectArray tabs,
jobjectArray incognito_tabs) {
ProcessTabArray(env, tabs, false);
ProcessTabArray(env, incognito_tabs, true);
ui_->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));
ui_->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);
ui_->UpdateTab(incognito, id, title);
}
void VrShell::OnTabRemoved(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jboolean incognito,
jint id) {
ui_->RemoveTab(incognito, id);
}
void VrShell::SetWebVRSecureOrigin(bool secure_origin) {
ui_->SetWebVrSecureOrigin(secure_origin);
}
void VrShell::CreateVRDisplayInfo(
const base::Callback<void(device::mojom::VRDisplayInfoPtr)>& callback,
uint32_t device_id) {
PostToGlThread(FROM_HERE,
base::Bind(&VrShellGl::CreateVRDisplayInfo,
gl_thread_->GetVrShellGl(), callback, device_id));
}
void VrShell::ConnectPresentingService(
device::mojom::VRSubmitFrameClientPtr submit_client,
device::mojom::VRPresentationProviderRequest request) {
PostToGlThread(FROM_HERE,
base::Bind(&VrShellGl::ConnectPresentingService,
gl_thread_->GetVrShellGl(),
base::Passed(submit_client.PassInterface()),
base::Passed(&request)));
}
base::android::ScopedJavaGlobalRef<jobject> VrShell::TakeContentSurface(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
if (!content_surface_) {
return base::android::ScopedJavaGlobalRef<jobject>(env, nullptr);
}
taken_surface_ = true;
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) {
// Don't try to restore the surface if we haven't successfully taken it yet.
if (!taken_surface_)
return;
taken_surface_ = false;
PostToGlThread(FROM_HERE, base::Bind(&VrShellGl::CreateContentSurface,
gl_thread_->GetVrShellGl()));
}
void VrShell::SetHistoryButtonsEnabled(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jboolean can_go_back,
jboolean can_go_forward) {
ui_->SetHistoryButtonsEnabled(can_go_back, can_go_forward);
}
void VrShell::RequestToExitVr(JNIEnv* env,
const JavaParamRef<jobject>& obj,
int reason) {
ui_->SetExitVrPromptEnabled(true, (vr::UiUnsupportedMode)reason);
}
void VrShell::ContentSurfaceChanged(jobject surface) {
content_surface_ = surface;
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_contentSurfaceChanged(env, j_vr_shell_);
compositor_->SurfaceChanged(content_surface_);
}
void VrShell::GvrDelegateReady(gvr::ViewerType viewer_type) {
delegate_provider_->SetDelegate(this, viewer_type);
}
void VrShell::OnPhysicalBackingSizeChanged(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& jweb_contents,
jint width,
jint height) {
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(jweb_contents);
gfx::Size size(width, height);
web_contents->GetNativeView()->OnPhysicalBackingSizeChanged(size);
}
void VrShell::ContentPhysicalBoundsChanged(JNIEnv* env,
const JavaParamRef<jobject>& object,
jint width,
jint height,
jfloat dpr) {
TRACE_EVENT0("gpu", "VrShell::ContentPhysicalBoundsChanged");
// TODO(acondor): Set the device scale factor for font rendering on the
// VR Shell textures.
PostToGlThread(FROM_HERE,
base::Bind(&VrShellGl::ContentPhysicalBoundsChanged,
gl_thread_->GetVrShellGl(), width, height));
compositor_->SetWindowBounds(gfx::Size(width, height));
}
// Note that the following code is obsolete and is here as reference for the
// actions that need to be implemented natively.
void VrShell::DoUiAction(const UiAction action,
const base::DictionaryValue* arguments) {
// Actions that can be handled natively.
switch (action) {
default:
break;
}
// Actions that are handled in java.
JNIEnv* env = base::android::AttachCurrentThread();
switch (action) {
case SHOW_TAB: {
int id;
CHECK(arguments->GetInteger("id", &id));
Java_VrShellImpl_showTab(env, j_vr_shell_, id);
return;
}
case OPEN_NEW_TAB: {
bool incognito;
CHECK(arguments->GetBoolean("incognito", &incognito));
Java_VrShellImpl_openNewTab(env, j_vr_shell_, incognito);
return;
}
case HISTORY_FORWARD:
Java_VrShellImpl_navigateForward(env, j_vr_shell_);
break;
case RELOAD:
Java_VrShellImpl_reload(env, j_vr_shell_);
break;
default:
NOTREACHED();
}
}
void VrShell::ContentFrameWasResized(bool width_changed) {
display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
PostToGlThread(
FROM_HERE,
base::Bind(&VrShellGl::ContentBoundsChanged, gl_thread_->GetVrShellGl(),
display.size().width(), display.size().height()));
}
void VrShell::ContentWebContentsDestroyed() {
input_manager_.reset();
web_contents_ = nullptr;
// TODO(mthiesse): Handle web contents being destroyed.
ForceExitVr();
}
void VrShell::ContentWasHidden() {
// Ensure we don't continue sending input to it.
input_manager_ = nullptr;
}
void VrShell::ContentWasShown() {
if (GetNonNativePageWebContents()) {
input_manager_ =
base::MakeUnique<VrInputManager>(GetNonNativePageWebContents());
}
}
void VrShell::ForceExitVr() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_forceExitVr(env, j_vr_shell_);
}
void VrShell::ExitPresent() {
delegate_provider_->ExitWebVRPresent();
}
void VrShell::ExitFullscreen() {
if (GetNonNativePageWebContents() &&
GetNonNativePageWebContents()->IsFullscreen()) {
GetNonNativePageWebContents()->ExitFullscreen(false);
}
}
void VrShell::LogUnsupportedModeUserMetric(JNIEnv* env,
const JavaParamRef<jobject>& obj,
int mode) {
LogUnsupportedModeUserMetric((vr::UiUnsupportedMode)mode);
}
void VrShell::LogUnsupportedModeUserMetric(vr::UiUnsupportedMode mode) {
UMA_HISTOGRAM_ENUMERATION("VR.Shell.EncounteredUnsupportedMode", mode,
vr::UiUnsupportedMode::kCount);
}
void VrShell::ExitVrDueToUnsupportedMode(vr::UiUnsupportedMode mode) {
ui_->SetIsExiting();
PostToGlThread(FROM_HERE, base::Bind(&VrShellGl::set_is_exiting,
gl_thread_->GetVrShellGl(), true));
main_thread_task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&VrShell::ForceExitVr, weak_ptr_factory_.GetWeakPtr()),
kExitVrDueToUnsupportedModeDelay);
LogUnsupportedModeUserMetric(mode);
}
content::WebContents* VrShell::GetNonNativePageWebContents() const {
return !web_contents_is_native_page_ ? web_contents_ : nullptr;
}
void VrShell::OnUnsupportedMode(vr::UiUnsupportedMode mode) {
switch (mode) {
case vr::UiUnsupportedMode::kUnhandledPageInfo: {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_onUnhandledPageInfo(env, j_vr_shell_);
break;
}
default:
ExitVrDueToUnsupportedMode(mode);
break;
}
}
void VrShell::OnExitVrPromptResult(vr::UiUnsupportedMode reason,
vr::ExitVrPromptChoice choice) {
bool should_exit;
switch (choice) {
case vr::ExitVrPromptChoice::CHOICE_NONE:
case vr::ExitVrPromptChoice::CHOICE_STAY:
ui_->SetExitVrPromptEnabled(false, vr::UiUnsupportedMode::kCount);
should_exit = false;
break;
case vr::ExitVrPromptChoice::CHOICE_EXIT:
should_exit = true;
break;
}
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_onExitVrRequestResult(env, j_vr_shell_,
static_cast<int>(reason), should_exit);
}
void VrShell::OnContentScreenBoundsChanged(const gfx::SizeF& bounds) {
// We have to fit the content into the portion of the display for one eye.
// Thus, take only half the width.
int width_pixels = display_size_pixels_.width() / 2;
float width_meters = display_size_meters_.width() / 2;
// The physical and pixel dimensions to draw the scene for one eye needs to be
// a square so that the content's aspect ratio is preserved (i.e. make pixels
// square for the calculations).
// For the resolution err on the side of too many pixels so that our content
// is rather drawn with too high of a resolution than too low.
int length_pixels = std::max(width_pixels, display_size_pixels_.height());
// For the size err on the side of a too small area so that the font size is
// rather too big than too small.
float length_meters = std::min(width_meters, display_size_meters_.height());
// Calculate the virtual window size and DPR and pass this to VrShellImpl.
gfx::Size window_size = gfx::ToRoundedSize(
gfx::ScaleSize(bounds, width_meters * kContentBoundsMetersToWindowSize));
// Need to use sqrt(kContentDprFactor) to translate from a factor applicable
// to the area to a factor applicable to one side length.
float dpr =
(length_pixels / (length_meters * kContentBoundsMetersToWindowSize)) *
std::sqrt(kContentDprFactor);
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_setContentCssSize(env, j_vr_shell_, window_size.width(),
window_size.height(), dpr);
}
void VrShell::PollMediaAccessFlag() {
poll_capturing_media_task_.Cancel();
poll_capturing_media_task_.Reset(
base::Bind(&VrShell::PollMediaAccessFlag, base::Unretained(this)));
main_thread_task_runner_->PostDelayedTask(
FROM_HERE, poll_capturing_media_task_.callback(),
poll_media_access_interval_);
int num_tabs_capturing_audio = 0;
int num_tabs_capturing_video = 0;
int num_tabs_capturing_screen = 0;
int num_tabs_bluetooth_connected = 0;
scoped_refptr<MediaStreamCaptureIndicator> indicator =
MediaCaptureDevicesDispatcher::GetInstance()
->GetMediaStreamCaptureIndicator();
std::unique_ptr<content::RenderWidgetHostIterator> widgets(
content::RenderWidgetHost::GetRenderWidgetHosts());
while (content::RenderWidgetHost* rwh = widgets->GetNextHost()) {
content::RenderViewHost* rvh = content::RenderViewHost::From(rwh);
if (!rvh)
continue;
content::WebContents* web_contents =
content::WebContents::FromRenderViewHost(rvh);
if (!web_contents)
continue;
if (web_contents->GetRenderViewHost() != rvh)
continue;
// Because a WebContents can only have one current RVH at a time, there will
// be no duplicate WebContents here.
if (indicator->IsCapturingAudio(web_contents))
num_tabs_capturing_audio++;
if (indicator->IsCapturingVideo(web_contents))
num_tabs_capturing_video++;
if (indicator->IsBeingMirrored(web_contents))
num_tabs_capturing_screen++;
if (web_contents->IsConnectedToBluetoothDevice())
num_tabs_bluetooth_connected++;
}
auto* connector =
content::ServiceManagerConnection::GetForProcess()->GetConnector();
connector->BindInterface("content_browser", &geolocation_config_);
geolocation_config_->IsHighAccuracyLocationBeingCaptured(
base::Bind(&VrShell::SetHighAccuracyLocation, base::Unretained(this)));
bool is_capturing_audio = num_tabs_capturing_audio > 0;
bool is_capturing_video = num_tabs_capturing_video > 0;
bool is_capturing_screen = num_tabs_capturing_screen > 0;
bool is_bluetooth_connected = num_tabs_bluetooth_connected > 0;
if (is_capturing_audio != is_capturing_audio_) {
ui_->SetAudioCapturingIndicator(is_capturing_audio);
is_capturing_audio_ = is_capturing_audio;
}
if (is_capturing_video != is_capturing_video_) {
ui_->SetVideoCapturingIndicator(is_capturing_video);
is_capturing_video_ = is_capturing_video;
}
if (is_capturing_screen != is_capturing_screen_) {
ui_->SetScreenCapturingIndicator(is_capturing_screen);
is_capturing_screen_ = is_capturing_screen;
}
if (is_bluetooth_connected != is_bluetooth_connected_) {
ui_->SetBluetoothConnectedIndicator(is_bluetooth_connected);
is_bluetooth_connected_ = is_bluetooth_connected;
}
}
void VrShell::SetHighAccuracyLocation(bool high_accuracy_location) {
if (high_accuracy_location == high_accuracy_location_)
return;
ui_->SetLocationAccessIndicator(high_accuracy_location);
high_accuracy_location_ = high_accuracy_location;
}
void VrShell::SetContentCssSize(float width, float height, float dpr) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_VrShellImpl_setContentCssSize(env, j_vr_shell_, width, height, dpr);
}
void VrShell::ProcessContentGesture(
std::unique_ptr<blink::WebInputEvent> event) {
if (input_manager_) {
input_manager_->ProcessUpdatedGesture(std::move(event));
} else if (android_ui_gesture_target_) {
android_ui_gesture_target_->DispatchWebInputEvent(std::move(event));
}
}
void VrShell::UpdateGamepadData(device::GvrGamepadData pad) {
if (gvr_gamepad_source_active_ != pad.connected)
ToggleGvrGamepad(pad.connected);
if (gvr_gamepad_data_fetcher_)
gvr_gamepad_data_fetcher_->SetGamepadData(pad);
}
void VrShell::RegisterGvrGamepadDataFetcher(
device::GvrGamepadDataFetcher* fetcher) {
DVLOG(1) << __FUNCTION__ << "(" << fetcher << ")";
gvr_gamepad_data_fetcher_ = fetcher;
}
void VrShell::RegisterCardboardGamepadDataFetcher(
device::CardboardGamepadDataFetcher* fetcher) {
DVLOG(1) << __FUNCTION__ << "(" << fetcher << ")";
cardboard_gamepad_data_fetcher_ = fetcher;
}
bool VrShell::HasDaydreamSupport(JNIEnv* env) {
return Java_VrShellImpl_hasDaydreamSupport(env, j_vr_shell_);
}
content::WebContents* VrShell::GetActiveWebContents() const {
// TODO(tiborg): Handle the case when Tab#isShowingErrorPage returns true.
return web_contents_;
}
bool VrShell::ShouldDisplayURL() const {
content::NavigationEntry* entry = GetNavigationEntry();
if (!entry) {
return ChromeToolbarModelDelegate::ShouldDisplayURL();
}
GURL url = entry->GetVirtualURL();
// URL is of the form chrome-native://.... This is not useful for the user.
// Hide it.
if (url.SchemeIs(chrome::kChromeUINativeScheme)) {
return false;
}
// URL is of the form chrome://....
if (url.SchemeIs(content::kChromeUIScheme)) {
return true;
}
return ChromeToolbarModelDelegate::ShouldDisplayURL();
}
// ----------------------------------------------------------------------------
// Native JNI methods
// ----------------------------------------------------------------------------
jlong Init(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& delegate,
jlong window_android,
jboolean for_web_vr,
jboolean web_vr_autopresentation_expected,
jboolean in_cct,
jlong gvr_api,
jboolean reprojected_rendering,
jfloat display_width_meters,
jfloat display_height_meters,
jint display_width_pixels,
jint display_pixel_height) {
return reinterpret_cast<intptr_t>(new VrShell(
env, obj, reinterpret_cast<ui::WindowAndroid*>(window_android),
for_web_vr, web_vr_autopresentation_expected, in_cct,
VrShellDelegate::GetNativeVrShellDelegate(env, delegate),
reinterpret_cast<gvr_context*>(gvr_api), reprojected_rendering,
display_width_meters, display_height_meters, display_width_pixels,
display_pixel_height));
}
} // namespace vr_shell