add JNIClassNames so that FindClass isn't used at runtime (this was a bug introduced by the upstream merge that seems to affect some quest users)

This commit is contained in:
amwatson 2024-04-18 18:40:53 -05:00
parent 84a2725906
commit 289ce5de91
10 changed files with 108 additions and 76 deletions

View file

@ -149,7 +149,7 @@ android {
productFlavors {
create("canary") {
dimension = "version"
applicationIdSuffix = ".canary"
applicationIdSuffix = ".playtest"
}
create("nightly") {

View file

@ -13,6 +13,7 @@
#include "jni/applets/swkbd.h"
#include "jni/camera/still_image_camera.h"
#include "jni/id_cache.h"
#include "vr/utils/JniClassNames.h"
#include <jni.h>
@ -254,6 +255,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
SoftwareKeyboard::InitJNI(env);
Camera::StillImage::InitJNI(env);
AndroidStorage::InitJNI(env, s_native_library_class);
VR::JNI::InitJNI(env);
return JNI_VERSION;
}

View file

@ -425,7 +425,7 @@ void GameSurfaceLayer::SetTopPanelFromController(const XrVector3f& controllerPos
// Set the initial distance of the window from the viewer.
const float sphereRadius =
XrMath::Vector3f::Length(mTopPanel.mPanelFromWorld.position - viewerPosition);
XrVector3f windowPosition =
const XrVector3f windowPosition =
CalculatePanelPosition(viewerPosition, controllerPosition, sphereRadius);
if (windowPosition.z >= -0.5f) { return; }
if (XrMath::Vector3f::LengthSq(windowPosition - mLowerPanel.mPanelFromWorld.position) <

View file

@ -1,5 +1,6 @@
#include "RibbonLayer.h"
#include "../utils/JniClassNames.h"
#include "../utils/LogUtils.h"
#include "../utils/XrMath.h"
@ -38,8 +39,8 @@ XrQuaternionf CalculatePanelRotation(const XrVector3f& windowPosition,
RibbonLayer::RibbonLayer(const XrVector3f&& position, const XrQuaternionf&& orientation,
JNIEnv* jni, jobject activityObject, const XrSession& session)
: UILayer("org/citra/citra_emu/vr/ui/VrRibbonLayer", std::move(position),
std::move(orientation), jni, activityObject, session)
: UILayer(VR::JniGlobalRef::gVrRibbonLayerClass, std::move(position), std::move(orientation),
jni, activityObject, session)
, mInitialPose({orientation, position}) {
mIsMenuBackgroundSelectedMethodId =
jni->GetMethodID(GetVrUILayerClass(), "isMenuBackgroundSelected", "()Z");

View file

@ -159,16 +159,15 @@ XrVector2f GetDensityScaleForSize(const int32_t texWidth, const int32_t texHeigh
} // anonymous namespace
UILayer::UILayer(const std::string& className, const XrVector3f&& position,
UILayer::UILayer(const jclass classObject, const XrVector3f&& position,
const XrQuaternionf&& orientation, JNIEnv* env, jobject activityObject,
const XrSession& session)
: mPanelFromWorld(XrPosef{orientation, position})
, mSession(session)
, mEnv(env) {
const int32_t initializationStatus = Init(className, activityObject, position, session);
const int32_t initializationStatus = Init(classObject, activityObject, position, session);
if (initializationStatus < 0) {
FAIL("Could not initialize UILayer(%s) -- error '%d'", className.c_str(),
initializationStatus);
FAIL("Could not initialize %s() -- error '%d'", __FUNCTION__, initializationStatus);
}
}
@ -215,10 +214,9 @@ bool UILayer::GetRayIntersectionWithPanel(const XrVector3f& start,
}
// Next error code: -8
int32_t UILayer::Init(const std::string& className, const jobject activityObject,
int32_t UILayer::Init(const jclass classObject, const jobject activityObject,
const XrVector3f& position, const XrSession& session) {
mVrUILayerClass = JniUtils::GetGlobalClassReference(mEnv, activityObject, className.c_str());
BAIL_ON_COND(mVrUILayerClass == nullptr, "No java UI Layer class", -1);
mVrUILayerClass = classObject;
jmethodID vrUILayerConstructor =
mEnv->GetMethodID(mVrUILayerClass, "<init>", "(Lorg/citra/citra_emu/vr/VrActivity;)V");

View file

@ -65,8 +65,10 @@ class UILayer {
public:
/** Constructor.
* @param className: the class name of the Java class representing the UI layer.
* @param classObject: the class object of the Java class representing the UI layer.
* This class should subclass org.citra.citra_emu.ui.VrUILayer.
* We do it this way because after the v4 update with quest v64, FindClass() returns
* null on the JNI threads (even with the original classloader).
* @param position: position of the layer
* @param orientation: orientation of the layer
* @param jni: the JNI environment. Should be attached to the current thread
@ -74,7 +76,7 @@ public:
* the class information for UILayer
* @param session a valid XrSession
*/
UILayer(const std::string& className, const XrVector3f&& position,
UILayer(const jclass classObject, const XrVector3f&& position,
const XrQuaternionf&& orientation, JNIEnv* jni, jobject activityObject,
const XrSession& session);
~UILayer();
@ -132,8 +134,8 @@ protected:
XrPosef mPanelFromWorld;
private:
int Init(const std::string& className, const jobject activityObject, const XrVector3f& position,
const XrSession& session);
int Init(const jclass classObject, const jobject activityObject, const XrVector3f& position,
const XrSession& session);
void Shutdown();
/** Creates the swapchain.

View file

@ -6,42 +6,36 @@
namespace VR {
namespace JniGlobalRef {
jmethodID gFindClassMethodID = nullptr;
jobject gClassLoader = nullptr;
jclass gVrKeyboardLayerClass = nullptr;
jclass gVrErrorMessageLayerClass = nullptr;
jclass gVrRibbonLayerClass = nullptr;
} // namespace JniGlobalRef
} // namespace VR
void VR::JNI::InitJNI(JNIEnv* jni, jobject activityObject) {
void VR::JNI::InitJNI(JNIEnv* jni) {
assert(jni != nullptr);
const jclass activityClass = jni->GetObjectClass(activityObject);
if (activityClass == nullptr) { FAIL("Failed to get activity class"); }
// Get the getClassLoader method ID
const jmethodID getClassLoaderMethod =
jni->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
if (getClassLoaderMethod == nullptr) { FAIL("Failed to get getClassLoader method ID"); }
VR::JniGlobalRef::gVrKeyboardLayerClass = static_cast<jclass>(
jni->NewGlobalRef(jni->FindClass("org/citra/citra_emu/vr/ui/VrKeyboardLayer")));
if (VR::JniGlobalRef::gVrKeyboardLayerClass == nullptr) {
FAIL("Could not find VrKeyboardLayer class");
}
VR::JniGlobalRef::gVrErrorMessageLayerClass = static_cast<jclass>(
jni->NewGlobalRef(jni->FindClass("org/citra/citra_emu/vr/ui/VrErrorMessageLayer")));
if (VR::JniGlobalRef::gVrErrorMessageLayerClass == nullptr) {
FAIL("Could not find VrErrorMessageLayer class");
}
// Call getClassLoader of the activity object to obtain the class loader
const jobject classLoaderObject = jni->CallObjectMethod(activityObject, getClassLoaderMethod);
if (classLoaderObject == nullptr) { FAIL("Failed to get class loader object"); }
JniGlobalRef::gClassLoader = jni->NewGlobalRef(classLoaderObject);
// Step 3: Cache the findClass method ID
jclass classLoaderClass = jni->FindClass("java/lang/ClassLoader");
if (classLoaderClass == nullptr) { FAIL("Failed to find class loader class"); }
JniGlobalRef::gFindClassMethodID =
jni->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
if (JniGlobalRef::gFindClassMethodID == nullptr) { FAIL("Failed to get findClass method ID"); }
// Cleanup local references
jni->DeleteLocalRef(activityClass);
jni->DeleteLocalRef(classLoaderClass);
VR::JniGlobalRef::gVrRibbonLayerClass = static_cast<jclass>(
jni->NewGlobalRef(jni->FindClass("org/citra/citra_emu/vr/ui/VrRibbonLayer")));
if (VR::JniGlobalRef::gVrRibbonLayerClass == nullptr) {
FAIL("Could not find VrRibbonLayer class");
}
}
void VR::JNI::CleanupJNI(JNIEnv* jni) {
assert(jni != nullptr);
if (JniGlobalRef::gClassLoader != nullptr) { jni->DeleteGlobalRef(JniGlobalRef::gClassLoader); }
JniGlobalRef::gClassLoader = nullptr;
JniGlobalRef::gFindClassMethodID = nullptr;
VR::JniGlobalRef::gVrKeyboardLayerClass = nullptr;
VR::JniGlobalRef::gVrErrorMessageLayerClass = nullptr;
VR::JniGlobalRef::gVrRibbonLayerClass = nullptr;
}

View file

@ -4,14 +4,15 @@
namespace VR {
namespace JniGlobalRef {
extern jmethodID gFindClassMethodID;
extern jobject gClassLoader;
extern jclass gVrKeyboardLayerClass;
extern jclass gVrErrorMessageLayerClass;
extern jclass gVrRibbonLayerClass;
} // namespace JniGlobalRef
namespace JNI {
// Called during JNI_OnLoad
void InitJNI(JNIEnv* env, jobject activityObject);
void InitJNI(JNIEnv* env);
// Called during JNI_OnUnload
void CleanupJNI(JNIEnv* env);
} // namespace JNI

View file

@ -12,39 +12,74 @@ License : Licensed under GPLv3 or any later version.
#include "JniUtils.h"
#include "JniClassNames.h"
#include "LogUtils.h"
jclass JniUtils::GetGlobalClassReference(JNIEnv* env, jobject activityObject,
jclass JniUtils::GetGlobalClassReference(JNIEnv* jni, jobject activityObject,
const std::string& className) {
// Convert dot ('.') to slash ('/') in class name (Java uses dots, JNI uses slashes for class
// names)
std::string correctedClassName = className;
std::replace(correctedClassName.begin(), correctedClassName.end(), '.', '/');
// Convert std::string to jstring
jstring classNameJString = env->NewStringUTF(correctedClassName.c_str());
// Use the global class loader to find the class
jclass clazz = static_cast<jclass>(env->CallObjectMethod(
VR::JniGlobalRef::gClassLoader, VR::JniGlobalRef::gFindClassMethodID, classNameJString));
if (clazz == nullptr) {
// Class not found
ALOGE("Class not found: {}", correctedClassName.c_str());
// First, get the class object of the activity to get its class loader
const jclass activityClass = jni->GetObjectClass(activityObject);
if (activityClass == nullptr) {
ALOGE("Failed to get activity class");
return nullptr;
}
// Clean up the local reference to the class name jstring
env->DeleteLocalRef(classNameJString);
// Check for exceptions and handle them. This is crucial to prevent crashes due to uncaught
// exceptions.
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return nullptr; // Class not found or other issue
// Get the getClassLoader method ID
const jmethodID getClassLoaderMethod =
jni->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
if (getClassLoaderMethod == nullptr) {
ALOGE("Failed to get getClassLoader method ID");
return nullptr;
}
// Return a global reference to the class
return static_cast<jclass>(env->NewGlobalRef(clazz));
// Call getClassLoader of the activity object to obtain the class loader
const jobject classLoaderObject = jni->CallObjectMethod(activityObject, getClassLoaderMethod);
if (classLoaderObject == nullptr) {
ALOGE("Failed to get class loader object");
return nullptr;
}
// Get the class loader class
const jclass classLoaderClass = jni->FindClass("java/lang/ClassLoader");
if (classLoaderClass == nullptr) {
ALOGE("Failed to get class loader class");
return nullptr;
}
// Get the findClass method ID from the class loader class
const jmethodID findClassMethod =
jni->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
if (findClassMethod == nullptr) {
ALOGE("Failed to get findClass method ID");
return nullptr;
}
// Convert the class name string to a jstring
const jstring javaClassName = jni->NewStringUTF(className.c_str());
if (javaClassName == nullptr) {
ALOGE("Failed to convert class name to jstring");
return nullptr;
}
// Call findClass on the class loader object with the class name
const jclass classToFind = static_cast<jclass>(
jni->CallObjectMethod(classLoaderObject, findClassMethod, javaClassName));
// Clean up local references
jni->DeleteLocalRef(activityClass);
jni->DeleteLocalRef(classLoaderObject);
jni->DeleteLocalRef(classLoaderClass);
jni->DeleteLocalRef(javaClassName);
if (classToFind == nullptr) {
// Handle error (Class not found)
return nullptr;
}
// Create a global reference to the class
const jclass globalClassRef = reinterpret_cast<jclass>(jni->NewGlobalRef(classToFind));
// Clean up the local reference of the class
jni->DeleteLocalRef(classToFind);
return globalClassRef;
}

View file

@ -301,12 +301,12 @@ private:
gOpenXr->mSession);
mKeyboardLayer = std::make_unique<UILayer>(
"org/citra/citra_emu/vr/ui/VrKeyboardLayer", XrVector3f{0, -0.4f, -0.5f},
VR::JniGlobalRef::gVrKeyboardLayerClass, XrVector3f{0, -0.4f, -0.5f},
XrMath::Quatf::FromEuler(-MATH_FLOAT_PI / 4.0f, 0.0f, 0.0f), jni, mActivityObject,
gOpenXr->mSession);
mErrorMessageLayer = std::make_unique<UILayer>(
"org/citra/citra_emu/vr/ui/VrErrorMessageLayer", XrVector3f{0, -0.1f, -1.0f},
VR::JniGlobalRef::gVrErrorMessageLayerClass, XrVector3f{0, -0.1f, -1.0f},
XrQuaternionf{0, 0, 0, 1}, jni, mActivityObject, gOpenXr->mSession);
// Create the cursor layer.
@ -1256,7 +1256,6 @@ Java_org_citra_citra_1emu_vr_VrActivity_nativeOnCreate(JNIEnv* env, jobject thiz
// time to first frame.
gOnCreateStartTime = std::chrono::steady_clock::now();
VR::JNI::InitJNI(env, thiz);
JavaVM* jvm;
env->GetJavaVM(&jvm);
auto ret = VRAppHandle(new VRAppThread(jvm, env, thiz)).l;