summaryrefslogtreecommitdiffstats
path: root/platform/android
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/android_input_handler.cpp4
-rw-r--r--platform/android/android_input_handler.h2
-rw-r--r--platform/android/dir_access_jandroid.cpp26
-rw-r--r--platform/android/display_server_android.cpp16
-rw-r--r--platform/android/display_server_android.h3
-rw-r--r--platform/android/export/export_plugin.cpp6
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp26
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt33
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java29
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java24
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java131
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java31
-rw-r--r--platform/android/java_class_wrapper.cpp8
-rw-r--r--platform/android/java_godot_lib_jni.cpp4
-rw-r--r--platform/android/java_godot_lib_jni.h2
-rw-r--r--platform/android/java_godot_wrapper.cpp42
-rw-r--r--platform/android/java_godot_wrapper.h8
-rw-r--r--platform/android/os_android.cpp42
-rw-r--r--platform/android/os_android.h6
-rw-r--r--platform/android/platform_gl.h (renamed from platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java)51
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp27
-rw-r--r--platform/android/plugin/godot_plugin_jni.h1
-rw-r--r--platform/android/tts_android.cpp16
24 files changed, 337 insertions, 203 deletions
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
index f6a0776017..bd194478d9 100644
--- a/platform/android/android_input_handler.cpp
+++ b/platform/android/android_input_handler.cpp
@@ -292,7 +292,7 @@ void AndroidInputHandler::_release_mouse_event_info(bool p_source_mouse_relative
mouse_event_info.valid = false;
}
-void AndroidInputHandler::process_mouse_event(int p_event_action, int p_event_android_buttons_mask, Point2 p_event_pos, Vector2 p_delta, bool p_double_click, bool p_source_mouse_relative) {
+void AndroidInputHandler::process_mouse_event(int p_event_action, int p_event_android_buttons_mask, Point2 p_event_pos, Vector2 p_delta, bool p_double_click, bool p_source_mouse_relative, float p_pressure, Vector2 p_tilt) {
BitField<MouseButtonMask> event_buttons_mask = _android_button_mask_to_godot_button_mask(p_event_android_buttons_mask);
switch (p_event_action) {
case AMOTION_EVENT_ACTION_HOVER_MOVE: // hover move
@@ -349,6 +349,8 @@ void AndroidInputHandler::process_mouse_event(int p_event_action, int p_event_an
hover_prev_pos = p_event_pos;
}
ev->set_button_mask(event_buttons_mask);
+ ev->set_pressure(p_pressure);
+ ev->set_tilt(p_tilt);
Input::get_singleton()->parse_input_event(ev);
} break;
diff --git a/platform/android/android_input_handler.h b/platform/android/android_input_handler.h
index c74c5020e3..78a484cf05 100644
--- a/platform/android/android_input_handler.h
+++ b/platform/android/android_input_handler.h
@@ -96,7 +96,7 @@ private:
void _cancel_all_touch();
public:
- void process_mouse_event(int p_event_action, int p_event_android_buttons_mask, Point2 p_event_pos, Vector2 p_delta, bool p_double_click, bool p_source_mouse_relative);
+ void process_mouse_event(int p_event_action, int p_event_android_buttons_mask, Point2 p_event_pos, Vector2 p_delta, bool p_double_click, bool p_source_mouse_relative, float p_pressure, Vector2 p_tilt);
void process_touch_event(int p_event, int p_pointer, const Vector<TouchPos> &p_points, bool p_double_tap);
void process_magnify(Point2 p_pos, float p_factor);
void process_pan(Point2 p_pos, Vector2 p_delta);
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 6bd09fe00a..d24d3fa389 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -67,7 +67,7 @@ String DirAccessJAndroid::get_next() {
ERR_FAIL_COND_V(id == 0, "");
if (_dir_next) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, "");
+ ERR_FAIL_NULL_V(env, "");
jstring str = (jstring)env->CallObjectMethod(dir_access_handler, _dir_next, get_access_type(), id);
if (!str) {
return "";
@@ -84,7 +84,7 @@ String DirAccessJAndroid::get_next() {
bool DirAccessJAndroid::current_is_dir() const {
if (_dir_is_dir) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
return env->CallBooleanMethod(dir_access_handler, _dir_is_dir, get_access_type(), id);
} else {
return false;
@@ -94,7 +94,7 @@ bool DirAccessJAndroid::current_is_dir() const {
bool DirAccessJAndroid::current_is_hidden() const {
if (_current_is_hidden) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
return env->CallBooleanMethod(dir_access_handler, _current_is_hidden, get_access_type(), id);
}
return false;
@@ -112,7 +112,7 @@ void DirAccessJAndroid::list_dir_end() {
int DirAccessJAndroid::get_drive_count() {
if (_get_drive_count) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, 0);
+ ERR_FAIL_NULL_V(env, 0);
return env->CallIntMethod(dir_access_handler, _get_drive_count, get_access_type());
} else {
return 0;
@@ -122,7 +122,7 @@ int DirAccessJAndroid::get_drive_count() {
String DirAccessJAndroid::get_drive(int p_drive) {
if (_get_drive) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, "");
+ ERR_FAIL_NULL_V(env, "");
jstring j_drive = (jstring)env->CallObjectMethod(dir_access_handler, _get_drive, get_access_type(), p_drive);
if (!j_drive) {
return "";
@@ -191,7 +191,7 @@ String DirAccessJAndroid::get_absolute_path(String p_path) {
bool DirAccessJAndroid::file_exists(String p_file) {
if (_file_exists) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
String path = get_absolute_path(p_file);
jstring j_path = env->NewStringUTF(path.utf8().get_data());
@@ -206,7 +206,7 @@ bool DirAccessJAndroid::file_exists(String p_file) {
bool DirAccessJAndroid::dir_exists(String p_dir) {
if (_dir_exists) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
String path = get_absolute_path(p_dir);
jstring j_path = env->NewStringUTF(path.utf8().get_data());
@@ -226,7 +226,7 @@ Error DirAccessJAndroid::make_dir_recursive(String p_dir) {
if (_make_dir) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, ERR_UNCONFIGURED);
+ ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
String path = get_absolute_path(p_dir);
jstring j_dir = env->NewStringUTF(path.utf8().get_data());
@@ -249,7 +249,7 @@ Error DirAccessJAndroid::make_dir(String p_dir) {
Error DirAccessJAndroid::rename(String p_from, String p_to) {
if (_rename) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, ERR_UNCONFIGURED);
+ ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
String from_path = get_absolute_path(p_from);
jstring j_from = env->NewStringUTF(from_path.utf8().get_data());
@@ -273,7 +273,7 @@ Error DirAccessJAndroid::rename(String p_from, String p_to) {
Error DirAccessJAndroid::remove(String p_name) {
if (_remove) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, ERR_UNCONFIGURED);
+ ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
String path = get_absolute_path(p_name);
jstring j_name = env->NewStringUTF(path.utf8().get_data());
@@ -292,7 +292,7 @@ Error DirAccessJAndroid::remove(String p_name) {
uint64_t DirAccessJAndroid::get_space_left() {
if (_get_space_left) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, 0);
+ ERR_FAIL_NULL_V(env, 0);
return env->CallLongMethod(dir_access_handler, _get_space_left, get_access_type());
} else {
return 0;
@@ -331,7 +331,7 @@ DirAccessJAndroid::~DirAccessJAndroid() {
int DirAccessJAndroid::dir_open(String p_path) {
if (_dir_open) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, 0);
+ ERR_FAIL_NULL_V(env, 0);
String path = get_absolute_path(p_path);
jstring js = env->NewStringUTF(path.utf8().get_data());
@@ -346,7 +346,7 @@ int DirAccessJAndroid::dir_open(String p_path) {
void DirAccessJAndroid::dir_close(int p_id) {
if (_dir_close) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
env->CallVoidMethod(dir_access_handler, _dir_close, get_access_type(), p_id);
}
}
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index f02b292868..11c0945ce0 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -111,6 +111,20 @@ void DisplayServerAndroid::tts_stop() {
TTS_Android::stop();
}
+bool DisplayServerAndroid::is_dark_mode_supported() const {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_NULL_V(godot_java, false);
+
+ return godot_java->is_dark_mode_supported();
+}
+
+bool DisplayServerAndroid::is_dark_mode() const {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_NULL_V(godot_java, false);
+
+ return godot_java->is_dark_mode();
+}
+
void DisplayServerAndroid::clipboard_set(const String &p_text) {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
ERR_FAIL_NULL(godot_java);
@@ -543,7 +557,7 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3") {
- RasterizerGLES3::make_current();
+ RasterizerGLES3::make_current(false);
}
#endif
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index e0ad2cb916..54912212dc 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -103,6 +103,9 @@ public:
virtual void tts_resume() override;
virtual void tts_stop() override;
+ virtual bool is_dark_mode_supported() const override;
+ virtual bool is_dark_mode() const override;
+
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
virtual bool clipboard_has() const override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 15ded5c61e..aeaa7b9ce7 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1832,7 +1832,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname", false, true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "com.example.$genname", false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "package/app_category", PROPERTY_HINT_ENUM, "Accessibility,Audio,Game,Image,Maps,News,Productivity,Social,Video"), APP_CATEGORY_GAME));
@@ -2635,6 +2635,8 @@ void EditorExportPlatformAndroid::_clear_assets_directory() {
if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) {
print_verbose("Clearing APK assets directory...");
Ref<DirAccess> da_assets = DirAccess::open(APK_ASSETS_DIRECTORY);
+ ERR_FAIL_COND(da_assets.is_null());
+
da_assets->erase_contents_recursive();
da_res->remove(APK_ASSETS_DIRECTORY);
}
@@ -2643,6 +2645,8 @@ void EditorExportPlatformAndroid::_clear_assets_directory() {
if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) {
print_verbose("Clearing AAB assets directory...");
Ref<DirAccess> da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY);
+ ERR_FAIL_COND(da_assets.is_null());
+
da_assets->erase_contents_recursive();
da_res->remove(AAB_ASSETS_DIRECTORY);
}
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index a1865fb1d4..beea73fd61 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -69,7 +69,7 @@ Error FileAccessFilesystemJAndroid::open_internal(const String &p_path, int p_mo
if (_file_open) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, ERR_UNCONFIGURED);
+ ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
String path = fix_path(p_path).simplify_path();
jstring js = env->NewStringUTF(path.utf8().get_data());
@@ -103,7 +103,7 @@ void FileAccessFilesystemJAndroid::_close() {
if (_file_close) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
env->CallVoidMethod(file_access_handler, _file_close, id);
}
id = 0;
@@ -116,7 +116,7 @@ bool FileAccessFilesystemJAndroid::is_open() const {
void FileAccessFilesystemJAndroid::seek(uint64_t p_position) {
if (_file_seek) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
env->CallVoidMethod(file_access_handler, _file_seek, id, p_position);
}
@@ -125,7 +125,7 @@ void FileAccessFilesystemJAndroid::seek(uint64_t p_position) {
void FileAccessFilesystemJAndroid::seek_end(int64_t p_position) {
if (_file_seek_end) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
env->CallVoidMethod(file_access_handler, _file_seek_end, id, p_position);
}
@@ -134,7 +134,7 @@ void FileAccessFilesystemJAndroid::seek_end(int64_t p_position) {
uint64_t FileAccessFilesystemJAndroid::get_position() const {
if (_file_tell) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, 0);
+ ERR_FAIL_NULL_V(env, 0);
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
return env->CallLongMethod(file_access_handler, _file_tell, id);
} else {
@@ -145,7 +145,7 @@ uint64_t FileAccessFilesystemJAndroid::get_position() const {
uint64_t FileAccessFilesystemJAndroid::get_length() const {
if (_file_get_size) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, 0);
+ ERR_FAIL_NULL_V(env, 0);
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
return env->CallLongMethod(file_access_handler, _file_get_size, id);
} else {
@@ -156,7 +156,7 @@ uint64_t FileAccessFilesystemJAndroid::get_length() const {
bool FileAccessFilesystemJAndroid::eof_reached() const {
if (_file_eof) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
ERR_FAIL_COND_V_MSG(!is_open(), false, "File must be opened before use.");
return env->CallBooleanMethod(file_access_handler, _file_eof, id);
} else {
@@ -169,7 +169,7 @@ void FileAccessFilesystemJAndroid::_set_eof(bool eof) {
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
env->CallVoidMethod(file_access_handler, _file_set_eof, id, eof);
}
}
@@ -235,7 +235,7 @@ uint64_t FileAccessFilesystemJAndroid::get_buffer(uint8_t *p_dst, uint64_t p_len
}
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, 0);
+ ERR_FAIL_NULL_V(env, 0);
jobject j_buffer = env->NewDirectByteBuffer(p_dst, p_length);
int length = env->CallIntMethod(file_access_handler, _file_read, id, j_buffer);
@@ -258,7 +258,7 @@ void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p
}
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
jobject j_buffer = env->NewDirectByteBuffer((void *)p_src, p_length);
env->CallVoidMethod(file_access_handler, _file_write, id, j_buffer);
@@ -276,7 +276,7 @@ Error FileAccessFilesystemJAndroid::get_error() const {
void FileAccessFilesystemJAndroid::flush() {
if (_file_flush) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
env->CallVoidMethod(file_access_handler, _file_flush, id);
}
@@ -285,7 +285,7 @@ void FileAccessFilesystemJAndroid::flush() {
bool FileAccessFilesystemJAndroid::file_exists(const String &p_path) {
if (_file_exists) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
String path = fix_path(p_path).simplify_path();
jstring js = env->NewStringUTF(path.utf8().get_data());
@@ -300,7 +300,7 @@ bool FileAccessFilesystemJAndroid::file_exists(const String &p_path) {
uint64_t FileAccessFilesystemJAndroid::_get_modified_time(const String &p_file) {
if (_file_last_modified) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
String path = fix_path(p_file).simplify_path();
jstring js = env->NewStringUTF(path.utf8().get_data());
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 9c1165bf8a..0e111d5247 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -35,6 +35,7 @@ import android.app.Activity
import android.app.AlertDialog
import android.content.*
import android.content.pm.PackageManager
+import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Rect
import android.hardware.Sensor
@@ -694,6 +695,25 @@ class Godot(private val context: Context) : SensorEventListener {
}
}
+ /**
+ * Returns true if dark mode is supported, false otherwise.
+ */
+ @Keep
+ private fun isDarkModeSupported(): Boolean {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+ }
+
+ /**
+ * Returns true if dark mode is supported and enabled, false otherwise.
+ */
+ @Keep
+ private fun isDarkMode(): Boolean {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
+ }
+ return false
+ }
+
fun hasClipboard(): Boolean {
return mClipboard.hasPrimaryClip()
}
@@ -907,6 +927,19 @@ class Godot(private val context: Context) : SensorEventListener {
return PermissionsUtil.getGrantedPermissions(getActivity())
}
+ /**
+ * Get the list of gdextension modules to register.
+ */
+ @Keep
+ private fun getGDExtensionConfigFiles(): Array<String> {
+ val configFiles = mutableSetOf<String>()
+ for (plugin in pluginRegistry.allPlugins) {
+ configFiles.addAll(plugin.pluginGDExtensionLibrariesPaths)
+ }
+
+ return configFiles.toTypedArray()
+ }
+
@Keep
private fun getCACertificates(): String {
return GodotNetUtils.getCACertificates()
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index b9ecd6971d..fee50e93c2 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -114,7 +114,7 @@ public class GodotLib {
/**
* Dispatch mouse events
*/
- public static native void dispatchMouseEvent(int event, int buttonMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative);
+ public static native void dispatchMouseEvent(int event, int buttonMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY);
public static native void magnify(float x, float y, float factor);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index a7064dfc1d..3070a8a207 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -33,6 +33,7 @@ package org.godotengine.godot.input;
import org.godotengine.godot.*;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.Handler;
import android.os.Message;
import android.text.InputFilter;
@@ -209,6 +210,13 @@ public class GodotEditText extends EditText {
mRenderView.getView().requestFocus();
}
+ // When a hardware keyboard is connected, all key events come through so we can route them
+ // directly to the engine.
+ // This is not the case when using a soft keyboard, requiring extra processing from this class.
+ if (hasHardwareKeyboard()) {
+ return mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent);
+ }
+
// pass event to godot in special cases
if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent)) {
return true;
@@ -219,6 +227,13 @@ public class GodotEditText extends EditText {
@Override
public boolean onKeyUp(int keyCode, KeyEvent keyEvent) {
+ // When a hardware keyboard is connected, all key events come through so we can route them
+ // directly to the engine.
+ // This is not the case when using a soft keyboard, requiring extra processing from this class.
+ if (hasHardwareKeyboard()) {
+ return mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent);
+ }
+
if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent)) {
return true;
} else {
@@ -235,10 +250,20 @@ public class GodotEditText extends EditText {
isModifiedKey;
}
+ boolean hasHardwareKeyboard() {
+ Configuration config = getResources().getConfiguration();
+ return config.keyboard != Configuration.KEYBOARD_NOKEYS &&
+ config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
+ }
+
// ===========================================================
// Methods
// ===========================================================
public void showKeyboard(String p_existing_text, VirtualKeyboardType p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+ if (hasHardwareKeyboard()) {
+ return;
+ }
+
int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;
if (p_cursor_start == -1) { // cursor position not given
this.mOriginText = p_existing_text;
@@ -262,6 +287,10 @@ public class GodotEditText extends EditText {
}
public void hideKeyboard() {
+ if (hasHardwareKeyboard()) {
+ return;
+ }
+
final Message msg = new Message();
msg.what = HANDLER_CLOSE_IME_KEYBOARD;
msg.obj = this;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index 1a25be0460..c8b222254e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -433,7 +433,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
private static boolean isMouseEvent(int eventSource) {
- boolean mouseSource = ((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) || ((eventSource & (InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_STYLUS)) == InputDevice.SOURCE_STYLUS);
+ boolean mouseSource = ((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) || ((eventSource & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mouseSource = mouseSource || ((eventSource & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE);
}
@@ -470,13 +470,27 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
final float y = event.getY();
final int buttonsMask = event.getButtonState();
+ final float pressure = event.getPressure();
+
+ // Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
+ final float orientation = event.getOrientation();
+
+ // Tilt is zero is perpendicular to the screen and pi/2 is flat on the surface.
+ final float tilt = event.getAxisValue(MotionEvent.AXIS_TILT);
+
+ float tiltMult = (float)Math.sin(tilt);
+
+ // To be consistent with expected tilt.
+ final float tiltX = (float)-Math.sin(orientation) * tiltMult;
+ final float tiltY = (float)Math.cos(orientation) * tiltMult;
+
final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
boolean sourceMouseRelative = false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
sourceMouseRelative = event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE);
}
- return handleMouseEvent(eventAction, buttonsMask, x, y, horizontalFactor, verticalFactor, false, sourceMouseRelative);
+ return handleMouseEvent(eventAction, buttonsMask, x, y, horizontalFactor, verticalFactor, false, sourceMouseRelative, pressure, tiltX, tiltY);
}
static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y) {
@@ -484,6 +498,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative) {
+ return handleMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, 1, 0, 0);
+ }
+
+ static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) {
// Fix the buttonsMask
switch (eventAction) {
case MotionEvent.ACTION_CANCEL:
@@ -511,7 +529,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_SCROLL: {
- GodotLib.dispatchMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative);
+ GodotLib.dispatchMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY);
return true;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
index 48aa231c7a..7f3a3ac7a3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -57,27 +57,28 @@ import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
- * Base class for the Godot Android plugins.
+ * Base class for Godot Android plugins.
* <p>
- * A Godot Android plugin is a regular Android library packaged as an aar archive file with the following caveats:
+ * A Godot Android plugin is an Android library with the following requirements:
* <p>
- * - The library must have a dependency on the Godot Android library (godot-lib.aar).
- * A stable version is available for each release.
+ * - The plugin must have a dependency on the Godot Android library: `implementation "org.godotengine:godot:<godotLibVersion>"`
+ * <p>
+ * - The plugin must include a <meta-data> tag in its Android manifest with the following format:
+ * <meta-data android:name="org.godotengine.plugin.v2.[PluginName]" android:value="[plugin.init.ClassFullName]" />
* <p>
- * - The library must include a <meta-data> tag in its manifest file setup as follow:
- * <meta-data android:name="org.godotengine.plugin.v1.[PluginName]" android:value="[plugin.init.ClassFullName]" />
* Where:
+ * <p>
* - 'PluginName' is the name of the plugin.
- * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class
+ * <p>
+ * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin init class
* extending {@link GodotPlugin}.
+ * <p>
+ * A Godot Android plugin can also define and provide c/c++ gdextension libraries, which will be
+ * automatically bundled by the aar build system.
+ * GDExtension ('*.gdextension') config files must be located in the project 'assets' directory and
+ * their paths specified by {@link GodotPlugin#getPluginGDExtensionLibrariesPaths()}.
*
- * A plugin can also define and provide c/c++ gdextension libraries and nativescripts for the target
- * app/game to leverage.
- * The shared library for the gdextension library will be automatically bundled by the aar build
- * system.
- * Godot '*.gdextension' resource files must however be manually defined in the project
- * 'assets' directory. The recommended path for these resources in the 'assets' directory should be:
- * 'godot/plugin/v1/[PluginName]/'
+ * @see <a href="https://docs.godotengine.org/en/stable/tutorials/platform/android/index.html">Android plugins</a>
*/
public abstract class GodotPlugin {
private static final String TAG = GodotPlugin.class.getSimpleName();
@@ -85,6 +86,10 @@ public abstract class GodotPlugin {
private final Godot godot;
private final ConcurrentHashMap<String, SignalInfo> registeredSignals = new ConcurrentHashMap<>();
+ /**
+ * Base constructor passing a {@link Godot} instance through which the plugin can access Godot's
+ * APIs and lifecycle events.
+ */
public GodotPlugin(Godot godot) {
this.godot = godot;
}
@@ -97,7 +102,7 @@ public abstract class GodotPlugin {
}
/**
- * Provides access to the underlying {@link Activity}.
+ * Provides access to the hosting {@link Activity}.
*/
@Nullable
protected Activity getActivity() {
@@ -106,33 +111,16 @@ public abstract class GodotPlugin {
/**
* Register the plugin with Godot native code.
- *
- * This method is invoked on the render thread.
+ * <p>
+ * This method is invoked by the Godot Engine on the render thread.
*/
public final void onRegisterPluginWithGodotNative() {
registeredSignals.putAll(
- registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(),
- getPluginGDExtensionLibrariesPaths()));
- }
-
- /**
- * Register the plugin with Godot native code.
- *
- * This method must be invoked on the render thread.
- */
- public static void registerPluginWithGodotNative(Object pluginObject,
- GodotPluginInfoProvider pluginInfoProvider) {
- registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(),
- Collections.emptyList(), pluginInfoProvider.getPluginSignals(),
- pluginInfoProvider.getPluginGDExtensionLibrariesPaths());
-
- // Notify that registration is complete.
- pluginInfoProvider.onPluginRegistered();
+ registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals()));
}
private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
- String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals,
- Set<String> pluginGDExtensionLibrariesPaths) {
+ String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals) {
nativeRegisterSingleton(pluginName, pluginObject);
Set<Method> filteredMethods = new HashSet<>();
@@ -176,23 +164,18 @@ public abstract class GodotPlugin {
registeredSignals.put(signalName, signalInfo);
}
- // Get the list of gdextension libraries to register.
- if (!pluginGDExtensionLibrariesPaths.isEmpty()) {
- nativeRegisterGDExtensionLibraries(pluginGDExtensionLibrariesPaths.toArray(new String[0]));
- }
-
return registeredSignals;
}
/**
- * Invoked once during the Godot Android initialization process after creation of the
+ * Invoked once during the initialization process after creation of the
* {@link org.godotengine.godot.GodotRenderView} view.
* <p>
- * The plugin can return a non-null {@link View} layout in order to add it to the Godot view
+ * The plugin can return a non-null {@link View} layout which will be added to the Godot view
* hierarchy.
- *
- * Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind
- * the main Godot view.
+ * <p>
+ * Use {@link GodotPlugin#shouldBeOnTop()} to specify whether the plugin's {@link View} should
+ * be added on top or behind the main Godot view.
*
* @see Activity#onCreate(Bundle)
* @return the plugin's view to be included; null if no views should be included.
@@ -235,44 +218,52 @@ public abstract class GodotPlugin {
public boolean onMainBackPressed() { return false; }
/**
- * Invoked on the render thread when the Godot setup is complete.
+ * Invoked on the render thread when set up of the Godot engine is complete.
+ * <p>
+ * This is invoked before {@link GodotPlugin#onGodotMainLoopStarted()}.
*/
public void onGodotSetupCompleted() {}
/**
* Invoked on the render thread when the Godot main loop has started.
+ *
+ * This is invoked after {@link GodotPlugin#onGodotSetupCompleted()}.
*/
public void onGodotMainLoopStarted() {}
/**
- * Invoked once per frame on the GL thread after the frame is drawn.
+ * When using the OpenGL renderer, this is invoked once per frame on the GL thread after the
+ * frame is drawn.
*/
public void onGLDrawFrame(GL10 gl) {}
/**
- * Called on the GL thread after the surface is created and whenever the OpenGL ES surface size
- * changes.
+ * When using the OpenGL renderer, this is called on the GL thread after the surface is created
+ * and whenever the OpenGL ES surface size changes.
*/
public void onGLSurfaceChanged(GL10 gl, int width, int height) {}
/**
- * Called on the GL thread when the surface is created or recreated.
+ * When using the OpenGL renderer, this is called on the GL thread when the surface is created
+ * or recreated.
*/
public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {}
/**
- * Invoked once per frame on the Vulkan thread after the frame is drawn.
+ * When using the Vulkan renderer, this is invoked once per frame on the Vulkan thread after
+ * the frame is drawn.
*/
public void onVkDrawFrame() {}
/**
- * Called on the Vulkan thread after the surface is created and whenever the surface size
- * changes.
+ * When using the Vulkan renderer, this is called on the Vulkan thread after the surface is
+ * created and whenever the surface size changes.
*/
public void onVkSurfaceChanged(Surface surface, int width, int height) {}
/**
- * Called on the Vulkan thread when the surface is created or recreated.
+ * When using the Vulkan renderer, this is called on the Vulkan thread when the surface is
+ * created or recreated.
*/
public void onVkSurfaceCreated(Surface surface) {}
@@ -287,7 +278,7 @@ public abstract class GodotPlugin {
/**
* Returns the list of methods to be exposed to Godot.
*
- * @deprecated Used the {@link UsedByGodot} annotation instead.
+ * @deprecated Use the {@link UsedByGodot} annotation instead.
*/
@NonNull
@Deprecated
@@ -304,19 +295,19 @@ public abstract class GodotPlugin {
}
/**
- * Returns the paths for the plugin's gdextension libraries.
- *
- * The paths must be relative to the 'assets' directory and point to a '*.gdextension' file.
+ * Returns the paths for the plugin's gdextension libraries (if any).
+ * <p>
+ * Each returned path must be relative to the 'assets' directory and point to a '*.gdextension' file.
*/
@NonNull
- protected Set<String> getPluginGDExtensionLibrariesPaths() {
+ public Set<String> getPluginGDExtensionLibrariesPaths() {
return Collections.emptySet();
}
/**
- * Returns whether the plugin's {@link View} returned in onMainCreate() should be placed on
- * top of the main Godot view.
- *
+ * Returns whether the plugin's {@link View} returned in
+ * {@link GodotPlugin#onMainCreate(Activity)} should be placed on top of the main Godot view.
+ * <p>
* Returning false causes the plugin's {@link View} to be placed behind, which can be useful
* when used with transparency in order to let the Godot view handle inputs.
*/
@@ -359,7 +350,7 @@ public abstract class GodotPlugin {
}
emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs);
} catch (IllegalArgumentException exception) {
- Log.w(TAG, exception.getMessage());
+ Log.w(TAG, exception);
if (BuildConfig.DEBUG) {
throw exception;
}
@@ -368,7 +359,7 @@ public abstract class GodotPlugin {
/**
* Emit a Godot signal.
- * @param godot
+ * @param godot Godot instance
* @param pluginName Name of the Godot plugin the signal will be emitted from. The plugin must already be registered with the Godot engine.
* @param signalInfo Information about the signal to emit.
* @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the given {@link SignalInfo} parameter.
@@ -397,7 +388,7 @@ public abstract class GodotPlugin {
godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs));
} catch (IllegalArgumentException exception) {
- Log.w(TAG, exception.getMessage());
+ Log.w(TAG, exception);
if (BuildConfig.DEBUG) {
throw exception;
}
@@ -420,13 +411,7 @@ public abstract class GodotPlugin {
private static native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params);
/**
- * Used to register gdextension libraries bundled by the plugin.
- * @param gdextensionPaths Paths to the libraries relative to the 'assets' directory.
- */
- private static native void nativeRegisterGDExtensionLibraries(String[] gdextensionPaths);
-
- /**
- * Used to complete registration of the {@link GodotPlugin} instance's methods.
+ * Used to complete registration of the {@link GodotPlugin} instance's signals.
* @param pluginName Name of the plugin
* @param signalName Name of the signal to register
* @param signalParamTypes Signal parameters types
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
index c2428de2e1..d338b72441 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
@@ -52,7 +52,14 @@ import java.util.concurrent.ConcurrentHashMap;
public final class GodotPluginRegistry {
private static final String TAG = GodotPluginRegistry.class.getSimpleName();
+ /**
+ * Prefix used for version 1 of the Godot plugin, mostly compatible with Godot 3.x
+ */
private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1.";
+ /**
+ * Prefix used for version 2 of the Godot plugin, compatible with Godot 4.2+
+ */
+ private static final String GODOT_PLUGIN_V2_NAME_PREFIX = "org.godotengine.plugin.v2.";
private static GodotPluginRegistry instance;
private final ConcurrentHashMap<String, GodotPlugin> registry;
@@ -123,11 +130,17 @@ public final class GodotPluginRegistry {
return;
}
- int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length();
for (String metaDataName : metaData.keySet()) {
// Parse the meta-data looking for entry with the Godot plugin name prefix.
- if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) {
- String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim();
+ String pluginName = null;
+ if (metaDataName.startsWith(GODOT_PLUGIN_V2_NAME_PREFIX)) {
+ pluginName = metaDataName.substring(GODOT_PLUGIN_V2_NAME_PREFIX.length()).trim();
+ } else if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) {
+ pluginName = metaDataName.substring(GODOT_PLUGIN_V1_NAME_PREFIX.length()).trim();
+ Log.w(TAG, "Godot v1 plugin are deprecated in Godot 4.2 and higher: " + pluginName);
+ }
+
+ if (!TextUtils.isEmpty(pluginName)) {
Log.i(TAG, "Initializing Godot plugin " + pluginName);
// Retrieve the plugin class full name.
@@ -149,15 +162,7 @@ public final class GodotPluginRegistry {
}
registry.put(pluginName, pluginHandle);
Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName());
- } catch (ClassNotFoundException e) {
- Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
- } catch (IllegalAccessException e) {
- Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
- } catch (InstantiationException e) {
- Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
- } catch (NoSuchMethodException e) {
- Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
- } catch (InvocationTargetException e) {
+ } catch (Exception e) {
Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
}
} else {
@@ -165,7 +170,7 @@ public final class GodotPluginRegistry {
}
}
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (Exception e) {
Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e);
}
}
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index edc934e927..d6455cbf1c 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -50,14 +50,14 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
}
int pc = E.param_types.size();
- if (pc > p_argcount) {
+ if (p_argcount < pc) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = pc;
+ r_error.expected = pc;
continue;
}
- if (pc < p_argcount) {
+ if (p_argcount > pc) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.argument = pc;
+ r_error.expected = pc;
continue;
}
uint32_t *ptypes = E.param_types.ptrw();
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 74605e3377..50075ed3f5 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -274,12 +274,12 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
}
// Called on the UI thread
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative, jfloat p_pressure, jfloat p_tilt_x, jfloat p_tilt_y) {
if (step.get() <= 0) {
return;
}
- input_handler->process_mouse_event(p_event_type, p_button_mask, Point2(p_x, p_y), Vector2(p_delta_x, p_delta_y), p_double_click, p_source_mouse_relative);
+ input_handler->process_mouse_event(p_event_type, p_button_mask, Point2(p_x, p_y), Vector2(p_delta_x, p_delta_y), p_double_click, p_source_mouse_relative, p_pressure, Vector2(p_tilt_x, p_tilt_y));
}
// Called on the UI thread
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index ee6a19034c..1ddda6c1c8 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -45,7 +45,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *env, jclass clazz, jint event, jint id, jint pos);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative, jfloat p_pressure, jfloat p_tilt_x, jfloat p_tilt_y);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jboolean p_double_tap);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnify(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_factor);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_pan(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y);
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 79ba2528ba..1703179b8e 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -62,6 +62,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_finish = p_env->GetMethodID(godot_class, "forceQuit", "(I)Z");
_set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V");
_alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V");
+ _is_dark_mode_supported = p_env->GetMethodID(godot_class, "isDarkModeSupported", "()Z");
+ _is_dark_mode = p_env->GetMethodID(godot_class, "isDarkMode", "()Z");
_get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
_has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
@@ -79,6 +81,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;)V");
_end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V");
_dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V");
+ _get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -171,6 +174,26 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
}
}
+bool GodotJavaWrapper::is_dark_mode_supported() {
+ if (_is_dark_mode_supported) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+ return env->CallBooleanMethod(godot_instance, _is_dark_mode_supported);
+ } else {
+ return false;
+ }
+}
+
+bool GodotJavaWrapper::is_dark_mode() {
+ if (_is_dark_mode) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+ return env->CallBooleanMethod(godot_instance, _is_dark_mode);
+ } else {
+ return false;
+ }
+}
+
bool GodotJavaWrapper::has_get_clipboard() {
return _get_clipboard != nullptr;
}
@@ -264,6 +287,25 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const {
return permissions_list;
}
+Vector<String> GodotJavaWrapper::get_gdextension_list_config_file() const {
+ Vector<String> config_file_list;
+ if (_get_gdextension_list_config_file) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, config_file_list);
+ jobject config_file_list_object = env->CallObjectMethod(godot_instance, _get_gdextension_list_config_file);
+ jobjectArray *arr = reinterpret_cast<jobjectArray *>(&config_file_list_object);
+
+ jsize len = env->GetArrayLength(*arr);
+ for (int i = 0; i < len; i++) {
+ jstring j_config_file = (jstring)env->GetObjectArrayElement(*arr, i);
+ String config_file = jstring_to_string(j_config_file, env);
+ config_file_list.push_back(config_file);
+ env->DeleteLocalRef(j_config_file);
+ }
+ }
+ return config_file_list;
+}
+
String GodotJavaWrapper::get_ca_certificates() const {
if (_get_ca_certificates) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index ba42d5dccd..f427a2937c 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -53,12 +53,15 @@ private:
jmethodID _finish = nullptr;
jmethodID _set_keep_screen_on = nullptr;
jmethodID _alert = nullptr;
+ jmethodID _is_dark_mode_supported = nullptr;
+ jmethodID _is_dark_mode = nullptr;
jmethodID _get_clipboard = nullptr;
jmethodID _set_clipboard = nullptr;
jmethodID _has_clipboard = nullptr;
jmethodID _request_permission = nullptr;
jmethodID _request_permissions = nullptr;
jmethodID _get_granted_permissions = nullptr;
+ jmethodID _get_gdextension_list_config_file = nullptr;
jmethodID _get_ca_certificates = nullptr;
jmethodID _init_input_devices = nullptr;
jmethodID _vibrate = nullptr;
@@ -85,6 +88,8 @@ public:
bool force_quit(JNIEnv *p_env = nullptr, int p_instance_id = 0);
void set_keep_screen_on(bool p_enabled);
void alert(const String &p_message, const String &p_title);
+ bool is_dark_mode_supported();
+ bool is_dark_mode();
bool has_get_clipboard();
String get_clipboard();
bool has_set_clipboard();
@@ -102,6 +107,9 @@ public:
void begin_benchmark_measure(const String &p_label);
void end_benchmark_measure(const String &p_label);
void dump_benchmark(const String &benchmark_file);
+
+ // Return the list of gdextensions config file.
+ Vector<String> get_gdextension_list_config_file() const;
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index c040d8c4c6..8f80516a9f 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -39,6 +39,8 @@
#include "net_socket_android.h"
#include "core/config/project_settings.h"
+#include "core/extension/gdextension_manager.h"
+#include "core/io/xml_parser.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "main/main.h"
@@ -162,11 +164,39 @@ Vector<String> OS_Android::get_granted_permissions() const {
Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String path = p_path;
+ bool so_file_exists = true;
if (!FileAccess::exists(path)) {
path = p_path.get_file();
+ so_file_exists = false;
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
+ if (!p_library_handle && so_file_exists) {
+ // The library may be on the sdcard and thus inaccessible. Try to copy it to the internal
+ // directory.
+ uint64_t so_modified_time = FileAccess::get_modified_time(p_path);
+ String dynamic_library_path = get_dynamic_libraries_path().path_join(String::num_uint64(so_modified_time));
+ String internal_path = dynamic_library_path.path_join(p_path.get_file());
+
+ bool internal_so_file_exists = FileAccess::exists(internal_path);
+ if (!internal_so_file_exists) {
+ Ref<DirAccess> da_ref = DirAccess::create_for_path(p_path);
+ if (da_ref.is_valid()) {
+ Error create_dir_result = da_ref->make_dir_recursive(dynamic_library_path);
+ if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
+ internal_so_file_exists = da_ref->copy(path, internal_path) == OK;
+ }
+ }
+ }
+
+ if (internal_so_file_exists) {
+ p_library_handle = dlopen(internal_path.utf8().get_data(), RTLD_NOW);
+ if (p_library_handle) {
+ path = internal_path;
+ }
+ }
+ }
+
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) {
@@ -584,6 +614,10 @@ String OS_Android::get_user_data_dir() const {
return ".";
}
+String OS_Android::get_dynamic_libraries_path() const {
+ return get_cache_path().path_join("dynamic_libraries");
+}
+
String OS_Android::get_cache_path() const {
if (!cache_dir_cache.is_empty()) {
return cache_dir_cache;
@@ -791,5 +825,13 @@ Error OS_Android::setup_remote_filesystem(const String &p_server_host, int p_por
return err;
}
+void OS_Android::load_platform_gdextensions() const {
+ Vector<String> extension_list_config_file = godot_java->get_gdextension_list_config_file();
+ for (String config_file_path : extension_list_config_file) {
+ GDExtensionManager::LoadStatus err = GDExtensionManager::get_singleton()->load_extension(config_file_path);
+ ERR_CONTINUE_MSG(err == GDExtensionManager::LOAD_STATUS_FAILED, "Error loading platform extension: " + config_file_path);
+ }
+}
+
OS_Android::~OS_Android() {
}
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index abcc412588..f88f3e0518 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -169,9 +169,15 @@ public:
virtual void benchmark_end_measure(const String &p_what) override;
virtual void benchmark_dump() override;
+ virtual void load_platform_gdextensions() const override;
+
virtual bool _check_internal_feature_support(const String &p_feature) override;
OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);
~OS_Android();
+
+private:
+ // Location where we relocate external dynamic libraries to make them accessible.
+ String get_dynamic_libraries_path() const;
};
#endif // OS_ANDROID_H
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java b/platform/android/platform_gl.h
index 63999a8321..af6edb103d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java
+++ b/platform/android/platform_gl.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* GodotPluginInfoProvider.java */
+/* platform_gl.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,45 +28,16 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-package org.godotengine.godot.plugin;
+#ifndef PLATFORM_GL_H
+#define PLATFORM_GL_H
-import androidx.annotation.NonNull;
+#ifndef GLES_API_ENABLED
+#define GLES_API_ENABLED // Allow using GLES.
+#endif
-import java.util.Collections;
-import java.util.Set;
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES3/gl3.h>
+#include <GLES3/gl3ext.h>
-/**
- * Provides the set of information expected from a Godot plugin.
- */
-public interface GodotPluginInfoProvider {
- /**
- * Returns the name of the plugin.
- */
- @NonNull
- String getPluginName();
-
- /**
- * Returns the list of signals to be exposed to Godot.
- */
- @NonNull
- default Set<SignalInfo> getPluginSignals() {
- return Collections.emptySet();
- }
-
- /**
- * Returns the paths for the plugin's gdextension libraries (if any).
- *
- * The paths must be relative to the 'assets' directory and point to a '*.gdextension' file.
- */
- @NonNull
- default Set<String> getPluginGDExtensionLibrariesPaths() {
- return Collections.emptySet();
- }
-
- /**
- * This is invoked on the render thread when the plugin described by this instance has been
- * registered.
- */
- default void onPluginRegistered() {
- }
-}
+#endif // PLATFORM_GL_H
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index 5d48c4e248..fd60ba4ae7 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -129,31 +129,4 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS
singleton->emit_signalp(StringName(signal_name), args, count);
}
-
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDExtensionLibraries(JNIEnv *env, jclass clazz, jobjectArray gdextension_paths) {
- int gdextension_count = env->GetArrayLength(gdextension_paths);
- if (gdextension_count == 0) {
- return;
- }
-
- // Retrieve the current list of gdextension libraries.
- Array singletons;
- if (ProjectSettings::get_singleton()->has_setting("gdextension/singletons")) {
- singletons = GLOBAL_GET("gdextension/singletons");
- }
-
- // Insert the libraries provided by the plugin
- for (int i = 0; i < gdextension_count; i++) {
- jstring relative_path = (jstring)env->GetObjectArrayElement(gdextension_paths, i);
-
- String path = "res://" + jstring_to_string(relative_path, env);
- if (!singletons.has(path)) {
- singletons.push_back(path);
- }
- env->DeleteLocalRef(relative_path);
- }
-
- // Insert the updated list back into project settings.
- ProjectSettings::get_singleton()->set("gdextension/singletons", singletons);
-}
}
diff --git a/platform/android/plugin/godot_plugin_jni.h b/platform/android/plugin/godot_plugin_jni.h
index 36a992246d..baa29a79ea 100644
--- a/platform/android/plugin/godot_plugin_jni.h
+++ b/platform/android/plugin/godot_plugin_jni.h
@@ -39,7 +39,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args);
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types);
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDExtensionLibraries(JNIEnv *env, jclass clazz, jobjectArray gdextension_paths);
}
#endif // GODOT_PLUGIN_JNI_H
diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp
index aef59c2584..93517d8045 100644
--- a/platform/android/tts_android.cpp
+++ b/platform/android/tts_android.cpp
@@ -54,7 +54,7 @@ void TTS_Android::setup(jobject p_tts) {
bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
if (tts_enabled) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
tts = env->NewGlobalRef(p_tts);
@@ -103,7 +103,7 @@ bool TTS_Android::is_speaking() {
if (_is_speaking) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
return env->CallBooleanMethod(tts, _is_speaking);
} else {
return false;
@@ -115,7 +115,7 @@ bool TTS_Android::is_paused() {
if (_is_paused) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, false);
+ ERR_FAIL_NULL_V(env, false);
return env->CallBooleanMethod(tts, _is_paused);
} else {
return false;
@@ -127,7 +127,7 @@ Array TTS_Android::get_voices() {
Array list;
if (_get_voices) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND_V(env == nullptr, list);
+ ERR_FAIL_NULL_V(env, list);
jobject voices_object = env->CallObjectMethod(tts, _get_voices);
jobjectArray *arr = reinterpret_cast<jobjectArray *>(&voices_object);
@@ -165,7 +165,7 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum
if (_speak) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
jstring jStrT = env->NewStringUTF(p_text.utf8().get_data());
jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data());
@@ -178,7 +178,7 @@ void TTS_Android::pause() {
if (_pause_speaking) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
env->CallVoidMethod(tts, _pause_speaking);
}
}
@@ -188,7 +188,7 @@ void TTS_Android::resume() {
if (_resume_speaking) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
env->CallVoidMethod(tts, _resume_speaking);
}
}
@@ -203,7 +203,7 @@ void TTS_Android::stop() {
if (_stop_speaking) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
+ ERR_FAIL_NULL(env);
env->CallVoidMethod(tts, _stop_speaking);
}
}