diff --git a/doc/classes/JNISingleton.xml b/doc/classes/JNISingleton.xml index 041b8600b0e..50271ac1fc9 100644 --- a/doc/classes/JNISingleton.xml +++ b/doc/classes/JNISingleton.xml @@ -9,4 +9,13 @@ $DOCS_URL/tutorials/platform/android/android_plugin.html#doc-android-plugin + + + + + + Returns [code]true[/code] if the given [param method] name exists in the JNISingleton's Java methods. + + + diff --git a/doc/classes/JavaClass.xml b/doc/classes/JavaClass.xml index 9a6c30df10e..0fea241a0e8 100644 --- a/doc/classes/JavaClass.xml +++ b/doc/classes/JavaClass.xml @@ -29,5 +29,12 @@ Returns a [JavaClass] representing the Java parent class of this class. + + + + + Returns [code]true[/code] if the given [param method] name exists in the object's Java methods. + + diff --git a/doc/classes/JavaObject.xml b/doc/classes/JavaObject.xml index f38070e7d9d..aa090a809b9 100644 --- a/doc/classes/JavaObject.xml +++ b/doc/classes/JavaObject.xml @@ -17,5 +17,12 @@ Returns the [JavaClass] that this object is an instance of. + + + + + Returns [code]true[/code] if the given [param method] name exists in the object's Java methods. + + diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp index 86051a87f3b..8c4a0779953 100644 --- a/platform/android/api/api.cpp +++ b/platform/android/api/api.cpp @@ -62,10 +62,12 @@ void JavaClass::_bind_methods() { ClassDB::bind_method(D_METHOD("get_java_class_name"), &JavaClass::get_java_class_name); ClassDB::bind_method(D_METHOD("get_java_method_list"), &JavaClass::get_java_method_list); ClassDB::bind_method(D_METHOD("get_java_parent_class"), &JavaClass::get_java_parent_class); + ClassDB::bind_method(D_METHOD("has_java_method", "method"), &JavaClass::has_java_method); } void JavaObject::_bind_methods() { ClassDB::bind_method(D_METHOD("get_java_class"), &JavaObject::get_java_class); + ClassDB::bind_method(D_METHOD("has_java_method", "method"), &JavaObject::has_java_method); } void JavaClassWrapper::_bind_methods() { @@ -94,6 +96,10 @@ Ref JavaClass::get_java_parent_class() const { return Ref(); } +bool JavaClass::has_java_method(const StringName &) const { + return false; +} + JavaClass::JavaClass() { } @@ -108,6 +114,10 @@ Ref JavaObject::get_java_class() const { return Ref(); } +bool JavaObject::has_java_method(const StringName &) const { + return false; +} + JavaClassWrapper *JavaClassWrapper::singleton = nullptr; Ref JavaClassWrapper::_wrap(const String &, bool) { diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index de04c80723b..e0f59df2248 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -200,6 +200,7 @@ public: String get_java_class_name() const; TypedArray get_java_method_list() const; Ref get_java_parent_class() const; + bool has_java_method(const StringName &p_method) const; #ifdef ANDROID_ENABLED virtual String to_string() override; @@ -226,6 +227,7 @@ public: virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; Ref get_java_class() const; + bool has_java_method(const StringName &p_method) const; #ifdef ANDROID_ENABLED virtual String to_string() override; @@ -244,7 +246,7 @@ class JavaClassWrapper : public Object { #ifdef ANDROID_ENABLED RBMap> class_cache; friend class JavaClass; - jmethodID Class_getDeclaredConstructors; + jmethodID Class_getConstructors; jmethodID Class_getDeclaredMethods; jmethodID Class_getFields; jmethodID Class_getName; @@ -272,7 +274,7 @@ class JavaClassWrapper : public Object { Ref exception; - Ref _wrap(const String &p_class, bool p_allow_private_methods_access); + Ref _wrap(const String &p_class, bool p_allow_non_public_methods_access); static JavaClassWrapper *singleton; diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index 63df40b1114..64c1e8bf6ad 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -46,35 +46,60 @@ class JNISingleton : public Object { RBMap method_map; Ref wrapped_object; +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("has_java_method", "method"), &JNISingleton::has_java_method); + } + public: virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { - if (wrapped_object.is_valid()) { - RBMap::Element *E = method_map.find(p_method); - - // Check the method we're looking for is in the JNISingleton map and that - // the arguments match. - bool call_error = !E || E->get().argtypes.size() != p_argcount; - if (!call_error) { - for (int i = 0; i < p_argcount; i++) { - if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { - call_error = true; - break; - } - } - } - - if (!call_error) { - return wrapped_object->callp(p_method, p_args, p_argcount, r_error); - } + // Godot methods take precedence. + Variant ret = Object::callp(p_method, p_args, p_argcount, r_error); + if (r_error.error == Callable::CallError::CALL_OK) { + return ret; } - return Object::callp(p_method, p_args, p_argcount, r_error); + // Check the method we're looking for is in the JNISingleton map. + RBMap::Element *E = method_map.find(p_method); + if (E) { + if (wrapped_object.is_valid()) { + // Check that the arguments match. + int method_arg_count = E->get().argtypes.size(); + if (p_argcount < method_arg_count) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + } else if (p_argcount > method_arg_count) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + } else { + // Check the arguments are valid. + bool arguments_valid = true; + for (int i = 0; i < p_argcount; i++) { + if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { + arguments_valid = false; + break; + } + } + + if (arguments_valid) { + return wrapped_object->callp(p_method, p_args, p_argcount, r_error); + } else { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + } + } + } else { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + } + } + return Variant(); } Ref get_wrapped_object() const { return wrapped_object; } + bool has_java_method(const StringName &p_method) const { + return method_map.has(p_method); + } + void add_method(const StringName &p_name, const Vector &p_args, Variant::Type p_ret_type) { MethodData md; md.argtypes = p_args; diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt b/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt index 35ff7d6fa14..ca1c32cce9e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt @@ -51,7 +51,7 @@ class AndroidRuntimePlugin(godot: Godot) : GodotPlugin(godot) { * Provides access to the host [android.app.Activity] to GDScript */ @UsedByGodot - override fun getActivity() = super.getActivity() + public override fun getActivity() = super.getActivity() /** * Utility method used to create [Runnable] from Godot [Callable]. diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 8339ff6ae3a..78607c5565c 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -36,6 +36,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) { HashMap>::Iterator M = methods.find(p_method); if (!M) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return false; } @@ -756,7 +757,11 @@ bool JavaClass::_get(const StringName &p_name, Variant &r_ret) const { } Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - Variant ret; + // Godot methods take precedence. + Variant ret = RefCounted::callp(p_method, p_args, p_argcount, r_error); + if (r_error.error == Callable::CallError::CALL_OK) { + return ret; + } String method = (p_method == java_constructor_name) ? "" : p_method; bool found = _call_method(nullptr, method, p_args, p_argcount, r_error, ret); @@ -764,7 +769,7 @@ Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int return ret; } - return RefCounted::callp(p_method, p_args, p_argcount, r_error); + return Variant(); } String JavaClass::get_java_class_name() const { @@ -858,6 +863,11 @@ String JavaClass::to_string() { return ""; } +bool JavaClass::has_java_method(const StringName &p_method) const { + String method = (p_method == java_constructor_name) ? "" : p_method; + return methods.has(method); +} + JavaClass::JavaClass() { } @@ -873,10 +883,15 @@ JavaClass::~JavaClass() { ///////////////////// Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + // Godot methods take precedence. + Variant ret = RefCounted::callp(p_method, p_args, p_argcount, r_error); + if (r_error.error == Callable::CallError::CALL_OK) { + return ret; + } + if (instance) { Ref c = base_class; while (c.is_valid()) { - Variant ret; bool found = c->_call_method(this, p_method, p_args, p_argcount, r_error, ret); if (found) { return ret; @@ -885,7 +900,7 @@ Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, in } } - return RefCounted::callp(p_method, p_args, p_argcount, r_error); + return Variant(); } Ref JavaObject::get_java_class() const { @@ -899,6 +914,19 @@ String JavaObject::to_string() { return RefCounted::to_string(); } +bool JavaObject::has_java_method(const StringName &p_method) const { + if (instance) { + Ref c = base_class; + while (c.is_valid()) { + if (c->has_java_method(p_method)) { + return true; + } + c = c->get_java_parent_class(); + } + } + return false; +} + JavaObject::JavaObject() { } @@ -1461,7 +1489,7 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return false; } -Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_private_methods_access) { +Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_non_public_methods_access) { String class_name_dots = p_class.replace_char('/', '.'); if (class_cache.has(class_name_dots)) { return class_cache[class_name_dots]; @@ -1473,10 +1501,18 @@ Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_priva jclass bclass = jni_find_class(env, class_name_dots.replace_char('.', '/').utf8().get_data()); ERR_FAIL_NULL_V_MSG(bclass, Ref(), vformat("Java class '%s' not found.", p_class)); - jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredConstructors); + jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getConstructors); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } ERR_FAIL_NULL_V(constructors, Ref()); jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredMethods); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } ERR_FAIL_NULL_V(methods, Ref()); Ref java_class = memnew(JavaClass); @@ -1515,8 +1551,9 @@ Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_priva Vector params; jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers); + bool is_public = (mods & 0x0001) != 0; // java.lang.reflect.Modifier.PUBLIC - if (!(mods & 0x0001) && (is_constructor || !p_allow_private_methods_access)) { + if (!is_public && (is_constructor || !p_allow_non_public_methods_access)) { env->DeleteLocalRef(obj); continue; //not public bye } @@ -1627,6 +1664,13 @@ Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_priva mi.method = env->GetMethodID(bclass, str_method.utf8().get_data(), signature.utf8().get_data()); } + if (env->ExceptionCheck()) { + // Exceptions may be thrown when trying to access hidden methods; write the exception to the logs and continue. + env->ExceptionDescribe(); + env->ExceptionClear(); + continue; + } + ERR_CONTINUE(!mi.method); java_class->methods[str_method].push_back(mi); @@ -1700,7 +1744,7 @@ JavaClassWrapper::JavaClassWrapper() { ERR_FAIL_NULL(env); jclass bclass = jni_find_class(env, "java/lang/Class"); - Class_getDeclaredConstructors = env->GetMethodID(bclass, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;"); + Class_getConstructors = env->GetMethodID(bclass, "getConstructors", "()[Ljava/lang/reflect/Constructor;"); Class_getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"); Class_getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;"); Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");