diff options
Diffstat (limited to 'platform/android')
25 files changed, 363 insertions, 45 deletions
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index b1481ebf7b..e21a331ab9 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -209,8 +209,6 @@ class JavaClassWrapper : public Object { #ifdef ANDROID_ENABLED RBMap<String, Ref<JavaClass>> class_cache; friend class JavaClass; - jclass activityClass; - jmethodID findClass; jmethodID getDeclaredMethods; jmethodID getFields; jmethodID getParameterTypes; @@ -229,7 +227,6 @@ class JavaClassWrapper : public Object { jmethodID Long_longValue; jmethodID Float_floatValue; jmethodID Double_doubleValue; - jobject classLoader; bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig); #endif diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index a2d1c08168..5b30c392e7 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -241,6 +241,17 @@ public: instance = nullptr; #endif } + + ~JNISingleton() { +#ifdef ANDROID_ENABLED + if (instance) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(instance); + } +#endif + } }; #endif // JNI_SINGLETON_H diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 972a7dbe6a..ab90527bfa 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -321,6 +321,14 @@ void DirAccessJAndroid::setup(jobject p_dir_access_handler) { _current_is_hidden = env->GetMethodID(cls, "isCurrentHidden", "(II)Z"); } +void DirAccessJAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(dir_access_handler); +} + DirAccessJAndroid::DirAccessJAndroid() { } diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index 9aaa78f38c..68578b0fa9 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -89,6 +89,7 @@ public: virtual uint64_t get_space_left() override; static void setup(jobject p_dir_access_handler); + static void terminate(); DirAccessJAndroid(); ~DirAccessJAndroid(); diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 138714634f..6a6d7149ff 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -33,6 +33,7 @@ #include "export_plugin.h" #include "core/os/os.h" +#include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/export/editor_export.h" @@ -46,10 +47,10 @@ void register_android_exporter() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME")); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); - EDITOR_DEF("export/android/debug_keystore", ""); + EDITOR_DEF("export/android/debug_keystore", EditorPaths::get_singleton()->get_debug_keystore_path()); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks")); - EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey"); - EDITOR_DEF("export/android/debug_keystore_pass", "android"); + EDITOR_DEF("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER); + EDITOR_DEF("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD)); EDITOR_DEF("export/android/force_system_user", false); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 7cab5e8d90..3b1a534daf 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -831,14 +831,82 @@ bool EditorExportPlatformAndroid::_uses_vulkan() { void EditorExportPlatformAndroid::_notification(int p_what) { #ifndef ANDROID_ENABLED - if (p_what == NOTIFICATION_POSTINITIALIZE) { - if (EditorExport::get_singleton()) { - EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status)); - } + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + if (EditorExport::get_singleton()) { + EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status)); + } + } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + if (EditorSettings::get_singleton()->check_changed_settings_in_group("export/android")) { + _create_editor_debug_keystore_if_needed(); + } + } break; } #endif } +void EditorExportPlatformAndroid::_create_editor_debug_keystore_if_needed() { + // Check if we have a valid keytool path. + String keytool_path = get_keytool_path(); + if (!FileAccess::exists(keytool_path)) { + return; + } + + // Check if the current editor debug keystore exists. + String editor_debug_keystore = EDITOR_GET("export/android/debug_keystore"); + if (FileAccess::exists(editor_debug_keystore)) { + return; + } + + // Generate the debug keystore. + String keystore_path = EditorPaths::get_singleton()->get_debug_keystore_path(); + String keystores_dir = keystore_path.get_base_dir(); + if (!DirAccess::exists(keystores_dir)) { + Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Error err = dir_access->make_dir_recursive(keystores_dir); + if (err != OK) { + WARN_PRINT(TTR("Error creating keystores directory:") + "\n" + keystores_dir); + return; + } + } + + if (!FileAccess::exists(keystore_path)) { + String output; + List<String> args; + args.push_back("-genkey"); + args.push_back("-keystore"); + args.push_back(keystore_path); + args.push_back("-storepass"); + args.push_back("android"); + args.push_back("-alias"); + args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_USER); + args.push_back("-keypass"); + args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD); + args.push_back("-keyalg"); + args.push_back("RSA"); + args.push_back("-keysize"); + args.push_back("2048"); + args.push_back("-validity"); + args.push_back("10000"); + args.push_back("-dname"); + args.push_back("cn=Godot, ou=Godot Engine, o=Stichting Godot, c=NL"); + Error error = OS::get_singleton()->execute(keytool_path, args, &output, nullptr, true); + print_verbose(output); + if (error != OK) { + WARN_PRINT("Error: Unable to create debug keystore"); + return; + } + } + + // Update the editor settings. + EditorSettings::get_singleton()->set("export/android/debug_keystore", keystore_path); + EditorSettings::get_singleton()->set("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER); + EditorSettings::get_singleton()->set("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD); + print_verbose("Updated editor debug keystore to " + keystore_path); +} + void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { const char **aperms = android_perms; while (*aperms) { @@ -2210,6 +2278,15 @@ String EditorExportPlatformAndroid::get_java_path() { return java_sdk_path.path_join("bin/java" + exe_ext); } +String EditorExportPlatformAndroid::get_keytool_path() { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + return java_sdk_path.path_join("bin/keytool" + exe_ext); +} + String EditorExportPlatformAndroid::get_adb_path() { String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { @@ -3655,6 +3732,7 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() { android_plugins_changed.set(); #endif // DISABLE_DEPRECATED #ifndef ANDROID_ENABLED + _create_editor_debug_keystore_if_needed(); _update_preset_status(); check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 7bc7bbf9e9..679afdc50f 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -60,6 +60,9 @@ const String ENV_ANDROID_KEYSTORE_RELEASE_PATH = "GODOT_ANDROID_KEYSTORE_RELEASE const String ENV_ANDROID_KEYSTORE_RELEASE_USER = "GODOT_ANDROID_KEYSTORE_RELEASE_USER"; const String ENV_ANDROID_KEYSTORE_RELEASE_PASS = "GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD"; +const String DEFAULT_ANDROID_KEYSTORE_DEBUG_USER = "androiddebugkey"; +const String DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD = "android"; + struct LauncherIcon { const char *export_path; int dimensions = 0; @@ -188,6 +191,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { const Ref<Image> &foreground, const Ref<Image> &background); + static void _create_editor_debug_keystore_if_needed(); + static Vector<ABI> get_enabled_abis(const Ref<EditorExportPreset> &p_preset); static bool _uses_vulkan(); @@ -236,6 +241,8 @@ public: static String get_java_path(); + static String get_keytool_path(); + virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; static bool has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error); diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index f56eda4694..ae336d6f9d 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -31,8 +31,12 @@ #include "file_access_android.h" #include "core/string/print_string.h" +#include "thread_jandroid.h" + +#include <android/asset_manager_jni.h> AAssetManager *FileAccessAndroid::asset_manager = nullptr; +jobject FileAccessAndroid::j_asset_manager = nullptr; String FileAccessAndroid::get_path() const { return path_src; @@ -257,3 +261,16 @@ void FileAccessAndroid::close() { FileAccessAndroid::~FileAccessAndroid() { _close(); } + +void FileAccessAndroid::setup(jobject p_asset_manager) { + JNIEnv *env = get_jni_env(); + j_asset_manager = env->NewGlobalRef(p_asset_manager); + asset_manager = AAssetManager_fromJava(env, j_asset_manager); +} + +void FileAccessAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(j_asset_manager); +} diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index ec613b6687..e79daeafb3 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -35,9 +35,13 @@ #include <android/asset_manager.h> #include <android/log.h> +#include <jni.h> #include <stdio.h> class FileAccessAndroid : public FileAccess { + static AAssetManager *asset_manager; + static jobject j_asset_manager; + mutable AAsset *asset = nullptr; mutable uint64_t len = 0; mutable uint64_t pos = 0; @@ -48,8 +52,6 @@ class FileAccessAndroid : public FileAccess { void _close(); public: - static AAssetManager *asset_manager; - virtual Error open_internal(const String &p_path, int p_mode_flags) override; // open a file virtual bool is_open() const override; // true when file is open @@ -65,6 +67,7 @@ public: virtual bool eof_reached() const override; // reading passed EOF + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual uint8_t get_8() const override; // get a byte virtual uint16_t get_16() const override; virtual uint32_t get_32() const override; @@ -92,6 +95,10 @@ public: virtual void close() override; + static void setup(jobject p_asset_manager); + + static void terminate(); + ~FileAccessAndroid(); }; diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp index 46d9728632..b66a201a12 100644 --- a/platform/android/file_access_filesystem_jandroid.cpp +++ b/platform/android/file_access_filesystem_jandroid.cpp @@ -53,6 +53,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_exists = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_last_modified = nullptr; +jmethodID FileAccessFilesystemJAndroid::_file_resize = nullptr; String FileAccessFilesystemJAndroid::get_path() const { return path_src; @@ -324,6 +325,27 @@ Error FileAccessFilesystemJAndroid::get_error() const { return OK; } +Error FileAccessFilesystemJAndroid::resize(int64_t p_length) { + if (_file_resize) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, FAILED); + ERR_FAIL_COND_V_MSG(!is_open(), FAILED, "File must be opened before use."); + int res = env->CallIntMethod(file_access_handler, _file_resize, id, p_length); + switch (res) { + case 0: + return OK; + case -3: + return ERR_INVALID_PARAMETER; + case -2: + return ERR_FILE_CANT_OPEN; + default: + return FAILED; + } + } else { + return ERR_UNAVAILABLE; + } +} + void FileAccessFilesystemJAndroid::flush() { if (_file_flush) { JNIEnv *env = get_jni_env(); @@ -383,6 +405,15 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) { _file_flush = env->GetMethodID(cls, "fileFlush", "(I)V"); _file_exists = env->GetMethodID(cls, "fileExists", "(Ljava/lang/String;)Z"); _file_last_modified = env->GetMethodID(cls, "fileLastModified", "(Ljava/lang/String;)J"); + _file_resize = env->GetMethodID(cls, "fileResize", "(IJ)I"); +} + +void FileAccessFilesystemJAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(file_access_handler); } void FileAccessFilesystemJAndroid::close() { diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index f33aa64ebe..6a8fc524b7 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -52,6 +52,7 @@ class FileAccessFilesystemJAndroid : public FileAccess { static jmethodID _file_close; static jmethodID _file_exists; static jmethodID _file_last_modified; + static jmethodID _file_resize; int id; String absolute_path; @@ -76,6 +77,7 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF + virtual Error resize(int64_t p_length) override; virtual uint8_t get_8() const override; ///< get a byte virtual uint16_t get_16() const override; virtual uint32_t get_32() const override; @@ -95,6 +97,7 @@ public: virtual bool file_exists(const String &p_path) override; ///< return true if a file exists static void setup(jobject p_file_access_handler); + static void terminate(); virtual uint64_t _get_modified_time(const String &p_file) override; virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 7797f4bc9d..b83ef1471c 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -205,36 +205,66 @@ android { } task copyAndRenameDebugApk(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/apk/debug/android_debug.apk" into getExportPath() rename "android_debug.apk", getExportFilename() } task copyAndRenameDevApk(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/apk/dev/android_dev.apk" into getExportPath() rename "android_dev.apk", getExportFilename() } task copyAndRenameReleaseApk(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/apk/release/android_release.apk" into getExportPath() rename "android_release.apk", getExportFilename() } task copyAndRenameDebugAab(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/bundle/debug/build-debug.aab" into getExportPath() rename "build-debug.aab", getExportFilename() } task copyAndRenameDevAab(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/bundle/dev/build-dev.aab" into getExportPath() rename "build-dev.aab", getExportFilename() } task copyAndRenameReleaseAab(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/bundle/release/build-release.aab" into getExportPath() rename "build-release.aab", getExportFilename() diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt index 0f447f0b05..b155c4e488 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt @@ -36,7 +36,9 @@ import android.util.Log import org.godotengine.godot.io.StorageScope import java.io.IOException import java.nio.ByteBuffer +import java.nio.channels.ClosedChannelException import java.nio.channels.FileChannel +import java.nio.channels.NonWritableChannelException import kotlin.math.max /** @@ -50,6 +52,11 @@ internal abstract class DataAccess(private val filePath: String) { companion object { private val TAG = DataAccess::class.java.simpleName + private const val OK_ERROR_ID = 0; + private const val FAILED_ERROR_ID = -1; + private const val FILE_CANT_OPEN_ERROR_ID = -2; + private const val INVALID_PARAMETER_ERROR_ID = -3; + fun generateDataAccess( storageScope: StorageScope, context: Context, @@ -135,6 +142,21 @@ internal abstract class DataAccess(private val filePath: String) { seek(positionFromBeginning) } + fun resize(length: Long): Int { + return try { + fileChannel.truncate(length) + OK_ERROR_ID + } catch (e: NonWritableChannelException) { + FILE_CANT_OPEN_ERROR_ID + } catch (e: ClosedChannelException) { + FILE_CANT_OPEN_ERROR_ID + } catch (e: IllegalArgumentException) { + INVALID_PARAMETER_ERROR_ID + } catch (e: IOException) { + FAILED_ERROR_ID + } + } + fun position(): Long { return try { fileChannel.position() diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt index 50741c1aab..6a8a10e56f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt @@ -45,6 +45,7 @@ class FileAccessHandler(val context: Context) { companion object { private val TAG = FileAccessHandler::class.java.simpleName + private const val FAILED_ERROR_ID = -1; private const val FILE_NOT_FOUND_ERROR_ID = -1 internal const val INVALID_FILE_ID = 0 private const val STARTING_FILE_ID = 1 @@ -190,6 +191,14 @@ class FileAccessHandler(val context: Context) { } } + fun fileResize(fileId: Int, length: Long): Int { + if (!hasFileId(fileId)) { + return FAILED_ERROR_ID + } + + return files[fileId].resize(length) + } + fun fileGetPosition(fileId: Int): Long { if (!hasFileId(fileId)) { return 0L diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index d6455cbf1c..a309a6ab74 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -1157,50 +1157,54 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL(env); - jclass activity = env->FindClass("android/app/Activity"); - jmethodID getClassLoader = env->GetMethodID(activity, "getClassLoader", "()Ljava/lang/ClassLoader;"); - classLoader = env->CallObjectMethod(p_activity, getClassLoader); - classLoader = (jclass)env->NewGlobalRef(classLoader); - jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); - findClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - jclass bclass = env->FindClass("java/lang/Class"); getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"); getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;"); Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Method"); getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;"); getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;"); getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Field"); Field_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); Field_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); Field_get = env->GetMethodID(bclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Boolean"); Boolean_booleanValue = env->GetMethodID(bclass, "booleanValue", "()Z"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Byte"); Byte_byteValue = env->GetMethodID(bclass, "byteValue", "()B"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Character"); Character_characterValue = env->GetMethodID(bclass, "charValue", "()C"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Short"); Short_shortValue = env->GetMethodID(bclass, "shortValue", "()S"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Integer"); Integer_integerValue = env->GetMethodID(bclass, "intValue", "()I"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Long"); Long_longValue = env->GetMethodID(bclass, "longValue", "()J"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Float"); Float_floatValue = env->GetMethodID(bclass, "floatValue", "()F"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Double"); Double_doubleValue = env->GetMethodID(bclass, "doubleValue", "()D"); + env->DeleteLocalRef(bclass); } diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 10716a5c79..49913b9c30 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -70,7 +70,11 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc } GodotIOJavaWrapper::~GodotIOJavaWrapper() { - // nothing to do here for now + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(godot_io_instance); } jobject GodotIOJavaWrapper::get_instance() { @@ -82,7 +86,9 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, ERR_UNAVAILABLE); jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); - return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; + Error result = env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; + env->DeleteLocalRef(jStr); + return result; } else { return ERR_UNAVAILABLE; } @@ -220,6 +226,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_type, int p_max ERR_FAIL_NULL(env); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_type, p_max_input_length, p_cursor_start, p_cursor_end); + env->DeleteLocalRef(jStr); } } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 2215f706c5..6cab7e74fd 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -95,6 +95,13 @@ static void _terminate(JNIEnv *env, bool p_restart = false) { if (godot_io_java) { delete godot_io_java; } + + TTS_Android::terminate(); + FileAccessAndroid::terminate(); + DirAccessJAndroid::terminate(); + FileAccessFilesystemJAndroid::terminate(); + NetSocketAndroid::terminate(); + if (godot_java) { if (!restart_on_cleanup) { if (p_restart) { @@ -125,10 +132,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv init_thread_jandroid(jvm, env); - jobject amgr = env->NewGlobalRef(p_asset_manager); - - FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr); - + FileAccessAndroid::setup(p_asset_manager); DirAccessJAndroid::setup(p_directory_access_handler); FileAccessFilesystemJAndroid::setup(p_file_access_handler); NetSocketAndroid::setup(p_net_utils); diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index a95f762e01..04424c1179 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -95,6 +95,7 @@ void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data()); env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y); + env->DeleteLocalRef(jImagePath); } } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 3c950bb1b1..61be6fc5db 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -172,6 +172,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); + env->DeleteLocalRef(jStrMessage); + env->DeleteLocalRef(jStrTitle); } } @@ -231,6 +233,7 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) { ERR_FAIL_NULL(env); jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); env->CallVoidMethod(godot_instance, _set_clipboard, jStr); + env->DeleteLocalRef(jStr); } } @@ -253,7 +256,9 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, false); jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); - return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); + bool result = env->CallBooleanMethod(godot_instance, _request_permission, jStrName); + env->DeleteLocalRef(jStrName); + return result; } else { return false; } @@ -340,7 +345,9 @@ int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) { ERR_FAIL_NULL_V(env, 0); jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); for (int i = 0; i < args.size(); i++) { - env->SetObjectArrayElement(jargs, i, env->NewStringUTF(args[i].utf8().get_data())); + jstring j_arg = env->NewStringUTF(args[i].utf8().get_data()); + env->SetObjectArrayElement(jargs, i, j_arg); + env->DeleteLocalRef(j_arg); } return env->CallIntMethod(godot_instance, _create_new_godot_instance, jargs); } else { @@ -355,6 +362,8 @@ void GodotJavaWrapper::begin_benchmark_measure(const String &p_context, const St jstring j_context = env->NewStringUTF(p_context.utf8().get_data()); jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_context, j_label); + env->DeleteLocalRef(j_context); + env->DeleteLocalRef(j_label); } } @@ -365,6 +374,8 @@ void GodotJavaWrapper::end_benchmark_measure(const String &p_context, const Stri jstring j_context = env->NewStringUTF(p_context.utf8().get_data()); jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_context, j_label); + env->DeleteLocalRef(j_context); + env->DeleteLocalRef(j_label); } } @@ -374,6 +385,7 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { ERR_FAIL_NULL(env); jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data()); env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file); + env->DeleteLocalRef(j_benchmark_file); } } @@ -383,7 +395,9 @@ bool GodotJavaWrapper::has_feature(const String &p_feature) const { ERR_FAIL_NULL_V(env, false); jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data()); - return env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + bool result = env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + env->DeleteLocalRef(j_feature); + return result; } else { return false; } diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index a2befdc9be..8f0ee51fac 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -49,6 +49,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) { _multicast_lock_release = env->GetMethodID(cls, "multicastLockRelease", "()V"); } +void NetSocketAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(net_utils); +} + void NetSocketAndroid::multicast_lock_acquire() { if (_multicast_lock_acquire) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h index e5f46d3236..26cb2d4e3d 100644 --- a/platform/android/net_socket_android.h +++ b/platform/android/net_socket_android.h @@ -63,6 +63,7 @@ protected: public: static void make_default(); static void setup(jobject p_net_utils); + static void terminate(); virtual void close(); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index bf6b7a7372..463a307854 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -162,7 +162,39 @@ Vector<String> OS_Android::get_granted_permissions() const { return godot_java->get_granted_permissions(); } -Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) { +bool OS_Android::copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path) { + if (!FileAccess::exists(p_library_path)) { + return false; + } + + Ref<DirAccess> da_ref = DirAccess::create_for_path(p_library_path); + if (!da_ref.is_valid()) { + return false; + } + + String copy_path = p_target_dir.path_join(p_library_path.get_file()); + bool copy_exists = FileAccess::exists(copy_path); + if (copy_exists) { + print_verbose("Deleting existing library copy " + copy_path); + if (da_ref->remove(copy_path) != OK) { + print_verbose("Unable to delete " + copy_path); + } + } + + print_verbose("Copying " + p_library_path + " to " + p_target_dir); + Error create_dir_result = da_ref->make_dir_recursive(p_target_dir); + if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) { + copy_exists = da_ref->copy(p_library_path, copy_path) == OK; + } + + if (copy_exists && r_copy_path != nullptr) { + *r_copy_path = copy_path; + } + + return copy_exists; +} + +Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { String path = p_path; bool so_file_exists = true; if (!FileAccess::exists(path)) { @@ -172,24 +204,32 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha 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; + // The library (and its dependencies) may be on the sdcard and thus inaccessible. + // Try to copy to the internal directory for access. + const String dynamic_library_path = get_dynamic_libraries_path(); + + if (p_data != nullptr && p_data->library_dependencies != nullptr && !p_data->library_dependencies->is_empty()) { + // Copy the library dependencies + print_verbose("Copying library dependencies.."); + for (const String &library_dependency_path : *p_data->library_dependencies) { + String internal_library_dependency_path; + if (!copy_dynamic_library(library_dependency_path, dynamic_library_path.path_join(library_dependency_path.get_base_dir()), &internal_library_dependency_path)) { + ERR_PRINT(vformat("Unable to copy library dependency %s", library_dependency_path)); + } else { + void *lib_dependency_handle = dlopen(internal_library_dependency_path.utf8().get_data(), RTLD_NOW); + if (!lib_dependency_handle) { + ERR_PRINT(vformat("Can't open dynamic library dependency: %s. Error: %s.", internal_library_dependency_path, dlerror())); + } } } } + String internal_path; + print_verbose("Copying library " + p_path); + const bool internal_so_file_exists = copy_dynamic_library(p_path, dynamic_library_path.path_join(p_path.get_base_dir()), &internal_path); + if (internal_so_file_exists) { + print_verbose("Opening library " + internal_path); p_library_handle = dlopen(internal_path.utf8().get_data(), RTLD_NOW); if (p_library_handle) { path = internal_path; @@ -199,8 +239,8 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha 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) { - *r_resolved_path = path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; } return OK; @@ -736,6 +776,10 @@ void OS_Android::benchmark_dump() { } bool OS_Android::_check_internal_feature_support(const String &p_feature) { + if (p_feature == "macos" || p_feature == "web_ios" || p_feature == "web_macos" || p_feature == "windows") { + return false; + } + if (p_feature == "system_fonts") { return true; } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index e01d759494..7bdbeef77a 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -113,7 +113,7 @@ public: virtual void alert(const String &p_alert, const String &p_title) override; - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override; + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; virtual String get_name() const override; virtual String get_distribution_name() const override; @@ -178,6 +178,8 @@ public: private: // Location where we relocate external dynamic libraries to make them accessible. String get_dynamic_libraries_path() const; + // Copy a dynamic library to the given location to make it accessible for loading. + bool copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path = nullptr); }; #endif // OS_ANDROID_H diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp index 93517d8045..be85e47972 100644 --- a/platform/android/tts_android.cpp +++ b/platform/android/tts_android.cpp @@ -77,6 +77,14 @@ void TTS_Android::setup(jobject p_tts) { } } +void TTS_Android::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(tts); +} + void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) { ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); if (ids.has(p_id)) { @@ -170,6 +178,8 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum jstring jStrT = env->NewStringUTF(p_text.utf8().get_data()); jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data()); env->CallVoidMethod(tts, _speak, jStrT, jStrV, CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, p_interrupt); + env->DeleteLocalRef(jStrT); + env->DeleteLocalRef(jStrV); } } diff --git a/platform/android/tts_android.h b/platform/android/tts_android.h index 39efef6ed1..4cc7c12846 100644 --- a/platform/android/tts_android.h +++ b/platform/android/tts_android.h @@ -57,6 +57,7 @@ class TTS_Android { public: static void setup(jobject p_tts); + static void terminate(); static void _java_utterance_callback(int p_event, int p_id, int p_pos); static bool is_speaking(); |