// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/webxr/android/openxr_platform_helper_android.h"
#include <vector>
#include "base/android/jni_android.h"
#include "components/webxr/android/webxr_utils.h"
#include "components/webxr/android/xr_session_coordinator.h"
#include "content/public/browser/web_contents.h"
#include "device/vr/openxr/android/openxr_graphics_binding_open_gles.h"
#include "device/vr/openxr/openxr_platform.h"
#include "device/vr/public/cpp/features.h"
#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
namespace webxr {
namespace {
static PFN_xrInitializeLoaderKHR g_initialize_loader_fn_ = nullptr;
} // anonymous namespace
OpenXrPlatformHelperAndroid::OpenXrPlatformHelperAndroid()
: session_coordinator_(std::make_unique<XrSessionCoordinator>()) {}
OpenXrPlatformHelperAndroid::~OpenXrPlatformHelperAndroid() = default;
std::unique_ptr<device::OpenXrGraphicsBinding>
OpenXrPlatformHelperAndroid::GetGraphicsBinding() {
return std::make_unique<device::OpenXrGraphicsBindingOpenGLES>();
}
void OpenXrPlatformHelperAndroid::GetPlatformCreateInfo(
const device::OpenXrCreateInfo& create_info,
PlatformCreateInfoReadyCallback result_callback,
PlatormInitiatedShutdownCallback shutdown_callback) {
auto activity_ready_callback =
base::BindOnce(&OpenXrPlatformHelperAndroid::OnXrActivityReady,
base::Unretained(this), std::move(result_callback));
session_coordinator_->RequestXrSession(std::move(activity_ready_callback),
std::move(shutdown_callback));
}
void OpenXrPlatformHelperAndroid::OnXrActivityReady(
PlatformCreateInfoReadyCallback callback,
const base::android::JavaParamRef<jobject>& activity) {
activity_ = activity;
create_info_.next = nullptr;
create_info_.applicationVM = base::android::GetVM();
create_info_.applicationActivity = activity_.obj();
std::move(callback).Run(&create_info_);
}
bool OpenXrPlatformHelperAndroid::Initialize() {
XrResult result = XR_SUCCESS;
if (g_initialize_loader_fn_ == nullptr) {
result =
xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR",
(PFN_xrVoidFunction*)(&g_initialize_loader_fn_));
if (XR_FAILED(result)) {
LOG(ERROR) << __func__ << " Could not get loader initialization method";
return false;
}
}
app_context_ = XrSessionCoordinator::GetApplicationContext();
XrLoaderInitInfoAndroidKHR loader_init_info;
loader_init_info.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
loader_init_info.next = nullptr;
loader_init_info.applicationVM = base::android::GetVM();
loader_init_info.applicationContext = app_context_.obj();
result = g_initialize_loader_fn_(
(const XrLoaderInitInfoBaseHeaderKHR*)&loader_init_info);
if (XR_FAILED(result)) {
LOG(ERROR) << "Initialize Loader failed with: " << result;
return false;
}
return true;
}
bool OpenXrPlatformHelperAndroid::CheckHardwareSupport(
content::WebContents* web_contents) {
if (!device::features::IsOpenXrArEnabled()) {
return true;
}
XrInstance instance = XR_NULL_HANDLE;
if (!XR_SUCCEEDED(CreateTemporaryInstance(&instance, web_contents))) {
return false;
}
is_ar_blend_mode_supported_ = IsArBlendModeSupported(instance);
// Ensure that we destroy the temporary instance we created.
return XR_SUCCEEDED(DestroyInstance(instance));
}
device::mojom::XRDeviceData OpenXrPlatformHelperAndroid::GetXRDeviceData() {
device::mojom::XRDeviceData device_data;
device_data.is_ar_blend_mode_supported = is_ar_blend_mode_supported_;
return device_data;
}
XrResult OpenXrPlatformHelperAndroid::CreateTemporaryInstance(
XrInstance* instance,
content::WebContents* web_contents) {
if (!web_contents) {
return XR_ERROR_VALIDATION_FAILURE;
}
activity_ =
XrSessionCoordinator::GetActivity(web_contents->GetJavaWebContents());
XrInstanceCreateInfoAndroidKHR create_info{
XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
create_info.next = nullptr;
create_info.applicationVM = base::android::GetVM();
create_info.applicationActivity = activity_.obj();
return CreateInstance(instance, &create_info);
}
void OpenXrPlatformHelperAndroid::OnInstanceCreateFailure() {
// Note that this may be called in the normal case of failing to create a
// "temporary" instance that we were using solely to check support, and so
// StartXrSession may not have been called yet; however, this method just
// forwards the call to the corresponding Java class who appropriately no-ops
// if there is no active session.
session_coordinator_->EndSession();
}
XrResult OpenXrPlatformHelperAndroid::DestroyInstance(XrInstance& instance) {
session_coordinator_->EndSession();
// The `EndSession` call above can cause us to get called re-entrantly. The
// base class `DestroyInstance` takes `instance` by reference and expects that
// it is not null. In the case of a re-entrant call occurring, that call could
// go through and null out the instance before our call to the base
// `DestroyInstance` here, so verify that the `instance` is still valid before
// attempting to destroy it.
XrResult result = XR_SUCCESS;
if (instance != XR_NULL_HANDLE) {
result = OpenXrPlatformHelper::DestroyInstance(instance);
}
// Since we can't validate that we were only ever called with a valid
// instance, we want to assert that the cached member is cleared as the result
// of at least *one* successful call to the base `DestroyInstance`.
if (XR_SUCCEEDED(result)) {
CHECK(xr_instance_ == XR_NULL_HANDLE);
}
activity_ = nullptr;
return result;
}
} // namespace webxr