blob: 04a583156212c63a4003e16c7627498abdaaa912 [file] [log] [blame]
// Copyright 2018 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/arcore_device/arcore_device.h"
#include "base/bind.h"
#include "base/numerics/math_constants.h"
#include "base/optional.h"
#include "base/task/post_task.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
#include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
#include "chrome/browser/android/vr/arcore_device/arcore_gl_thread.h"
#include "chrome/browser/android/vr/arcore_device/arcore_impl.h"
#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
#include "chrome/browser/android/vr/arcore_device/arcore_java_utils.h"
#include "chrome/browser/android/vr/arcore_device/arcore_permission_helper.h"
#include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
#include "chrome/browser/permissions/permission_manager.h"
#include "chrome/browser/permissions/permission_result.h"
#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
#include "device/vr/vr_display_impl.h"
#include "ui/display/display.h"
using base::android::JavaRef;
namespace {
constexpr float kDegreesPerRadian = 180.0f / base::kPiFloat;
} // namespace
namespace device {
namespace {
mojom::VRDisplayInfoPtr CreateVRDisplayInfo(mojom::XRDeviceId device_id) {
mojom::VRDisplayInfoPtr device = mojom::VRDisplayInfo::New();
device->id = device_id;
device->displayName = "ARCore VR Device";
device->capabilities = mojom::VRDisplayCapabilities::New();
device->capabilities->hasPosition = true;
device->capabilities->hasExternalDisplay = false;
device->capabilities->canPresent = false;
device->capabilities->can_provide_pass_through_images = true;
device->leftEye = mojom::VREyeParameters::New();
device->rightEye = nullptr;
mojom::VREyeParametersPtr& left_eye = device->leftEye;
left_eye->fieldOfView = mojom::VRFieldOfView::New();
// TODO(lincolnfrog): get these values for real (see gvr device).
uint width = 1080;
uint height = 1795;
double fov_x = 1437.387;
double fov_y = 1438.074;
// TODO(lincolnfrog): get real camera intrinsics.
float horizontal_degrees = atan(width / (2.0 * fov_x)) * kDegreesPerRadian;
float vertical_degrees = atan(height / (2.0 * fov_y)) * kDegreesPerRadian;
left_eye->fieldOfView->leftDegrees = horizontal_degrees;
left_eye->fieldOfView->rightDegrees = horizontal_degrees;
left_eye->fieldOfView->upDegrees = vertical_degrees;
left_eye->fieldOfView->downDegrees = vertical_degrees;
left_eye->offset = {0.0f, 0.0f, 0.0f};
left_eye->renderWidth = width;
left_eye->renderHeight = height;
return device;
}
} // namespace
ArCoreDevice::ArCoreDevice(
std::unique_ptr<ArCoreFactory> arcore_factory,
std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_to_surface_bridge,
std::unique_ptr<vr::ArCoreInstallUtils> arcore_install_utils,
std::unique_ptr<ArCorePermissionHelper> arcore_permission_helper)
: VRDeviceBase(mojom::XRDeviceId::ARCORE_DEVICE_ID),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
arcore_factory_(std::move(arcore_factory)),
ar_image_transport_factory_(std::move(ar_image_transport_factory)),
mailbox_bridge_(std::move(mailbox_to_surface_bridge)),
arcore_install_utils_(std::move(arcore_install_utils)),
arcore_permission_helper_(std::move(arcore_permission_helper)),
weak_ptr_factory_(this) {
SetVRDisplayInfo(CreateVRDisplayInfo(GetId()));
// TODO(https://crbug.com/836524) clean up usage of mailbox bridge
// and extract the methods in this class that interact with ARCore API
// into a separate class that implements the ArCore interface.
mailbox_bridge_->CreateUnboundContextProvider(
base::BindOnce(&ArCoreDevice::OnMailboxBridgeReady, GetWeakPtr()));
}
ArCoreDevice::ArCoreDevice()
: ArCoreDevice(std::make_unique<ArCoreImplFactory>(),
std::make_unique<ArImageTransportFactory>(),
std::make_unique<vr::MailboxToSurfaceBridge>(),
std::make_unique<vr::ArCoreJavaUtils>(this),
std::make_unique<ArCorePermissionHelper>()) {}
ArCoreDevice::~ArCoreDevice() {}
void ArCoreDevice::PauseTracking() {
DCHECK(IsOnMainThread());
if (is_paused_)
return;
is_paused_ = true;
if (!is_arcore_gl_initialized_)
return;
PostTaskToGlThread(base::BindOnce(
&ArCoreGl::Pause, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr()));
}
void ArCoreDevice::ResumeTracking() {
DCHECK(IsOnMainThread());
if (!is_paused_)
return;
is_paused_ = false;
// TODO(crbug.com/883046): ResumeTracking does not fire after ArCore has been
// updated/installed or the update/installation was cancelled. Thus, we never
// handle queued up session requests.
if (on_request_arcore_install_or_update_result_callback_)
std::move(on_request_arcore_install_or_update_result_callback_)
.Run(!arcore_install_utils_->ShouldRequestInstallSupportedArCore());
if (!is_arcore_gl_initialized_)
return;
PostTaskToGlThread(base::BindOnce(
&ArCoreGl::Resume, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr()));
}
void ArCoreDevice::OnMailboxBridgeReady() {
DCHECK(IsOnMainThread());
DCHECK(!arcore_gl_thread_);
// MailboxToSurfaceBridge's destructor's call to DestroyContext must
// happen on the GL thread, so transferring it to that thread is appropriate.
// TODO(https://crbug.com/836553): use same GL thread as GVR.
arcore_gl_thread_ = std::make_unique<ArCoreGlThread>(
std::move(ar_image_transport_factory_), std::move(mailbox_bridge_),
CreateMainThreadCallback(base::BindOnce(
&ArCoreDevice::OnArCoreGlThreadInitialized, GetWeakPtr())));
arcore_gl_thread_->Start();
}
void ArCoreDevice::OnArCoreGlThreadInitialized() {
DCHECK(IsOnMainThread());
is_arcore_gl_thread_initialized_ = true;
if (pending_request_ar_module_callback_) {
std::move(pending_request_ar_module_callback_).Run();
}
}
void ArCoreDevice::RequestSession(
mojom::XRRuntimeSessionOptionsPtr options,
mojom::XRRuntime::RequestSessionCallback callback) {
DCHECK(IsOnMainThread());
// If we are currently handling another request defer this request. All
// deferred requests will be processed once handling is complete.
deferred_request_session_callbacks_.push_back(std::move(callback));
if (deferred_request_session_callbacks_.size() > 1) {
return;
}
// TODO(https://crbug.com/849568): Instead of splitting the initialization
// of this class between construction and RequestSession, perform all the
// initialization at once on the first successful RequestSession call.
if (!is_arcore_gl_thread_initialized_) {
pending_request_ar_module_callback_ =
base::BindOnce(&ArCoreDevice::RequestArModule, GetWeakPtr(),
options->render_process_id, options->render_frame_id,
options->has_user_activation);
return;
}
RequestArModule(options->render_process_id, options->render_frame_id,
options->has_user_activation);
}
void ArCoreDevice::RequestArModule(int render_process_id,
int render_frame_id,
bool has_user_activation) {
if (arcore_install_utils_->ShouldRequestInstallArModule()) {
on_request_ar_module_result_callback_ =
base::BindOnce(&ArCoreDevice::OnRequestArModuleResult, GetWeakPtr(),
render_process_id, render_frame_id, has_user_activation);
arcore_install_utils_->RequestInstallArModule();
return;
}
OnRequestArModuleResult(render_process_id, render_frame_id,
has_user_activation, true);
}
void ArCoreDevice::OnRequestArModuleResult(int render_process_id,
int render_frame_id,
bool has_user_activation,
bool success) {
if (!success) {
CallDeferredRequestSessionCallbacks(/*success=*/false);
return;
}
RequestArCoreInstallOrUpdate(render_process_id, render_frame_id,
has_user_activation);
}
void ArCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id,
int render_frame_id,
bool has_user_activation) {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
DCHECK(!on_request_arcore_install_or_update_result_callback_);
if (arcore_install_utils_->ShouldRequestInstallSupportedArCore()) {
// ARCore is not installed or requires an update. Store the callback to be
// processed later once installation/update is complete or got cancelled.
on_request_arcore_install_or_update_result_callback_ = base::BindOnce(
&ArCoreDevice::OnRequestArCoreInstallOrUpdateResult, GetWeakPtr(),
render_process_id, render_frame_id, has_user_activation);
arcore_install_utils_->RequestInstallSupportedArCore(render_process_id,
render_frame_id);
return;
}
OnRequestArCoreInstallOrUpdateResult(render_process_id, render_frame_id,
has_user_activation, true);
}
void ArCoreDevice::OnRequestArCoreInstallOrUpdateResult(
int render_process_id,
int render_frame_id,
bool has_user_activation,
bool success) {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
if (!success) {
CallDeferredRequestSessionCallbacks(/*success=*/false);
return;
}
// TODO(https://crbug.com/845792): Consider calling a method to ask for the
// appropriate permissions.
// ARCore sessions require camera permission.
arcore_permission_helper_->RequestCameraPermission(
render_process_id, render_frame_id, has_user_activation,
base::BindOnce(&ArCoreDevice::OnRequestCameraPermissionComplete,
GetWeakPtr()));
}
void ArCoreDevice::OnRequestCameraPermissionComplete(bool success) {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
if (!success) {
CallDeferredRequestSessionCallbacks(/*success=*/false);
return;
}
// By this point ARCore has already been set up, so continue handling request.
RequestArCoreGlInitialization();
}
void ArCoreDevice::OnRequestInstallArModuleResult(bool success) {
DCHECK(IsOnMainThread());
if (on_request_ar_module_result_callback_) {
std::move(on_request_ar_module_result_callback_).Run(success);
}
}
void ArCoreDevice::OnRequestInstallSupportedArCoreCanceled() {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
DCHECK(on_request_arcore_install_or_update_result_callback_);
std::move(on_request_arcore_install_or_update_result_callback_).Run(false);
}
void ArCoreDevice::CallDeferredRequestSessionCallbacks(bool success) {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
DCHECK(!deferred_request_session_callbacks_.empty());
for (auto& deferred_callback : deferred_request_session_callbacks_) {
mojom::XRSessionControllerPtr controller;
mojom::XRSessionPtr session;
if (success) {
mojom::XRFrameDataProviderPtr data_provider;
mojom::XREnvironmentIntegrationProviderPtr environment_provider;
magic_window_sessions_.push_back(std::make_unique<VRDisplayImpl>(
this, mojo::MakeRequest(&data_provider),
mojo::MakeRequest(&environment_provider),
mojo::MakeRequest(&controller)));
session = mojom::XRSession::New();
session->data_provider = data_provider.PassInterface();
session->environment_provider = environment_provider.PassInterface();
session->display_info = display_info_.Clone();
}
// We don't expect this call to alter deferred_request_session_callbacks_.
// The call may request another session, which should be handled right here
// in this loop as well.
std::move(deferred_callback).Run(std::move(session), std::move(controller));
}
deferred_request_session_callbacks_.clear();
}
void ArCoreDevice::PostTaskToGlThread(base::OnceClosure task) {
DCHECK(IsOnMainThread());
arcore_gl_thread_->GetArCoreGl()->GetGlThreadTaskRunner()->PostTask(
FROM_HERE, std::move(task));
}
bool ArCoreDevice::IsOnMainThread() {
return main_thread_task_runner_->BelongsToCurrentThread();
}
void ArCoreDevice::RequestArCoreGlInitialization() {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
if (!arcore_install_utils_->EnsureLoaded()) {
DLOG(ERROR) << "ARCore was not loaded properly.";
OnArCoreGlInitializationComplete(false);
return;
}
if (!is_arcore_gl_initialized_) {
// This won't happen twice because this method is called from the
// end of the permission sequence, which only happens once. We
// set is_arcore_gl_initialized in the callback so we don't
// allow operations that require its readiness to happen.
PostTaskToGlThread(base::BindOnce(
&ArCoreGl::Initialize, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
std::move(arcore_install_utils_), std::move(arcore_factory_),
CreateMainThreadCallback(base::BindOnce(
&ArCoreDevice::OnArCoreGlInitializationComplete, GetWeakPtr()))));
return;
}
OnArCoreGlInitializationComplete(true);
}
void ArCoreDevice::OnArCoreGlInitializationComplete(bool success) {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
if (!success) {
CallDeferredRequestSessionCallbacks(/*success=*/false);
return;
}
is_arcore_gl_initialized_ = true;
if (!is_paused_) {
PostTaskToGlThread(base::BindOnce(
&ArCoreGl::Resume, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr()));
}
CallDeferredRequestSessionCallbacks(/*success=*/true);
}
bool ArCoreDevice::ShouldPauseTrackingWhenFrameDataRestricted() {
return true;
}
void ArCoreDevice::OnMagicWindowFrameDataRequest(
mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsOnMainThread());
// We should not be able to reach this point if we are not initialized.
DCHECK(is_arcore_gl_thread_initialized_);
if (is_paused_) {
DVLOG(3) << "ARCore is paused and cannot fulfill frame data requests.";
std::move(callback).Run(nullptr);
return;
}
// TODO(https://crbug.com/836496) This current implementation does not handle
// multiple sessions well. There should be a better way to handle this than
// taking the max of all sessions.
gfx::Size max_size(0, 0);
display::Display::Rotation rotation;
for (auto& session : magic_window_sessions_) {
max_size.SetToMax(session->sessionFrameSize());
// We have to pick a rotation so just go with the last one.
rotation = session->sessionRotation();
}
if (max_size.IsEmpty()) {
DLOG(ERROR) << "No valid AR frame size provided!";
std::move(callback).Run(nullptr);
return;
}
PostTaskToGlThread(base::BindOnce(
&ArCoreGl::ProduceFrame, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
max_size, rotation, CreateMainThreadCallback(std::move(callback))));
}
void ArCoreDevice::RequestHitTest(
mojom::XRRayPtr ray,
mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback) {
DCHECK(IsOnMainThread());
PostTaskToGlThread(base::BindOnce(
&ArCoreGl::RequestHitTest, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
std::move(ray), CreateMainThreadCallback(std::move(callback))));
}
} // namespace device