diff --git a/doc/classes/JavaClassWrapper.xml b/doc/classes/JavaClassWrapper.xml
index c6f76700b3..9bc455280c 100644
--- a/doc/classes/JavaClassWrapper.xml
+++ b/doc/classes/JavaClassWrapper.xml
@@ -21,6 +21,42 @@
$DOCS_URL/tutorials/platform/android/javaclasswrapper_and_androidruntimeplugin.html
+
+
+
+
+
+ Creates a [JavaObject] implementing the given Java interfaces using the given [Object] as the implementation.
+ The [param object] must contain methods signatures matching the methods signatures from the passed Java [param interfaces]. Invoking methods from the Java [param interfaces] will route to the matching [param object] method.
+ [codeblock]
+ class PrintProxy:
+ func println(content: String) -> void:
+ print(content)
+
+ var print_proxy = PrintProxy.new()
+ var printer_object = JavaClassWrapper.create_proxy(print_proxy, ["android.util.Printer"])
+ printer_object.println("Hello Godot World!")
+ [/codeblock]
+ [b]Note:[/b] This method only works on Android. On every other platform, this method will always return [code]null[/code].
+
+
+
+
+
+
+
+ Creates a [JavaObject] implementing the Java Single Abstract Method (SAM) interface using the Godot [Callable] as the implementation.
+ The [param sam_interface] [b]must be[/b] a Java SAM interface, meaning it must only have a single abstract method to implement.
+ The [param callable] must be able to handle the same parameter types as the SAM interface method, and must provide the same return type. The [param callable] will be invoked as a callback, passing the arguments from the Java SAM interface method.
+ [codeblock]
+ var cb = func (content: String) -> void:
+ print(content)
+ var callback = JavaClassWrapper.create_sam_callback("android.util.Printer", cb)
+ callback.println("Hello Godot World!")
+ [/codeblock]
+ [b]Note:[/b] This method only works on Android. On every other platform, this method will always return [code]null[/code].
+
+
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index 24d67bdbe6..5a10a47ba5 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -74,6 +74,8 @@ void JavaObject::_bind_methods() {
void JavaClassWrapper::_bind_methods() {
ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap);
ClassDB::bind_method(D_METHOD("get_exception"), &JavaClassWrapper::get_exception);
+ ClassDB::bind_method(D_METHOD("create_sam_callback", "sam_interface", "callable"), &JavaClassWrapper::create_sam_callback);
+ ClassDB::bind_method(D_METHOD("create_proxy", "object", "interfaces"), &JavaClassWrapper::create_proxy);
}
#if !defined(ANDROID_ENABLED)
@@ -125,6 +127,14 @@ Ref JavaClassWrapper::_wrap(const String &, bool) {
return Ref();
}
+Ref JavaClassWrapper::create_sam_callback(const String &p_interface, const Callable &p_callable) {
+ return Ref();
+}
+
+Ref JavaClassWrapper::create_proxy(const Object *p_object, const PackedStringArray &p_interfaces) {
+ return Ref();
+}
+
JavaClassWrapper::JavaClassWrapper() {
singleton = this;
}
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index ab469217c9..9e5657fb87 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -191,6 +191,7 @@ class JavaClass : public RefCounted {
String java_constructor_name;
HashMap> methods;
jclass _class;
+ bool is_interface;
#endif
protected:
@@ -252,8 +253,10 @@ class JavaClassWrapper : public Object {
jmethodID Class_getConstructors;
jmethodID Class_getDeclaredMethods;
jmethodID Class_getFields;
+ jmethodID Class_getInterfaces;
jmethodID Class_getName;
jmethodID Class_getSuperclass;
+ jmethodID Class_isInterface;
jmethodID Constructor_getParameterTypes;
jmethodID Constructor_getModifiers;
jmethodID Method_getParameterTypes;
@@ -272,7 +275,16 @@ class JavaClassWrapper : public Object {
jmethodID Float_floatValue;
jmethodID Double_doubleValue;
+ jclass proxy_class;
+ jmethodID Proxy_isProxyClass;
+
+ jclass android_runtime_class;
+ jmethodID ARP_create_proxy_from_godot_callable;
+ jmethodID ARP_create_proxy_from_godot_object_id;
+
+ bool _is_proxy_class(JNIEnv *env, jclass p_class);
bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig);
+ bool _wrap_class_components(JNIEnv *p_env, const Ref &p_java_class, jclass p_class, bool p_allow_non_public_methods_access);
#endif
Ref exception;
@@ -291,6 +303,9 @@ public:
return _wrap(p_class, false);
}
+ Ref create_sam_callback(const String &p_sam_interface, const Callable &p_callable);
+ Ref create_proxy(const Object *p_object, const PackedStringArray &p_interfaces);
+
Ref get_exception() {
return exception;
}
diff --git a/platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg b/platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg
index b6ed5489bf..1f8f066232 100644
--- a/platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg
+++ b/platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg
@@ -1,12 +1,4 @@
list=[{
-"base": &"RefCounted",
-"class": &"BaseTest",
-"icon": "",
-"is_abstract": true,
-"is_tool": false,
-"language": &"GDScript",
-"path": "res://test/base_test.gd"
-}, {
"base": &"BaseTest",
"class": &"FileAccessTests",
"icon": "",
@@ -22,4 +14,12 @@ list=[{
"is_tool": false,
"language": &"GDScript",
"path": "res://test/javaclasswrapper/java_class_wrapper_tests.gd"
+}, {
+"base": &"RefCounted",
+"class": &"BaseTest",
+"icon": "",
+"is_abstract": true,
+"is_tool": false,
+"language": &"GDScript",
+"path": "res://test/base_test.gd"
}]
diff --git a/platform/android/java/app/src/instrumented/assets/main.tscn b/platform/android/java/app/src/instrumented/assets/main.tscn
index 848845bab6..692618a73f 100644
--- a/platform/android/java/app/src/instrumented/assets/main.tscn
+++ b/platform/android/java/app/src/instrumented/assets/main.tscn
@@ -1,29 +1,29 @@
-[gd_scene load_steps=2 format=3 uid="uid://cg3hylang5fxn"]
+[gd_scene format=3 uid="uid://cg3hylang5fxn"]
[ext_resource type="Script" uid="uid://bv6y7in6otgcm" path="res://main.gd" id="1_j0gfq"]
-[node name="Main" type="Node2D"]
+[node name="Main" type="Node2D" unique_id=852911723]
script = ExtResource("1_j0gfq")
-[node name="VBoxContainer" type="VBoxContainer" parent="."]
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1839352715]
offset_left = 68.0
offset_top = 102.0
offset_right = 506.0
offset_bottom = 408.0
theme_override_constants/separation = 25
-[node name="PluginToastButton" type="Button" parent="VBoxContainer"]
+[node name="PluginToastButton" type="Button" parent="VBoxContainer" unique_id=1670434164]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "Plugin Toast
"
-[node name="VibrationButton" type="Button" parent="VBoxContainer"]
+[node name="VibrationButton" type="Button" parent="VBoxContainer" unique_id=648980813]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "Vibration"
-[node name="GDScriptToastButton" type="Button" parent="VBoxContainer"]
+[node name="GDScriptToastButton" type="Button" parent="VBoxContainer" unique_id=95554078]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "GDScript Toast
diff --git a/platform/android/java/app/src/instrumented/assets/project.godot b/platform/android/java/app/src/instrumented/assets/project.godot
index f4ec299792..68bc653c43 100644
--- a/platform/android/java/app/src/instrumented/assets/project.godot
+++ b/platform/android/java/app/src/instrumented/assets/project.godot
@@ -8,11 +8,15 @@
config_version=5
+[animation]
+
+compatibility/default_parent_skeleton_in_mesh_instance_3d=true
+
[application]
config/name="Godot App Instrumentation Tests"
run/main_scene="res://main.tscn"
-config/features=PackedStringArray("4.5", "GL Compatibility")
+config/features=PackedStringArray("4.6", "GL Compatibility")
config/icon="res://icon.svg"
[debug]
diff --git a/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd b/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd
index 65843f1f3b..1d4a57669c 100644
--- a/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd
+++ b/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd
@@ -20,6 +20,10 @@ func run_tests():
__exec_test(test_callable)
+ __exec_test(test_interface_callable_proxy)
+
+ __exec_test(test_interface_object_proxy)
+
print("JavaClassWrapper tests finished.")
print("Tests started: " + str(_test_started))
print("Tests completed: " + str(_test_completed))
@@ -169,3 +173,34 @@ func test_callable() -> bool:
assert_equal(cb1_data['called'], true)
return true
+
+func test_interface_callable_proxy() -> bool:
+ var cb1_data := {called = false, content = ""}
+ var cb1 = func (content: String) -> void:
+ cb1_data['called'] = true
+ cb1_data['content'] = content
+
+ var printer_proxy = JavaClassWrapper.create_sam_callback("android.util.Printer", cb1)
+ assert_true(printer_proxy != null)
+
+ printer_proxy.println("This is a callback test")
+ assert_equal(cb1_data['called'], true)
+ assert_equal(cb1_data['content'], "This is a callback test")
+ return true
+
+class PrintProxy:
+ var test_data := {called = false, content = ""}
+
+ func println(content: String) -> void:
+ test_data['called'] = true
+ test_data['content'] = content
+
+func test_interface_object_proxy() -> bool:
+ var print_object = PrintProxy.new()
+ var proxy = JavaClassWrapper.create_proxy(print_object, ["android.util.Printer"])
+ assert_true(proxy != null)
+
+ proxy.println("This is proxy test")
+ assert_equal(print_object.test_data['called'], true)
+ assert_equal(print_object.test_data['content'], "This is proxy test")
+ return true
diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt
index 4394d51404..19c9d51b1e 100644
--- a/platform/android/java/lib/src/main/java/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt
+++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt
@@ -32,10 +32,13 @@ package org.godotengine.godot.plugin
import android.content.Intent
import android.util.Log
+import androidx.annotation.Keep
import androidx.core.net.toUri
import org.godotengine.godot.Godot
import org.godotengine.godot.variant.Callable
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.Proxy
/**
* Built-in Godot Android plugin used to provide access to the Android runtime capabilities.
@@ -43,7 +46,76 @@ import org.godotengine.godot.variant.Callable
* @see Integrating with Android APIs
*/
class AndroidRuntimePlugin(godot: Godot) : GodotPlugin(godot) {
- private val TAG = AndroidRuntimePlugin::class.java.simpleName
+
+ companion object {
+ private val TAG = AndroidRuntimePlugin::class.java.simpleName
+
+ /**
+ * Helper method used to generate Godot Proxy instances.
+ */
+ @JvmStatic
+ @Keep
+ private fun generateProxyInstance(interfaces: Array, invocationHandler: InvocationHandler): Any? {
+ try {
+ val interfaceClasses = interfaces.map { Class.forName(it) }.toTypedArray()
+
+ val proxy = Proxy.newProxyInstance(invocationHandler.javaClass.classLoader, interfaceClasses, invocationHandler)
+ return proxy
+ } catch (e: Exception) {
+ Log.w(TAG, "Error generating Godot proxy for interfaces ${interfaces.joinToString(",")}", e)
+ }
+ return null
+ }
+
+ /**
+ * Utility method used to create [java.lang.reflect.Proxy] instance wrapping a given Godot [Callable].
+ *
+ * The [Proxy] instance is used to implement one SAM interface with the [Callable] serving as the delegate
+ * implementation for the SAM interface overridden methods.
+ */
+ @JvmStatic
+ @Keep
+ private fun createProxyFromGodotCallable(interfaceName: String, godotCallable: Callable): Any? {
+ return generateProxyInstance(arrayOf(interfaceName)) { proxy, method, args ->
+ when (method.name) {
+ // We automatically handle 'toString', 'equals' and 'hashCode' to simplify the task of the caller
+ // and provide consistency.
+ "toString" -> "Godot Callable Proxy for $interfaceName"
+ "equals" -> proxy == args[0]
+ "hashCode" -> godotCallable.hashCode()
+
+ // Invocation for the interface single abstract method falls here and is dispatched to the
+ // Godot [Callable].
+ else -> godotCallable.call(*args)
+ }
+ }
+ }
+
+ /**
+ * Utility method used to create [java.lang.reflect.Proxy] instance wrapping a given Godot Object represented by
+ * its ObjectID.
+ *
+ * The [Proxy] instance is used to implement one or multiple interfaces with the Object represented by
+ * [godotObjectID] serving as the delegate implementation for the interface(s) overridden methods.
+ */
+ @JvmStatic
+ @Keep
+ private fun createProxyFromGodotObjectID(godotObjectID: Long, interfaces: Array): Any? {
+ return generateProxyInstance(interfaces) { proxy, method, args ->
+ when (val methodName = method.name) {
+ // We automatically handle 'toString', 'equals' and 'hashCode' to simplify the task of the caller
+ // and provide consistency.
+ "toString" -> "Godot Object Proxy for ${interfaces.joinToString(",")}"
+ "equals" -> proxy == args[0]
+ "hashCode" -> godotObjectID
+
+ // Invocation for the remaining interface(s) methods falls here and is dispatched to the
+ // Godot Object.
+ else -> Callable.call(godotObjectID, methodName, *args)
+ }
+ }
+ }
+ }
override fun getPluginName() = "AndroidRuntime"
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index 051775f043..f1a9b689df 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -1461,74 +1461,58 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return false;
}
-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];
+bool JavaClassWrapper::_wrap_class_components(JNIEnv *p_env, const Ref &p_java_class, jclass p_class, bool p_allow_non_public_methods_access) {
+ ERR_FAIL_NULL_V(p_class, false);
+
+ jobjectArray constructors = (jobjectArray)p_env->CallObjectMethod(p_class, Class_getConstructors);
+ if (p_env->ExceptionCheck()) {
+ p_env->ExceptionDescribe();
+ p_env->ExceptionClear();
}
+ ERR_FAIL_NULL_V(constructors, false);
- JNIEnv *env = get_jni_env();
- ERR_FAIL_NULL_V(env, Ref());
-
- 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_getConstructors);
- if (env->ExceptionCheck()) {
- env->ExceptionDescribe();
- env->ExceptionClear();
+ jobjectArray methods = (jobjectArray)p_env->CallObjectMethod(p_class, Class_getDeclaredMethods);
+ if (p_env->ExceptionCheck()) {
+ p_env->ExceptionDescribe();
+ p_env->ExceptionClear();
}
- ERR_FAIL_NULL_V(constructors, Ref());
+ ERR_FAIL_NULL_V(methods, false);
- 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);
- java_class->java_class_name = class_name_dots;
- Vector class_name_parts = class_name_dots.split(".");
- java_class->java_constructor_name = class_name_parts[class_name_parts.size() - 1];
- java_class->_class = (jclass)env->NewGlobalRef(bclass);
- class_cache[class_name_dots] = java_class;
-
- int constructor_count = env->GetArrayLength(constructors);
- int method_count = env->GetArrayLength(methods);
+ int constructor_count = p_env->GetArrayLength(constructors);
+ int method_count = p_env->GetArrayLength(methods);
int methods_and_constructors_count = method_count + constructor_count;
for (int i = 0; i < methods_and_constructors_count; i++) {
bool is_constructor = i < constructor_count;
jobject obj = is_constructor
- ? env->GetObjectArrayElement(constructors, i)
- : env->GetObjectArrayElement(methods, i - constructor_count);
+ ? p_env->GetObjectArrayElement(constructors, i)
+ : p_env->GetObjectArrayElement(methods, i - constructor_count);
ERR_CONTINUE(!obj);
String str_method;
if (is_constructor) {
str_method = "";
} else {
- jstring name = (jstring)env->CallObjectMethod(obj, Method_getName);
- str_method = jstring_to_string(name, env);
- env->DeleteLocalRef(name);
+ jstring name = (jstring)p_env->CallObjectMethod(obj, Method_getName);
+ str_method = jstring_to_string(name, p_env);
+ p_env->DeleteLocalRef(name);
}
Vector params;
- jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers);
+ jint mods = p_env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers);
bool is_public = (mods & 0x0001) != 0; // java.lang.reflect.Modifier.PUBLIC
if (!is_public && (is_constructor || !p_allow_non_public_methods_access)) {
- env->DeleteLocalRef(obj);
+ p_env->DeleteLocalRef(obj);
continue; //not public bye
}
- jobjectArray param_types = (jobjectArray)env->CallObjectMethod(obj, is_constructor ? Constructor_getParameterTypes : Method_getParameterTypes);
- int count = env->GetArrayLength(param_types);
+ jobjectArray param_types = (jobjectArray)p_env->CallObjectMethod(obj, is_constructor ? Constructor_getParameterTypes : Method_getParameterTypes);
+ int count = p_env->GetArrayLength(param_types);
- if (!java_class->methods.has(str_method)) {
- java_class->methods[str_method] = List();
+ if (!p_java_class->methods.has(str_method)) {
+ p_java_class->methods[str_method] = List();
}
JavaClass::MethodInfo mi;
@@ -1539,24 +1523,24 @@ Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_non_p
String signature = "(";
for (int j = 0; j < count; j++) {
- jobject obj2 = env->GetObjectArrayElement(param_types, j);
+ jobject obj2 = p_env->GetObjectArrayElement(param_types, j);
String strsig;
uint32_t sig = 0;
- if (!_get_type_sig(env, obj2, sig, strsig)) {
+ if (!_get_type_sig(p_env, obj2, sig, strsig)) {
valid = false;
- env->DeleteLocalRef(obj2);
+ p_env->DeleteLocalRef(obj2);
break;
}
signature += strsig;
mi.param_types.push_back(sig);
mi.param_sigs.push_back(strsig);
- env->DeleteLocalRef(obj2);
+ p_env->DeleteLocalRef(obj2);
}
if (!valid) {
- print_line("Method can't be bound (unsupported arguments): " + class_name_dots + "::" + str_method);
- env->DeleteLocalRef(obj);
- env->DeleteLocalRef(param_types);
+ print_line("Method can't be bound (unsupported arguments): " + p_java_class->java_class_name + "::" + str_method);
+ p_env->DeleteLocalRef(obj);
+ p_env->DeleteLocalRef(param_types);
continue;
}
@@ -1566,27 +1550,27 @@ Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_non_p
signature += "V";
mi.return_type = JavaClass::ARG_TYPE_CLASS;
} else {
- jobject return_type = (jobject)env->CallObjectMethod(obj, Method_getReturnType);
+ jobject return_type = (jobject)p_env->CallObjectMethod(obj, Method_getReturnType);
String strsig;
uint32_t sig = 0;
- if (!_get_type_sig(env, return_type, sig, strsig)) {
- print_line("Method can't be bound (unsupported return type): " + class_name_dots + "::" + str_method);
- env->DeleteLocalRef(obj);
- env->DeleteLocalRef(param_types);
- env->DeleteLocalRef(return_type);
+ if (!_get_type_sig(p_env, return_type, sig, strsig)) {
+ print_line("Method can't be bound (unsupported return type): " + p_java_class->java_class_name + "::" + str_method);
+ p_env->DeleteLocalRef(obj);
+ p_env->DeleteLocalRef(param_types);
+ p_env->DeleteLocalRef(return_type);
continue;
}
signature += strsig;
mi.return_type = sig;
- env->DeleteLocalRef(return_type);
+ p_env->DeleteLocalRef(return_type);
}
bool discard = false;
- for (List::Element *E = java_class->methods[str_method].front(); E; E = E->next()) {
+ for (List::Element *E = p_java_class->methods[str_method].front(); E; E = E->next()) {
float new_likeliness = 0;
float existing_likeliness = 0;
@@ -1617,7 +1601,7 @@ Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_non_p
}
if (new_likeliness > existing_likeliness) {
- java_class->methods[str_method].erase(E);
+ p_java_class->methods[str_method].erase(E);
break;
} else {
discard = true;
@@ -1626,72 +1610,202 @@ Ref JavaClassWrapper::_wrap(const String &p_class, bool p_allow_non_p
if (!discard) {
if (mi._static) {
- mi.method = env->GetStaticMethodID(bclass, str_method.utf8().get_data(), signature.utf8().get_data());
+ mi.method = p_env->GetStaticMethodID(p_class, str_method.utf8().get_data(), signature.utf8().get_data());
} else {
- mi.method = env->GetMethodID(bclass, str_method.utf8().get_data(), signature.utf8().get_data());
+ mi.method = p_env->GetMethodID(p_class, str_method.utf8().get_data(), signature.utf8().get_data());
}
- if (env->ExceptionCheck()) {
+ if (p_env->ExceptionCheck()) {
// Exceptions may be thrown when trying to access hidden methods; write the exception to the logs and continue.
- env->ExceptionDescribe();
- env->ExceptionClear();
+ p_env->ExceptionDescribe();
+ p_env->ExceptionClear();
continue;
}
if (mi.method) {
- java_class->methods[str_method].push_back(mi);
+ p_java_class->methods[str_method].push_back(mi);
}
}
- env->DeleteLocalRef(obj);
- env->DeleteLocalRef(param_types);
+ p_env->DeleteLocalRef(obj);
+ p_env->DeleteLocalRef(param_types);
}
- env->DeleteLocalRef(constructors);
- env->DeleteLocalRef(methods);
+ p_env->DeleteLocalRef(constructors);
+ p_env->DeleteLocalRef(methods);
- jobjectArray fields = (jobjectArray)env->CallObjectMethod(bclass, Class_getFields);
+ jobjectArray fields = (jobjectArray)p_env->CallObjectMethod(p_class, Class_getFields);
- int count = env->GetArrayLength(fields);
+ int count = p_env->GetArrayLength(fields);
for (int i = 0; i < count; i++) {
- jobject obj = env->GetObjectArrayElement(fields, i);
+ jobject obj = p_env->GetObjectArrayElement(fields, i);
ERR_CONTINUE(!obj);
- jstring name = (jstring)env->CallObjectMethod(obj, Field_getName);
- String str_field = jstring_to_string(name, env);
- env->DeleteLocalRef(name);
- int mods = env->CallIntMethod(obj, Field_getModifiers);
+ jstring name = (jstring)p_env->CallObjectMethod(obj, Field_getName);
+ String str_field = jstring_to_string(name, p_env);
+ p_env->DeleteLocalRef(name);
+ int mods = p_env->CallIntMethod(obj, Field_getModifiers);
if ((mods & 0x8) && (mods & 0x1)) { //static public!
- jobject objc = env->CallObjectMethod(obj, Field_get, nullptr);
+ jobject objc = p_env->CallObjectMethod(obj, Field_get, nullptr);
if (objc) {
uint32_t sig;
String strsig;
- jclass cl = env->GetObjectClass(objc);
- if (JavaClassWrapper::_get_type_sig(env, cl, sig, strsig)) {
+ jclass cl = p_env->GetObjectClass(objc);
+ if (JavaClassWrapper::_get_type_sig(p_env, cl, sig, strsig)) {
if ((sig & JavaClass::ARG_TYPE_MASK) <= JavaClass::ARG_TYPE_STRING) {
Variant value;
- if (JavaClass::_convert_object_to_variant(env, objc, value, sig)) {
- java_class->constant_map[str_field] = value;
+ if (JavaClass::_convert_object_to_variant(p_env, objc, value, sig)) {
+ p_java_class->constant_map[str_field] = value;
}
}
}
- env->DeleteLocalRef(cl);
+ p_env->DeleteLocalRef(cl);
}
- env->DeleteLocalRef(objc);
+ p_env->DeleteLocalRef(objc);
}
- env->DeleteLocalRef(obj);
+ p_env->DeleteLocalRef(obj);
}
- env->DeleteLocalRef(fields);
+ p_env->DeleteLocalRef(fields);
+ return true;
+}
+
+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];
+ }
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Ref());
+
+ 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));
+
+ Ref java_class;
+ java_class.instantiate();
+ java_class->java_class_name = class_name_dots;
+ Vector class_name_parts = class_name_dots.split(".");
+ java_class->java_constructor_name = class_name_parts[class_name_parts.size() - 1];
+ java_class->_class = (jclass)env->NewGlobalRef(bclass);
+ java_class->is_interface = env->CallBooleanMethod(bclass, Class_isInterface);
+
+ bool class_components_configured;
+ if (_is_proxy_class(env, bclass)) {
+ // Proxy class components must be setup using the interfaces they implement.
+ jobjectArray interfaces = (jobjectArray)env->CallObjectMethod(bclass, Class_getInterfaces);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ int interfaces_count = interfaces == nullptr ? 0 : env->GetArrayLength(interfaces);
+ for (int i = 0; i < interfaces_count; i++) {
+ jclass interface = (jclass)env->GetObjectArrayElement(interfaces, i);
+ ERR_CONTINUE(!interface);
+
+ jstring j_interface_name = (jstring)env->CallObjectMethod(interface, Class_getName);
+ String interface_name = jstring_to_string(j_interface_name, env);
+ env->DeleteLocalRef(j_interface_name);
+ if (!_wrap_class_components(env, java_class, interface, p_allow_non_public_methods_access)) {
+ ERR_PRINT(vformat("Unable to set up components for proxy class interface %s.", interface_name));
+ continue;
+ }
+
+ class_components_configured = true;
+ }
+ } else {
+ class_components_configured = _wrap_class_components(env, java_class, bclass, p_allow_non_public_methods_access);
+ }
env->DeleteLocalRef(bclass);
+ if (!class_components_configured) {
+ java_class.unref();
+ return Ref();
+ }
+
+ // Cache the initialized JavaClass instance.
+ class_cache[class_name_dots] = java_class;
+
return java_class;
}
+Ref JavaClassWrapper::create_sam_callback(const String &p_sam_interface, const Callable &p_callable) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Ref());
+ ERR_FAIL_NULL_V(android_runtime_class, Ref());
+ ERR_FAIL_NULL_V(ARP_create_proxy_from_godot_callable, Ref());
+
+ Ref interface_class = wrap(p_sam_interface);
+ if (interface_class.is_null()) {
+ ERR_PRINT(vformat("Invalid java class: %s.", p_sam_interface));
+ return Ref();
+ }
+ if (!interface_class->is_interface) {
+ ERR_PRINT(vformat("Java class %s must be an interface.", p_sam_interface));
+ return Ref();
+ }
+
+ if (interface_class->methods.size() != 1) {
+ ERR_PRINT(vformat("%s must be a Single Abstract Method (SAM) interface.", p_sam_interface));
+ return Ref();
+ }
+
+ jobject j_callable = callable_to_jcallable(env, p_callable);
+ jstring j_interface = env->NewStringUTF(p_sam_interface.utf8().get_data());
+
+ jobject proxy = env->CallStaticObjectMethod(android_runtime_class, ARP_create_proxy_from_godot_callable, j_interface, j_callable);
+ Ref result = _jobject_to_variant(env, proxy);
+
+ env->DeleteLocalRef(j_callable);
+ env->DeleteLocalRef(j_interface);
+ env->DeleteLocalRef(proxy);
+
+ return result;
+}
+
+Ref JavaClassWrapper::create_proxy(const Object *p_object, const PackedStringArray &p_interfaces) {
+ ERR_FAIL_NULL_V(p_object, Ref());
+ ERR_FAIL_COND_V(p_interfaces.is_empty(), Ref());
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Ref());
+ ERR_FAIL_NULL_V(android_runtime_class, Ref());
+ ERR_FAIL_NULL_V(ARP_create_proxy_from_godot_callable, Ref());
+
+ for (const String &interface_name : p_interfaces) {
+ Ref interface_class = wrap(interface_name);
+ if (interface_class.is_null()) {
+ ERR_PRINT(vformat("Invalid java class: %s.", interface_name));
+ return Ref();
+ }
+
+ if (!interface_class->is_interface) {
+ ERR_PRINT(vformat("Java class %s must be an interface.", interface_name));
+ return Ref();
+ }
+ }
+
+ jlong object_id = p_object->get_instance_id();
+ jobjectArray j_interfaces = env->NewObjectArray(p_interfaces.size(), jni_find_class(env, "java/lang/String"), nullptr);
+ for (int i = 0; i < p_interfaces.size(); i++) {
+ jstring j_interface = env->NewStringUTF(p_interfaces[i].utf8().get_data());
+ env->SetObjectArrayElement(j_interfaces, i, j_interface);
+ env->DeleteLocalRef(j_interface);
+ }
+
+ jobject proxy = env->CallStaticObjectMethod(android_runtime_class, ARP_create_proxy_from_godot_object_id, object_id, j_interfaces);
+ Ref result = _jobject_to_variant(env, proxy);
+
+ env->DeleteLocalRef(j_interfaces);
+ env->DeleteLocalRef(proxy);
+
+ return result;
+}
+
Ref JavaClassWrapper::wrap_jclass(jclass p_class, bool p_allow_non_public_methods_access) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, Ref());
@@ -1703,6 +1817,14 @@ Ref JavaClassWrapper::wrap_jclass(jclass p_class, bool p_allow_non_pu
return _wrap(class_name_string, p_allow_non_public_methods_access);
}
+bool JavaClassWrapper::_is_proxy_class(JNIEnv *env, jclass p_class) {
+ ERR_FAIL_NULL_V(proxy_class, false);
+ ERR_FAIL_NULL_V(Proxy_isProxyClass, false);
+ ERR_FAIL_NULL_V(env, false);
+
+ return env->CallStaticBooleanMethod(proxy_class, Proxy_isProxyClass, p_class);
+}
+
JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
JavaClassWrapper::JavaClassWrapper() {
@@ -1711,12 +1833,27 @@ JavaClassWrapper::JavaClassWrapper() {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
+ proxy_class = jni_find_class(env, "java/lang/reflect/Proxy");
+ if (proxy_class) {
+ proxy_class = (jclass)env->NewGlobalRef(proxy_class);
+ Proxy_isProxyClass = env->GetStaticMethodID(proxy_class, "isProxyClass", "(Ljava/lang/Class;)Z");
+ }
+
+ android_runtime_class = jni_find_class(env, "org/godotengine/godot/plugin/AndroidRuntimePlugin");
+ if (android_runtime_class) {
+ android_runtime_class = (jclass)env->NewGlobalRef(android_runtime_class);
+ ARP_create_proxy_from_godot_callable = env->GetStaticMethodID(android_runtime_class, "createProxyFromGodotCallable", "(Ljava/lang/String;Lorg/godotengine/godot/variant/Callable;)Ljava/lang/Object;");
+ ARP_create_proxy_from_godot_object_id = env->GetStaticMethodID(android_runtime_class, "createProxyFromGodotObjectID", "(J[Ljava/lang/String;)Ljava/lang/Object;");
+ }
+
jclass bclass = jni_find_class(env, "java/lang/Class");
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_getInterfaces = env->GetMethodID(bclass, "getInterfaces", "()[Ljava/lang/Class;");
Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
Class_getSuperclass = env->GetMethodID(bclass, "getSuperclass", "()Ljava/lang/Class;");
+ Class_isInterface = env->GetMethodID(bclass, "isInterface", "()Z");
env->DeleteLocalRef(bclass);
bclass = jni_find_class(env, "java/lang/reflect/Constructor");