diff options
Diffstat (limited to 'platform/android')
21 files changed, 254 insertions, 230 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/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 15ded5c61e..661f7cbc80 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)); 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..e115494cfd 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -907,6 +907,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/GodotPluginInfoProvider.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java deleted file mode 100644 index 63999a8321..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java +++ /dev/null @@ -1,72 +0,0 @@ -/**************************************************************************/ -/* GodotPluginInfoProvider.java */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -package org.godotengine.godot.plugin; - -import androidx.annotation.NonNull; - -import java.util.Collections; -import java.util.Set; - -/** - * 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() { - } -} 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_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..a01a74f1fd 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -79,6 +79,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() { @@ -264,6 +265,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..2ce756807f 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -59,6 +59,7 @@ private: 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; @@ -102,6 +103,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/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); } } |