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;");