summaryrefslogtreecommitdiff
path: root/platform/android
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/detect.py5
-rw-r--r--platform/android/display_server_android.cpp40
-rw-r--r--platform/android/export/export.cpp31
-rw-r--r--platform/android/export/gradle_export_util.h11
-rw-r--r--platform/android/file_access_android.cpp3
-rw-r--r--platform/android/java/app/AndroidManifest.xml10
-rw-r--r--platform/android/java/app/assets/.gitignore2
-rw-r--r--platform/android/java/app/build.gradle10
-rw-r--r--platform/android/java/app/config.gradle80
-rw-r--r--platform/android/java/build.gradle6
-rw-r--r--platform/android/java/lib/AndroidManifest.xml5
-rw-r--r--platform/android/java/lib/build.gradle8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java25
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java107
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java56
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java86
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java23
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java38
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java45
-rw-r--r--platform/android/java/nativeSrcsConfigs/README.md2
-rw-r--r--platform/android/java/nativeSrcsConfigs/build.gradle3
-rw-r--r--platform/android/java_class_wrapper.cpp3
-rw-r--r--platform/android/java_godot_io_wrapper.cpp12
-rw-r--r--platform/android/java_godot_lib_jni.cpp15
-rw-r--r--platform/android/java_godot_view_wrapper.cpp7
-rw-r--r--platform/android/java_godot_view_wrapper.h2
-rw-r--r--platform/android/java_godot_wrapper.cpp63
-rw-r--r--platform/android/java_godot_wrapper.h2
-rw-r--r--platform/android/thread_jandroid.cpp27
29 files changed, 528 insertions, 199 deletions
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 5f0fcc9b77..996b6dcf41 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -197,12 +197,11 @@ def configure(env):
if env["optimize"] == "speed": # optimize for speed (default)
env.Append(LINKFLAGS=["-O2"])
env.Append(CCFLAGS=["-O2", "-fomit-frame-pointer"])
- env.Append(CPPDEFINES=["NDEBUG"])
- else: # optimize for size
+ elif env["optimize"] == "size": # optimize for size
env.Append(CCFLAGS=["-Os"])
- env.Append(CPPDEFINES=["NDEBUG"])
env.Append(LINKFLAGS=["-Os"])
+ env.Append(CPPDEFINES=["NDEBUG"])
if can_vectorize:
env.Append(CCFLAGS=["-ftree-vectorize"])
if env["target"] == "release_debug":
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 5f7e5eaa83..dd001baba9 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -477,7 +477,7 @@ void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p
Input::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed);
break;
case JOY_EVENT_AXIS:
- Input::JoyAxis value;
+ Input::JoyAxisValue value;
value.min = -1;
value.value = p_event.value;
Input::get_singleton()->joy_axis(p_event.device, p_event.index, value);
@@ -741,15 +741,15 @@ void DisplayServerAndroid::process_mouse_event(int input_device, int event_actio
ev->set_pressed(true);
buttons_state = event_buttons_mask;
if (event_vertical_factor > 0) {
- _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_UP, event_vertical_factor);
+ _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_UP, event_vertical_factor);
} else if (event_vertical_factor < 0) {
- _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_DOWN, -event_vertical_factor);
+ _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_DOWN, -event_vertical_factor);
}
if (event_horizontal_factor > 0) {
- _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_RIGHT, event_horizontal_factor);
+ _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_RIGHT, event_horizontal_factor);
} else if (event_horizontal_factor < 0) {
- _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_LEFT, -event_horizontal_factor);
+ _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_LEFT, -event_horizontal_factor);
}
} break;
}
@@ -784,16 +784,16 @@ void DisplayServerAndroid::process_double_tap(int event_android_button_mask, Poi
int DisplayServerAndroid::_button_index_from_mask(int button_mask) {
switch (button_mask) {
- case BUTTON_MASK_LEFT:
- return BUTTON_LEFT;
- case BUTTON_MASK_RIGHT:
- return BUTTON_RIGHT;
- case BUTTON_MASK_MIDDLE:
- return BUTTON_MIDDLE;
- case BUTTON_MASK_XBUTTON1:
- return BUTTON_XBUTTON1;
- case BUTTON_MASK_XBUTTON2:
- return BUTTON_XBUTTON2;
+ case MOUSE_BUTTON_MASK_LEFT:
+ return MOUSE_BUTTON_LEFT;
+ case MOUSE_BUTTON_MASK_RIGHT:
+ return MOUSE_BUTTON_RIGHT;
+ case MOUSE_BUTTON_MASK_MIDDLE:
+ return MOUSE_BUTTON_MIDDLE;
+ case MOUSE_BUTTON_MASK_XBUTTON1:
+ return MOUSE_BUTTON_XBUTTON1;
+ case MOUSE_BUTTON_MASK_XBUTTON2:
+ return MOUSE_BUTTON_XBUTTON2;
default:
return 0;
}
@@ -854,19 +854,19 @@ int DisplayServerAndroid::mouse_get_button_state() const {
int DisplayServerAndroid::_android_button_mask_to_godot_button_mask(int android_button_mask) {
int godot_button_mask = 0;
if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) {
- godot_button_mask |= BUTTON_MASK_LEFT;
+ godot_button_mask |= MOUSE_BUTTON_MASK_LEFT;
}
if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) {
- godot_button_mask |= BUTTON_MASK_RIGHT;
+ godot_button_mask |= MOUSE_BUTTON_MASK_RIGHT;
}
if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) {
- godot_button_mask |= BUTTON_MASK_MIDDLE;
+ godot_button_mask |= MOUSE_BUTTON_MASK_MIDDLE;
}
if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) {
- godot_button_mask |= BUTTON_MASK_XBUTTON1;
+ godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1;
}
if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) {
- godot_button_mask |= BUTTON_MASK_XBUTTON2;
+ godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2;
}
return godot_button_mask;
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 888b1546e4..326e513261 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -806,8 +806,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
manifest_text += _get_xr_features_tag(p_preset);
manifest_text += _get_instrumentation_tag(p_preset);
- String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
- manifest_text += _get_application_tag(p_preset, plugins_names);
+ manifest_text += _get_application_tag(p_preset);
manifest_text += "</manifest>\n";
String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
@@ -856,8 +855,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int xr_mode_index = p_preset->get("xr_features/xr_mode");
bool focus_awareness = p_preset->get("xr_features/focus_awareness");
- String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
-
Vector<String> perms;
// Write permissions into the perms variable.
_get_permissions(p_preset, p_give_internet, perms);
@@ -995,11 +992,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
}
- if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.is_empty()) {
- // Update the meta-data 'android:value' attribute with the list of enabled plugins.
- string_table.write[attr_value] = plugins_names;
- }
-
is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware";
iofs += 20;
}
@@ -2003,6 +1995,7 @@ public:
String template_err;
bool dvalid = false;
bool rvalid = false;
+ bool has_export_templates = false;
if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
@@ -2010,7 +2003,7 @@ public:
template_err += TTR("Custom debug template not found.") + "\n";
}
} else {
- dvalid = exists_export_template("android_debug.apk", &template_err);
+ has_export_templates |= exists_export_template("android_debug.apk", &template_err);
}
if (p_preset->get("custom_template/release") != "") {
@@ -2019,22 +2012,24 @@ public:
template_err += TTR("Custom release template not found.") + "\n";
}
} else {
- rvalid = exists_export_template("android_release.apk", &template_err);
+ has_export_templates |= exists_export_template("android_release.apk", &template_err);
}
- valid = dvalid || rvalid;
+ r_missing_templates = !has_export_templates;
+ valid = dvalid || rvalid || has_export_templates;
if (!valid) {
err += template_err;
}
} else {
- valid = exists_export_template("android_source.zip", &err);
+ r_missing_templates = !exists_export_template("android_source.zip", &err);
- if (!FileAccess::exists("res://android/build/build.gradle")) {
+ bool installed_android_build_template = FileAccess::exists("res://android/build/build.gradle");
+ if (!installed_android_build_template) {
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
- valid = false;
}
+
+ valid = installed_android_build_template && !r_missing_templates;
}
- r_missing_templates = !valid;
// Validate the rest of the configuration.
@@ -2414,6 +2409,9 @@ public:
print_verbose("- custom build enabled: " + bool_to_string(use_custom_build));
print_verbose("- apk expansion enabled: " + bool_to_string(apk_expansion));
print_verbose("- enabled abis: " + String(",").join(enabled_abis));
+ print_verbose("- export filter: " + itos(p_preset->get_export_filter()));
+ print_verbose("- include filter: " + p_preset->get_include_filter());
+ print_verbose("- exclude filter: " + p_preset->get_exclude_filter());
Ref<Image> splash_image;
Ref<Image> splash_bg_color_image;
@@ -2553,6 +2551,7 @@ public:
cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
cmdline.push_back("-Pperform_zipalign=" + zipalign_flag); // argument to specify whether the build should be zipaligned.
cmdline.push_back("-Pperform_signing=" + sign_flag); // argument to specify whether the build should be signed.
+ cmdline.push_back("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG));
// NOTE: The release keystore is not included in the verbose logging
// to avoid accidentally leaking sensitive information when sharing verbose logs for troubleshooting.
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index ce6a3c96db..097a2391ee 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -269,14 +269,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
return manifest_instrumentation_text;
}
-String _get_plugins_tag(const String &plugins_names) {
- if (!plugins_names.is_empty()) {
- return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names);
- } else {
- return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n";
- }
-}
-
String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
String orientation = _get_android_orientation_label(_get_screen_orientation());
@@ -295,7 +287,7 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
return manifest_activity_text;
}
-String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) {
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset) {
bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
String manifest_application_text =
" <application android:label=\"@string/godot_project_name_string\"\n"
@@ -303,7 +295,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const Strin
" android:icon=\"@mipmap/icon\">\n\n"
" <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n";
- manifest_application_text += _get_plugins_tag(plugins_names);
if (uses_xr) {
manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n";
}
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index 165d5da3ae..e288c16777 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -114,6 +114,9 @@ uint8_t FileAccessAndroid::get_8() const {
}
int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
+ ERR_FAIL_COND_V(!p_dst, -1);
+ ERR_FAIL_COND_V(p_length < 0, -1);
+
off_t r = AAsset_read(a, p_dst, p_length);
if (pos + p_length > len) {
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index e94681659c..948fa8c00b 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -22,6 +22,11 @@
tools:ignore="GoogleAppIndexingWarning"
android:icon="@mipmap/icon" >
+ <!-- Records the version of the Godot editor used for building -->
+ <meta-data
+ android:name="org.godotengine.editor.version"
+ android:value="${godotEditorVersion}" />
+
<!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. -->
<!-- Do these changes in the export preset. Adding new ones is fine. -->
@@ -30,11 +35,6 @@
android:name="xr_mode_metadata_name"
android:value="xr_mode_metadata_value" />
- <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. -->
- <meta-data
- android:name="plugins"
- android:value="plugins_value"/>
-
<activity
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
diff --git a/platform/android/java/app/assets/.gitignore b/platform/android/java/app/assets/.gitignore
new file mode 100644
index 0000000000..d6b7ef32c8
--- /dev/null
+++ b/platform/android/java/app/assets/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 814cc30613..f103f22db2 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -77,7 +77,7 @@ android {
defaultConfig {
// The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
aaptOptions {
- ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
+ ignoreAssetsPattern "!.svn:!.git:!.gitignore:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
ndk {
@@ -85,6 +85,8 @@ android {
abiFilters export_abi_list
}
+ manifestPlaceholders = [godotEditorVersion: getGodotEditorVersion()]
+
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
versionCode getExportVersionCode()
@@ -104,8 +106,10 @@ android {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
- // Should be uncommented for development purpose within Android Studio
- // doNotStrip '**/*.so'
+ // 'doNotStrip' is enabled for development within Android Studio
+ if (shouldNotStrip()) {
+ doNotStrip '**/*.so'
+ }
}
signingConfigs {
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 202b3c35c0..c0ae4007d2 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -50,7 +50,56 @@ ext.getExportVersionName = { ->
return versionName
}
-final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
+ext.getGodotEditorVersion = { ->
+ String editorVersion = project.hasProperty("godot_editor_version") ? project.property("godot_editor_version") : ""
+ if (editorVersion == null || editorVersion.isEmpty()) {
+ // Try the library version first
+ editorVersion = getGodotLibraryVersion()
+
+ if (editorVersion.isEmpty()) {
+ // Fallback value.
+ editorVersion = "custom_build"
+ }
+ }
+ return editorVersion
+}
+
+ext.getGodotLibraryVersion = { ->
+ // Attempt to read the version from the `version.py` file.
+ String libraryVersion = ""
+
+ File versionFile = new File("../../../version.py")
+ if (versionFile.isFile()) {
+ List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"]
+ def map = [:]
+
+ List<String> lines = versionFile.readLines()
+ for (String line in lines) {
+ String[] keyValue = line.split("=")
+ String key = keyValue[0].trim()
+ String value = keyValue[1].trim().replaceAll("\"", "")
+
+ if (requiredKeys.contains(key)) {
+ if (!value.isEmpty()) {
+ map[key] = value
+ }
+ requiredKeys.remove(key)
+ }
+ }
+
+ if (requiredKeys.empty) {
+ libraryVersion = map.values().join(".")
+ }
+ }
+
+ if (libraryVersion.isEmpty()) {
+ // Fallback value in case we're unable to read the file.
+ libraryVersion = "custom_build"
+ }
+ return libraryVersion
+}
+
+final String VALUE_SEPARATOR_REGEX = "\\|"
// get the list of ABIs the project should be exported to
ext.getExportEnabledABIs = { ->
@@ -59,7 +108,7 @@ ext.getExportEnabledABIs = { ->
enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
}
Set<String> exportAbiFilter = [];
- for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) {
if (!abi_name.trim().isEmpty()){
exportAbiFilter.add(abi_name);
}
@@ -94,7 +143,7 @@ ext.getGodotPluginsMavenRepos = { ->
if (project.hasProperty("plugins_maven_repos")) {
String mavenReposProperty = project.property("plugins_maven_repos")
if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) {
- for (String mavenRepoUrl : mavenReposProperty.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String mavenRepoUrl : mavenReposProperty.split(VALUE_SEPARATOR_REGEX)) {
mavenRepos += mavenRepoUrl.trim()
}
}
@@ -114,7 +163,7 @@ ext.getGodotPluginsRemoteBinaries = { ->
if (project.hasProperty("plugins_remote_binaries")) {
String remoteDepsList = project.property("plugins_remote_binaries")
if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) {
- for (String dep: remoteDepsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String dep: remoteDepsList.split(VALUE_SEPARATOR_REGEX)) {
remoteDeps += dep.trim()
}
}
@@ -133,7 +182,7 @@ ext.getGodotPluginsLocalBinaries = { ->
if (project.hasProperty("plugins_local_binaries")) {
String pluginsList = project.property("plugins_local_binaries")
if (pluginsList != null && !pluginsList.trim().isEmpty()) {
- for (String plugin : pluginsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String plugin : pluginsList.split(VALUE_SEPARATOR_REGEX)) {
binDeps += plugin.trim()
}
}
@@ -160,10 +209,19 @@ ext.getReleaseKeyAlias = { ->
return keyAlias
}
+ext.isAndroidStudio = { ->
+ def sysProps = System.getProperties()
+ return sysProps != null && sysProps['idea.platform.prefix'] != null
+}
+
ext.shouldZipAlign = { ->
String zipAlignFlag = project.hasProperty("perform_zipalign") ? project.property("perform_zipalign") : ""
if (zipAlignFlag == null || zipAlignFlag.isEmpty()) {
- zipAlignFlag = "false"
+ if (isAndroidStudio()) {
+ zipAlignFlag = "true"
+ } else {
+ zipAlignFlag = "false"
+ }
}
return Boolean.parseBoolean(zipAlignFlag)
}
@@ -171,7 +229,15 @@ ext.shouldZipAlign = { ->
ext.shouldSign = { ->
String signFlag = project.hasProperty("perform_signing") ? project.property("perform_signing") : ""
if (signFlag == null || signFlag.isEmpty()) {
- signFlag = "false"
+ if (isAndroidStudio()) {
+ signFlag = "true"
+ } else {
+ signFlag = "false"
+ }
}
return Boolean.parseBoolean(signFlag)
}
+
+ext.shouldNotStrip = { ->
+ return isAndroidStudio()
+}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 73c136ed0e..ec02b0fc7a 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -165,12 +165,6 @@ task cleanGodotTemplates(type: Delete) {
// Delete the library generated AAR files
delete("lib/build/outputs/aar")
- // Delete the godotpayment libs directory contents
- delete("plugins/godotpayment/libs")
-
- // Delete the generated godotpayment aar
- delete("plugins/godotpayment/build/outputs/aar")
-
// Delete the app libs directory contents
delete("app/libs")
diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml
index fa39bc0f1d..3034794d69 100644
--- a/platform/android/java/lib/AndroidManifest.xml
+++ b/platform/android/java/lib/AndroidManifest.xml
@@ -6,6 +6,11 @@
<application>
+ <!-- Records the version of the Godot library -->
+ <meta-data
+ android:name="org.godotengine.library.version"
+ android:value="${godotLibraryVersion}" />
+
<service android:name=".GodotDownloaderService" />
</application>
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index ca5153f7f6..663ba73d40 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -18,6 +18,8 @@ android {
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
+
+ manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()]
}
compileOptions {
@@ -34,8 +36,10 @@ android {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
- // Should be uncommented for development purpose within Android Studio
- // doNotStrip '**/*.so'
+ // 'doNotStrip' is enabled for development within Android Studio
+ if (shouldNotStrip()) {
+ doNotStrip '**/*.so'
+ }
}
sourceSets {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
index 4e67402c63..ec2ace4821 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -34,6 +34,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
+import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
@@ -44,7 +45,7 @@ import androidx.fragment.app.FragmentActivity;
* It's also a reference implementation for how to setup and use the {@link Godot} fragment
* within an Android app.
*/
-public abstract class FullScreenGodotApp extends FragmentActivity {
+public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost {
@Nullable
private Godot godotFragment;
@@ -64,6 +65,28 @@ public abstract class FullScreenGodotApp extends FragmentActivity {
public void onNewIntent(Intent intent) {
if (godotFragment != null) {
godotFragment.onNewIntent(intent);
+ } else {
+ super.onNewIntent(intent);
+ }
+ }
+
+ @CallSuper
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (godotFragment != null) {
+ godotFragment.onActivityResult(requestCode, resultCode, data);
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ @CallSuper
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ if (godotFragment != null) {
+ godotFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ } else {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 7d396b402e..0c16214c8a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -103,6 +103,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -131,6 +132,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
private boolean activityResumed;
private int mState;
+ private GodotHost godotHost;
private GodotPluginRegistry pluginRegistry;
static private Intent mCurrentIntent;
@@ -178,7 +180,25 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
public ResultCallback result_callback;
@Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (getParentFragment() instanceof GodotHost) {
+ godotHost = (GodotHost)getParentFragment();
+ } else if (getActivity() instanceof GodotHost) {
+ godotHost = (GodotHost)getActivity();
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ godotHost = null;
+ }
+
+ @CallSuper
+ @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
if (result_callback != null) {
result_callback.callback(requestCode, resultCode, data);
result_callback = null;
@@ -189,8 +209,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
}
+ @CallSuper
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults);
}
@@ -201,6 +223,20 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
};
/**
+ * Invoked on the render thread when the Godot setup is complete.
+ */
+ @CallSuper
+ protected void onGodotSetupCompleted() {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGodotSetupCompleted();
+ }
+
+ if (godotHost != null) {
+ godotHost.onGodotSetupCompleted();
+ }
+ }
+
+ /**
* Invoked on the render thread when the Godot main loop has started.
*/
@CallSuper
@@ -208,6 +244,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onGodotMainLoopStarted();
}
+
+ if (godotHost != null) {
+ godotHost.onGodotMainLoopStarted();
+ }
}
/**
@@ -228,7 +268,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
GodotLib.setup(command_line);
- final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name");
+ final String videoDriver = GodotLib.getGlobal("rendering/driver/driver_name");
if (videoDriver.equals("Vulkan")) {
mRenderView = new GodotVulkanRenderView(activity, this);
} else {
@@ -301,7 +341,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
- //deprecated in API 26
+ // deprecated in API 26
v.vibrate(durationMs);
}
}
@@ -356,6 +396,21 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
@CallSuper
protected String[] getCommandLine() {
+ String[] original = parseCommandLine();
+ String[] updated;
+ List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null;
+ if (hostCommandLine == null || hostCommandLine.isEmpty()) {
+ updated = original;
+ } else {
+ updated = Arrays.copyOf(original, original.length + hostCommandLine.size());
+ for (int i = 0; i < hostCommandLine.size(); i++) {
+ updated[original.length + i] = hostCommandLine.get(i);
+ }
+ }
+ return updated;
+ }
+
+ private String[] parseCommandLine() {
InputStream is;
try {
is = getActivity().getAssets().open("_cl_");
@@ -464,14 +519,16 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) {
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
final Activity activity = getActivity();
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
- //check for apk expansion API
+ // check for apk expansion API
boolean md5mismatch = false;
command_line = getCommandLine();
String main_pack_md5 = null;
@@ -527,9 +584,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
command_line = new_args.toArray(new String[new_args.size()]);
}
if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) {
- //check that environment is ok!
+ // check that environment is ok!
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- //show popup and die
+ // show popup and die
}
// Build the full path to the app's expansion files
@@ -572,24 +629,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// This is where you do set up to display the download
- // progress (next step)
+ // progress (next step in onCreateView)
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
GodotDownloaderService.class);
- View downloadingExpansionView =
- inflater.inflate(R.layout.downloading_expansion, container, false);
- mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar);
- mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText);
- mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction);
- mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage);
- mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed);
- mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining);
- mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard);
- mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular);
- mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton);
- mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton);
-
- return downloadingExpansionView;
+ return;
}
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
@@ -600,6 +644,27 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
mCurrentIntent = activity.getIntent();
initializeGodot();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) {
+ if (mDownloaderClientStub != null) {
+ View downloadingExpansionView =
+ inflater.inflate(R.layout.downloading_expansion, container, false);
+ mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar);
+ mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText);
+ mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction);
+ mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage);
+ mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed);
+ mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining);
+ mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard);
+ mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular);
+ mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton);
+ mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton);
+
+ return downloadingExpansionView;
+ }
+
return containerLayout;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
new file mode 100644
index 0000000000..317fd13535
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* GodotHost.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} fragment.
+ */
+public interface GodotHost {
+ /**
+ * Provides a set of command line parameters to setup the engine.
+ */
+ default List<String> getCommandLine() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Invoked on the render thread when the Godot setup is complete.
+ */
+ default void onGodotSetupCompleted() {}
+
+ /**
+ * Invoked on the render thread when the Godot main loop has started.
+ */
+ default void onGodotMainLoopStarted() {}
+}
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 993f0e9127..6c8a3d4219 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
@@ -47,6 +47,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -109,31 +110,9 @@ public abstract class GodotPlugin {
* This method is invoked on the render thread.
*/
public final void onRegisterPluginWithGodotNative() {
- registeredSignals.putAll(registerPluginWithGodotNative(this, new GodotPluginInfoProvider() {
- @NonNull
- @Override
- public String getPluginName() {
- return GodotPlugin.this.getPluginName();
- }
-
- @NonNull
- @Override
- public List<String> getPluginMethods() {
- return GodotPlugin.this.getPluginMethods();
- }
-
- @NonNull
- @Override
- public Set<SignalInfo> getPluginSignals() {
- return GodotPlugin.this.getPluginSignals();
- }
-
- @NonNull
- @Override
- public Set<String> getPluginGDNativeLibrariesPaths() {
- return GodotPlugin.this.getPluginGDNativeLibrariesPaths();
- }
- }));
+ registeredSignals.putAll(
+ registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(),
+ getPluginGDNativeLibrariesPaths()));
}
/**
@@ -141,23 +120,41 @@ public abstract class GodotPlugin {
*
* This method must be invoked on the render thread.
*/
- public static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, GodotPluginInfoProvider pluginInfoProvider) {
- nativeRegisterSingleton(pluginInfoProvider.getPluginName(), pluginObject);
+ public static void registerPluginWithGodotNative(Object pluginObject,
+ GodotPluginInfoProvider pluginInfoProvider) {
+ registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(),
+ Collections.emptyList(), pluginInfoProvider.getPluginSignals(),
+ pluginInfoProvider.getPluginGDNativeLibrariesPaths());
+
+ // Notify that registration is complete.
+ pluginInfoProvider.onPluginRegistered();
+ }
+ private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
+ String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals,
+ Set<String> pluginGDNativeLibrariesPaths) {
+ nativeRegisterSingleton(pluginName, pluginObject);
+
+ Set<Method> filteredMethods = new HashSet<>();
Class clazz = pluginObject.getClass();
+
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
- boolean found = false;
-
- for (String s : pluginInfoProvider.getPluginMethods()) {
- if (s.equals(method.getName())) {
- found = true;
- break;
+ // Check if the method is annotated with {@link UsedByGodot}.
+ if (method.getAnnotation(UsedByGodot.class) != null) {
+ filteredMethods.add(method);
+ } else {
+ // For backward compatibility, process the methods from the given <pluginMethods> argument.
+ for (String methodName : pluginMethods) {
+ if (methodName.equals(method.getName())) {
+ filteredMethods.add(method);
+ break;
+ }
}
}
- if (!found)
- continue;
+ }
+ for (Method method : filteredMethods) {
List<String> ptr = new ArrayList<>();
Class[] paramTypes = method.getParameterTypes();
@@ -168,21 +165,20 @@ public abstract class GodotPlugin {
String[] pt = new String[ptr.size()];
ptr.toArray(pt);
- nativeRegisterMethod(pluginInfoProvider.getPluginName(), method.getName(), method.getReturnType().getName(), pt);
+ nativeRegisterMethod(pluginName, method.getName(), method.getReturnType().getName(), pt);
}
// Register the signals for this plugin.
Map<String, SignalInfo> registeredSignals = new HashMap<>();
- for (SignalInfo signalInfo : pluginInfoProvider.getPluginSignals()) {
+ for (SignalInfo signalInfo : pluginSignals) {
String signalName = signalInfo.getName();
- nativeRegisterSignal(pluginInfoProvider.getPluginName(), signalName, signalInfo.getParamTypesNames());
+ nativeRegisterSignal(pluginName, signalName, signalInfo.getParamTypesNames());
registeredSignals.put(signalName, signalInfo);
}
// Get the list of gdnative libraries to register.
- Set<String> gdnativeLibrariesPaths = pluginInfoProvider.getPluginGDNativeLibrariesPaths();
- if (!gdnativeLibrariesPaths.isEmpty()) {
- nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0]));
+ if (!pluginGDNativeLibrariesPaths.isEmpty()) {
+ nativeRegisterGDNativeLibraries(pluginGDNativeLibrariesPaths.toArray(new String[0]));
}
return registeredSignals;
@@ -236,6 +232,11 @@ public abstract class GodotPlugin {
public boolean onMainBackPressed() { return false; }
/**
+ * Invoked on the render thread when the Godot setup is complete.
+ */
+ public void onGodotSetupCompleted() {}
+
+ /**
* Invoked on the render thread when the Godot main loop has started.
*/
public void onGodotMainLoopStarted() {}
@@ -282,8 +283,11 @@ public abstract class GodotPlugin {
/**
* Returns the list of methods to be exposed to Godot.
+ *
+ * @deprecated Used the {@link UsedByGodot} annotation instead.
*/
@NonNull
+ @Deprecated
public List<String> getPluginMethods() {
return Collections.emptyList();
}
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
index c3084b036e..09366384c2 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java
@@ -32,7 +32,7 @@ package org.godotengine.godot.plugin;
import androidx.annotation.NonNull;
-import java.util.List;
+import java.util.Collections;
import java.util.Set;
/**
@@ -46,16 +46,12 @@ public interface GodotPluginInfoProvider {
String getPluginName();
/**
- * Returns the list of methods to be exposed to Godot.
- */
- @NonNull
- List<String> getPluginMethods();
-
- /**
* Returns the list of signals to be exposed to Godot.
*/
@NonNull
- Set<SignalInfo> getPluginSignals();
+ default Set<SignalInfo> getPluginSignals() {
+ return Collections.emptySet();
+ }
/**
* Returns the paths for the plugin's gdnative libraries (if any).
@@ -63,5 +59,14 @@ public interface GodotPluginInfoProvider {
* The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file.
*/
@NonNull
- Set<String> getPluginGDNativeLibrariesPaths();
+ default Set<String> getPluginGDNativeLibrariesPaths() {
+ 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 99811f72ed..5b41205253 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
@@ -44,8 +44,6 @@ import androidx.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -56,13 +54,6 @@ public final class GodotPluginRegistry {
private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1.";
- /**
- * Name for the metadata containing the list of Godot plugins to enable.
- */
- private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins";
-
- private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|";
-
private static GodotPluginRegistry instance;
private final ConcurrentHashMap<String, GodotPlugin> registry;
@@ -132,37 +123,11 @@ public final class GodotPluginRegistry {
return;
}
- // When using the Godot editor for building and exporting the apk, this is used to check
- // which plugins to enable.
- // When using a custom process to generate the apk, the metadata is not needed since
- // it's assumed that the developer is aware of the dependencies included in the apk.
- final Set<String> enabledPluginsSet;
- if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) {
- String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, "");
- String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX);
- if (enabledPluginsList.length == 0) {
- // No plugins to enable. Aborting early.
- return;
- }
-
- enabledPluginsSet = new HashSet<>();
- for (String enabledPlugin : enabledPluginsList) {
- enabledPluginsSet.add(enabledPlugin.trim());
- }
- } else {
- enabledPluginsSet = null;
- }
-
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();
- if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) {
- Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled.");
- continue;
- }
-
Log.i(TAG, "Initializing Godot plugin " + pluginName);
// Retrieve the plugin class full name.
@@ -177,8 +142,7 @@ public final class GodotPluginRegistry {
.getConstructor(Godot.class);
GodotPlugin pluginHandle = pluginConstructor.newInstance(godot);
- // Load the plugin initializer into the registry using the plugin name
- // as key.
+ // Load the plugin initializer into the registry using the plugin name as key.
if (!pluginName.equals(pluginHandle.getPluginName())) {
Log.w(TAG,
"Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java
new file mode 100644
index 0000000000..04c091d944
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* UsedByGodot.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate a method is being invoked from the Godot game logic.
+ *
+ * At runtime, annotated plugin methods are detected and automatically registered.
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UsedByGodot {}
diff --git a/platform/android/java/nativeSrcsConfigs/README.md b/platform/android/java/nativeSrcsConfigs/README.md
index e48505ccda..9d884415cc 100644
--- a/platform/android/java/nativeSrcsConfigs/README.md
+++ b/platform/android/java/nativeSrcsConfigs/README.md
@@ -1,4 +1,4 @@
## Native sources configs
-This is a non functional Android library used to provide Android Studio editor support to the Godot project native files.
+This is a non-functional Android library used to provide Android Studio editor support to the Godot project native files.
Nothing else should be added to this library.
diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle
index 66077060ea..158bb2b98e 100644
--- a/platform/android/java/nativeSrcsConfigs/build.gradle
+++ b/platform/android/java/nativeSrcsConfigs/build.gradle
@@ -20,9 +20,6 @@ android {
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
-
- // Should be uncommented for development purpose within Android Studio
- // doNotStrip '**/*.so'
}
sourceSets {
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index ab03599dc3..f49b0e843a 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -38,6 +38,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
return false;
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
MethodInfo *method = nullptr;
for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) {
@@ -965,6 +966,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
return class_cache[p_class];
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, Ref<JavaClass>());
jclass bclass = env->FindClass(p_class.utf8().get_data());
ERR_FAIL_COND_V(!bclass, Ref<JavaClass>());
@@ -1149,6 +1151,7 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
singleton = this;
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
jclass activityClass = env->FindClass("android/app/Activity");
jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 41201db32b..ec3b6f8ac0 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -73,6 +73,7 @@ jobject GodotIOJavaWrapper::get_instance() {
Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
if (_open_URI) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, ERR_UNAVAILABLE);
jstring jStr = env->NewStringUTF(p_uri.utf8().get_data());
return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK;
} else {
@@ -83,6 +84,7 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
String GodotIOJavaWrapper::get_user_data_dir() {
if (_get_data_dir) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir);
return jstring_to_string(s, env);
} else {
@@ -93,6 +95,7 @@ String GodotIOJavaWrapper::get_user_data_dir() {
String GodotIOJavaWrapper::get_locale() {
if (_get_locale) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_locale);
return jstring_to_string(s, env);
} else {
@@ -103,6 +106,7 @@ String GodotIOJavaWrapper::get_locale() {
String GodotIOJavaWrapper::get_model() {
if (_get_model) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_model);
return jstring_to_string(s, env);
} else {
@@ -113,6 +117,7 @@ String GodotIOJavaWrapper::get_model() {
int GodotIOJavaWrapper::get_screen_dpi() {
if (_get_screen_DPI) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, 160);
return env->CallIntMethod(godot_io_instance, _get_screen_DPI);
} else {
return 160;
@@ -122,6 +127,7 @@ int GodotIOJavaWrapper::get_screen_dpi() {
void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) {
if (_screen_get_usable_rect) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect);
ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4);
jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE);
@@ -135,6 +141,7 @@ void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) {
String GodotIOJavaWrapper::get_unique_id() {
if (_get_unique_id) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id);
return jstring_to_string(s, env);
} else {
@@ -149,6 +156,7 @@ bool GodotIOJavaWrapper::has_vk() {
void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
}
@@ -157,6 +165,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int
void GodotIOJavaWrapper::hide_vk() {
if (_hide_keyboard) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
env->CallVoidMethod(godot_io_instance, _hide_keyboard);
}
}
@@ -164,6 +173,7 @@ void GodotIOJavaWrapper::hide_vk() {
void GodotIOJavaWrapper::set_screen_orientation(int p_orient) {
if (_set_screen_orientation) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
env->CallVoidMethod(godot_io_instance, _set_screen_orientation, p_orient);
}
}
@@ -171,6 +181,7 @@ void GodotIOJavaWrapper::set_screen_orientation(int p_orient) {
int GodotIOJavaWrapper::get_screen_orientation() {
if (_get_screen_orientation) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, 0);
return env->CallIntMethod(godot_io_instance, _get_screen_orientation);
} else {
return 0;
@@ -180,6 +191,7 @@ int GodotIOJavaWrapper::get_screen_orientation() {
String GodotIOJavaWrapper::get_system_dir(int p_dir) {
if (_get_system_dir) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String("."));
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir);
return jstring_to_string(s, env);
} else {
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index bb22162879..0c342dc280 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -127,9 +127,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc
if (p_cmdline) {
cmdlen = env->GetArrayLength(p_cmdline);
if (cmdlen) {
- cmdline = (const char **)malloc((cmdlen + 1) * sizeof(const char *));
+ cmdline = (const char **)memalloc((cmdlen + 1) * sizeof(const char *));
+ ERR_FAIL_NULL_MSG(cmdline, "Out of memory.");
cmdline[cmdlen] = nullptr;
- j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring));
+ j_cmdline = (jstring *)memalloc(cmdlen * sizeof(jstring));
+ ERR_FAIL_NULL_MSG(j_cmdline, "Out of memory.");
for (int i = 0; i < cmdlen; i++) {
jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i);
@@ -147,13 +149,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc
for (int i = 0; i < cmdlen; ++i) {
env->ReleaseStringUTFChars(j_cmdline[i], cmdline[i]);
}
- free(j_cmdline);
+ memfree(j_cmdline);
}
- free(cmdline);
+ memfree(cmdline);
}
if (err != OK) {
- return; //should exit instead and print the error
+ return; // should exit instead and print the error
}
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
@@ -215,9 +217,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl
if (step == 1) {
if (!Main::start()) {
- return; //should exit instead and print the error
+ return; // should exit instead and print the error
}
+ godot_java->on_godot_setup_completed(env);
os_android->main_loop_begin();
godot_java->on_godot_main_loop_started(env);
++step;
diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp
index 5b638300ef..6b5e44f371 100644
--- a/platform/android/java_godot_view_wrapper.cpp
+++ b/platform/android/java_godot_view_wrapper.cpp
@@ -34,6 +34,7 @@
GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
_godot_view = env->NewGlobalRef(godot_view);
@@ -48,6 +49,8 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
void GodotJavaViewWrapper::request_pointer_capture() {
if (_request_pointer_capture != 0) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(_godot_view, _request_pointer_capture);
}
}
@@ -55,12 +58,16 @@ void GodotJavaViewWrapper::request_pointer_capture() {
void GodotJavaViewWrapper::release_pointer_capture() {
if (_request_pointer_capture != 0) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(_godot_view, _release_pointer_capture);
}
}
GodotJavaViewWrapper::~GodotJavaViewWrapper() {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->DeleteGlobalRef(_godot_view);
env->DeleteGlobalRef(_cls);
}
diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h
index 548c278292..bfb4369fb8 100644
--- a/platform/android/java_godot_view_wrapper.h
+++ b/platform/android/java_godot_view_wrapper.h
@@ -34,6 +34,8 @@
#include <android/log.h>
#include <jni.h>
+#include "string_android.h"
+
// Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++
class GodotJavaViewWrapper {
private:
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 759980373a..bfd93345f3 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -74,6 +74,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z");
_vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V");
_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
+ _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
// get some Activity method pointers...
@@ -93,6 +94,8 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl
if (p_env == nullptr)
p_env = get_jni_env();
+ ERR_FAIL_COND_V(p_env == nullptr, nullptr);
+
jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class);
return p_env->GetStaticObjectField(godot_class, fid);
} else {
@@ -103,6 +106,8 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl
jobject GodotJavaWrapper::get_class_loader() {
if (_get_class_loader) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, nullptr);
+
return env->CallObjectMethod(activity, _get_class_loader);
} else {
return nullptr;
@@ -114,17 +119,30 @@ GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() {
return _godot_view;
}
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, nullptr);
+
jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
_godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter));
return _godot_view;
}
void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
- if (_on_video_init)
+ if (_on_video_init) {
if (p_env == nullptr)
p_env = get_jni_env();
+ ERR_FAIL_COND(p_env == nullptr);
- p_env->CallVoidMethod(godot_instance, _on_video_init);
+ p_env->CallVoidMethod(godot_instance, _on_video_init);
+ }
+}
+
+void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) {
+ if (_on_godot_setup_completed) {
+ if (p_env == nullptr) {
+ p_env = get_jni_env();
+ }
+ p_env->CallVoidMethod(godot_instance, _on_godot_setup_completed);
+ }
}
void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
@@ -132,29 +150,36 @@ void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
if (p_env == nullptr) {
p_env = get_jni_env();
}
+ ERR_FAIL_COND(p_env == nullptr);
+ p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started);
}
- p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started);
}
void GodotJavaWrapper::restart(JNIEnv *p_env) {
- if (_restart)
+ if (_restart) {
if (p_env == nullptr)
p_env = get_jni_env();
+ ERR_FAIL_COND(p_env == nullptr);
- p_env->CallVoidMethod(godot_instance, _restart);
+ p_env->CallVoidMethod(godot_instance, _restart);
+ }
}
void GodotJavaWrapper::force_quit(JNIEnv *p_env) {
- if (_finish)
+ if (_finish) {
if (p_env == nullptr)
p_env = get_jni_env();
+ ERR_FAIL_COND(p_env == nullptr);
- p_env->CallVoidMethod(godot_instance, _finish);
+ p_env->CallVoidMethod(godot_instance, _finish);
+ }
}
void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) {
if (_set_keep_screen_on) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(godot_instance, _set_keep_screen_on, p_enabled);
}
}
@@ -162,6 +187,8 @@ void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) {
void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
if (_alert) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
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);
@@ -170,6 +197,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
int GodotJavaWrapper::get_gles_version_code() {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, 0);
+
if (_get_GLES_version_code) {
return env->CallIntMethod(godot_instance, _get_GLES_version_code);
}
@@ -184,6 +213,8 @@ bool GodotJavaWrapper::has_get_clipboard() {
String GodotJavaWrapper::get_clipboard() {
if (_get_clipboard) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
+
jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard);
return jstring_to_string(s, env);
} else {
@@ -194,6 +225,8 @@ String GodotJavaWrapper::get_clipboard() {
String GodotJavaWrapper::get_input_fallback_mapping() {
if (_get_input_fallback_mapping) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
+
jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping);
return jstring_to_string(fallback_mapping, env);
} else {
@@ -208,6 +241,8 @@ bool GodotJavaWrapper::has_set_clipboard() {
void GodotJavaWrapper::set_clipboard(const String &p_text) {
if (_set_clipboard) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
jstring jStr = env->NewStringUTF(p_text.utf8().get_data());
env->CallVoidMethod(godot_instance, _set_clipboard, jStr);
}
@@ -216,6 +251,8 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) {
bool GodotJavaWrapper::request_permission(const String &p_name) {
if (_request_permission) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
+
jstring jStrName = env->NewStringUTF(p_name.utf8().get_data());
return env->CallBooleanMethod(godot_instance, _request_permission, jStrName);
} else {
@@ -226,6 +263,8 @@ bool GodotJavaWrapper::request_permission(const String &p_name) {
bool GodotJavaWrapper::request_permissions() {
if (_request_permissions) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
+
return env->CallBooleanMethod(godot_instance, _request_permissions);
} else {
return false;
@@ -236,6 +275,8 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const {
Vector<String> permissions_list;
if (_get_granted_permissions) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, permissions_list);
+
jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions);
jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object);
@@ -254,6 +295,8 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const {
void GodotJavaWrapper::init_input_devices() {
if (_init_input_devices) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(godot_instance, _init_input_devices);
}
}
@@ -261,6 +304,8 @@ void GodotJavaWrapper::init_input_devices() {
jobject GodotJavaWrapper::get_surface() {
if (_get_surface) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, nullptr);
+
return env->CallObjectMethod(godot_instance, _get_surface);
} else {
return nullptr;
@@ -270,6 +315,8 @@ jobject GodotJavaWrapper::get_surface() {
bool GodotJavaWrapper::is_activity_resumed() {
if (_is_activity_resumed) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
+
return env->CallBooleanMethod(godot_instance, _is_activity_resumed);
} else {
return false;
@@ -279,6 +326,8 @@ bool GodotJavaWrapper::is_activity_resumed() {
void GodotJavaWrapper::vibrate(int p_duration_ms) {
if (_vibrate) {
JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms);
}
}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 99a60dffa1..0e20747a16 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -66,6 +66,7 @@ private:
jmethodID _is_activity_resumed = 0;
jmethodID _vibrate = 0;
jmethodID _get_input_fallback_mapping = 0;
+ jmethodID _on_godot_setup_completed = 0;
jmethodID _on_godot_main_loop_started = 0;
jmethodID _get_class_loader = 0;
@@ -80,6 +81,7 @@ public:
GodotJavaViewWrapper *get_godot_view();
void on_video_init(JNIEnv *p_env = nullptr);
+ void on_godot_setup_completed(JNIEnv *p_env = nullptr);
void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
void restart(JNIEnv *p_env = nullptr);
void force_quit(JNIEnv *p_env = nullptr);
diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp
index afcc7294f2..ba379c8d43 100644
--- a/platform/android/thread_jandroid.cpp
+++ b/platform/android/thread_jandroid.cpp
@@ -30,17 +30,34 @@
#include "thread_jandroid.h"
+#include <android/log.h>
+
#include "core/os/thread.h"
static JavaVM *java_vm = nullptr;
static thread_local JNIEnv *env = nullptr;
+// The logic here need to improve, init_thread/term_tread are designed to work with Thread::callback
+// Calling init_thread from setup_android_thread and get_jni_env to setup an env we're keeping and not detaching
+// could cause issues on app termination.
+//
+// We should be making sure that any thread started calls a nice cleanup function when it's done,
+// especially now that we use many more threads.
+
static void init_thread() {
+ if (env) {
+ // thread never detached! just keep using...
+ return;
+ }
+
java_vm->AttachCurrentThread(&env, nullptr);
}
static void term_thread() {
java_vm->DetachCurrentThread();
+
+ // this is no longer valid, must called init_thread to re-establish
+ env = nullptr;
}
void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) {
@@ -50,9 +67,17 @@ void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) {
}
void setup_android_thread() {
- init_thread();
+ if (!env) {
+ // !BAS! see remarks above
+ init_thread();
+ }
}
JNIEnv *get_jni_env() {
+ if (!env) {
+ // !BAS! see remarks above
+ init_thread();
+ }
+
return env;
}