Merge pull request #115498 from m4gr3d/add_javaclasswrapper_proxy_interfaces
Android: Allow implementing java interfaces from GDScript
This commit is contained in:
+8
-8
@@ -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"
|
||||
}]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
+35
@@ -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
|
||||
|
||||
+73
-1
@@ -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 <a href="https://docs.godotengine.org/en/latest/tutorials/platform/android/javaclasswrapper_and_androidruntimeplugin.html">Integrating with Android APIs</a>
|
||||
*/
|
||||
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<String>, 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<String>): 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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user