// Copyright 2018 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/xr_session_coordinator.h"
#include <memory>
#include <utility>
#include "base/android/jni_string.h"
#include "components/webxr/android/webxr_utils.h"
#include "device/vr/android/compositor_delegate_provider.h"
#include "device/vr/buildflags/buildflags.h"
#include "gpu/ipc/common/gpu_surface_tracker.h"
#include "ui/android/window_android.h"
#include "ui/gl/android/scoped_a_native_window.h"
#include "ui/gl/android/scoped_java_surface.h"
#if BUILDFLAG(ENABLE_ARCORE)
#include "base/android/bundle_utils.h"
#include "device/vr/android/arcore/arcore_shim.h"
#endif
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/webxr/android/xr_jni_headers/XrSessionCoordinator_jni.h"
using base::android::AttachCurrentThread;
using base::android::ScopedJavaLocalRef;
namespace webxr {
XrSessionCoordinator::XrSessionCoordinator() {
JNIEnv* env = AttachCurrentThread();
if (!env) {
return;
}
ScopedJavaLocalRef<jobject> j_xr_session_coordinator =
Java_XrSessionCoordinator_create(env, (jlong)this);
if (j_xr_session_coordinator.is_null()) {
return;
}
j_xr_session_coordinator_.Reset(j_xr_session_coordinator);
}
XrSessionCoordinator::~XrSessionCoordinator() {
JNIEnv* env = AttachCurrentThread();
Java_XrSessionCoordinator_onNativeDestroy(env, j_xr_session_coordinator_);
}
void XrSessionCoordinator::RequestArSession(
int render_process_id,
int render_frame_id,
bool use_overlay,
bool can_render_dom_content,
const device::CompositorDelegateProvider& compositor_delegate_provider,
device::SurfaceReadyCallback ready_callback,
device::SurfaceTouchCallback touch_callback,
device::JavaShutdownCallback destroyed_callback) {
DVLOG(1) << __func__;
JNIEnv* env = AttachCurrentThread();
surface_ready_callback_ = std::move(ready_callback);
surface_touch_callback_ = std::move(touch_callback);
java_shutdown_callback_ = std::move(destroyed_callback);
Java_XrSessionCoordinator_startArSession(
env, j_xr_session_coordinator_,
compositor_delegate_provider.GetJavaObject(),
webxr::GetJavaWebContents(render_process_id, render_frame_id),
use_overlay, can_render_dom_content);
}
void XrSessionCoordinator::RequestVrSession(
int render_process_id,
int render_frame_id,
const device::CompositorDelegateProvider& compositor_delegate_provider,
device::SurfaceReadyCallback ready_callback,
device::SurfaceTouchCallback touch_callback,
device::JavaShutdownCallback destroyed_callback,
device::XrSessionButtonTouchedCallback button_touched_callback) {
DVLOG(1) << __func__;
JNIEnv* env = AttachCurrentThread();
surface_ready_callback_ = std::move(ready_callback);
surface_touch_callback_ = std::move(touch_callback);
java_shutdown_callback_ = std::move(destroyed_callback);
xr_button_touched_callback_ = std::move(button_touched_callback);
Java_XrSessionCoordinator_startVrSession(
env, j_xr_session_coordinator_,
compositor_delegate_provider.GetJavaObject(),
webxr::GetJavaWebContents(render_process_id, render_frame_id));
}
void XrSessionCoordinator::RequestXrSession(
ActivityReadyCallback ready_callback,
device::JavaShutdownCallback shutdown_callback) {
DVLOG(1) << __func__;
JNIEnv* env = AttachCurrentThread();
activity_ready_callback_ = std::move(ready_callback);
java_shutdown_callback_ = std::move(shutdown_callback);
Java_XrSessionCoordinator_startXrSession(env, j_xr_session_coordinator_);
}
void XrSessionCoordinator::EndSession() {
DVLOG(1) << __func__;
JNIEnv* env = AttachCurrentThread();
Java_XrSessionCoordinator_endSession(env, j_xr_session_coordinator_);
}
void XrSessionCoordinator::OnDrawingSurfaceReady(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jobject>& surface,
const base::android::JavaParamRef<jobject>& java_root_window,
int rotation,
int width,
int height) {
DVLOG(1) << __func__ << ": width=" << width << " height=" << height
<< " rotation=" << rotation;
gl::ScopedJavaSurface scoped_surface(surface, /*auto_release=*/false);
gl::ScopedANativeWindow window(scoped_surface);
gpu::SurfaceHandle surface_handle =
gpu::GpuSurfaceTracker::Get()->AddSurfaceForNativeWidget(
gpu::GpuSurfaceTracker::SurfaceRecord(
std::move(scoped_surface),
/*can_be_used_with_surface_control=*/false));
ui::WindowAndroid* root_window =
ui::WindowAndroid::FromJavaWindowAndroid(java_root_window);
display::Display::Rotation display_rotation =
static_cast<display::Display::Rotation>(rotation);
surface_ready_callback_.Run(window.a_native_window(), surface_handle,
root_window, display_rotation, {width, height});
}
void XrSessionCoordinator::OnDrawingSurfaceTouch(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
bool primary,
bool touching,
int32_t pointer_id,
float x,
float y) {
DVLOG(3) << __func__ << ": pointer_id=" << pointer_id
<< " primary=" << primary << " touching=" << touching;
surface_touch_callback_.Run(primary, touching, pointer_id, {x, y});
}
void XrSessionCoordinator::OnJavaShutdown(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
DVLOG(1) << __func__ << ":::";
if (java_shutdown_callback_) {
std::move(java_shutdown_callback_).Run();
}
}
void XrSessionCoordinator::OnXrSessionButtonTouched(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
DVLOG(1) << __func__ << ":::";
if (xr_button_touched_callback_) {
std::move(xr_button_touched_callback_).Run();
}
}
void XrSessionCoordinator::OnXrHostActivityReady(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jobject>& activity) {
DVLOG(1) << __func__;
if (activity_ready_callback_) {
std::move(activity_ready_callback_).Run(activity);
}
}
bool XrSessionCoordinator::EnsureARCoreLoaded() {
#if BUILDFLAG(ENABLE_ARCORE)
DCHECK(device::IsArCoreSupported());
// TODO(crbug.com/41414239): Allow loading the ARCore shim by name instead of
// by absolute path.
std::string path = base::android::BundleUtils::ResolveLibraryPath(
/*library_name=*/"arcore_sdk_c", /*split_name=*/"");
// Crash in debug builds if `path` is empty but handle this situation in
// release builds. This is done by design - the `path` will be empty only if
// there was a regression introduced to our gn/gni files w/o causing a build
// break. In release builds, this approach will result in the site not being
// able to request an AR session. We need to ensure that both loadable_modules
// and secondary_abi_loadable_modules are set correctly when building and that
// we're in the split we expect them to be (currently not passing a split
// name).
if (path.empty()) {
LOG(DFATAL) << "Unable to find path to ARCore SDK library";
return false;
}
return device::LoadArCoreSdk(path);
#else // BUILDFLAG(ENABLE_ARCORE)
return false;
#endif
}
ScopedJavaLocalRef<jobject> XrSessionCoordinator::GetCurrentActivityContext() {
JNIEnv* env = AttachCurrentThread();
return Java_XrSessionCoordinator_getCurrentActivityContext(env);
}
ScopedJavaLocalRef<jobject> XrSessionCoordinator::GetActivityFrom(
int render_process_id,
int render_frame_id) {
return GetActivity(
webxr::GetJavaWebContents(render_process_id, render_frame_id));
}
// static
ScopedJavaLocalRef<jobject> XrSessionCoordinator::GetApplicationContext() {
JNIEnv* env = AttachCurrentThread();
return Java_XrSessionCoordinator_getApplicationContext(env);
}
// static
ScopedJavaLocalRef<jobject> XrSessionCoordinator::GetActivity(
ScopedJavaLocalRef<jobject> web_contents) {
JNIEnv* env = AttachCurrentThread();
return Java_XrSessionCoordinator_getActivity(env, web_contents);
}
} // namespace webxr