summaryrefslogtreecommitdiffstats
path: root/platform/android
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/SCsub7
-rw-r--r--platform/android/android_input_handler.cpp25
-rw-r--r--platform/android/android_input_handler.h6
-rw-r--r--platform/android/android_keys_utils.h4
-rw-r--r--platform/android/api/api.cpp3
-rw-r--r--platform/android/api/jni_singleton.h3
-rw-r--r--platform/android/audio_driver_opensl.cpp3
-rw-r--r--platform/android/detect.py17
-rw-r--r--platform/android/dir_access_jandroid.cpp3
-rw-r--r--platform/android/dir_access_jandroid.h4
-rw-r--r--platform/android/display_server_android.cpp23
-rw-r--r--platform/android/display_server_android.h3
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml590
-rw-r--r--platform/android/export/export.cpp7
-rw-r--r--platform/android/export/export.h1
-rw-r--r--platform/android/export/export_plugin.cpp335
-rw-r--r--platform/android/export/export_plugin.h19
-rw-r--r--platform/android/export/godot_plugin_config.cpp1
-rw-r--r--platform/android/export/gradle_export_util.cpp6
-rw-r--r--platform/android/export/logo.svg (renamed from platform/android/logo.svg)0
-rw-r--r--platform/android/export/run_icon.svg (renamed from platform/android/run_icon.svg)0
-rw-r--r--platform/android/file_access_android.h1
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp3
-rw-r--r--platform/android/file_access_filesystem_jandroid.h3
-rw-r--r--platform/android/java/app/AndroidManifest.xml2
-rw-r--r--platform/android/java/app/config.gradle21
-rw-r--r--platform/android/java/build.gradle65
-rw-r--r--platform/android/java/editor/build.gradle92
-rw-r--r--platform/android/java/editor/src/debug/res/values/strings.xml4
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml8
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt127
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt5
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt7
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--platform/android/java/lib/build.gradle24
-rw-r--r--platform/android/java/lib/res/xml/godot_provider_paths.xml4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java29
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java169
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java10
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java14
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java10
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt34
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java35
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java23
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt122
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java38
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java70
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java2
-rw-r--r--platform/android/java_godot_io_wrapper.h7
-rw-r--r--platform/android/java_godot_lib_jni.cpp86
-rw-r--r--platform/android/java_godot_lib_jni.h7
-rw-r--r--platform/android/java_godot_view_wrapper.cpp13
-rw-r--r--platform/android/java_godot_view_wrapper.h6
-rw-r--r--platform/android/java_godot_wrapper.cpp42
-rw-r--r--platform/android/java_godot_wrapper.h17
-rw-r--r--platform/android/jni_utils.h6
-rw-r--r--platform/android/os_android.cpp70
-rw-r--r--platform/android/os_android.h9
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp16
-rw-r--r--platform/android/string_android.h4
-rw-r--r--platform/android/thread_jandroid.cpp4
-rw-r--r--platform/android/tts_android.cpp42
-rw-r--r--platform/android/tts_android.h4
-rw-r--r--platform/android/vulkan_context_android.cpp (renamed from platform/android/vulkan/vulkan_context_android.cpp)4
-rw-r--r--platform/android/vulkan_context_android.h (renamed from platform/android/vulkan/vulkan_context_android.h)0
69 files changed, 1883 insertions, 479 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub
index e4d04f1df9..dfc921cc54 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -21,7 +21,7 @@ android_files = [
"android_keys_utils.cpp",
"display_server_android.cpp",
"plugin/godot_plugin_jni.cpp",
- "vulkan/vulkan_context_android.cpp",
+ "vulkan_context_android.cpp",
]
env_android = env.Clone()
@@ -56,7 +56,10 @@ if lib_arch_dir != "":
if env.dev_build:
lib_type_dir = "dev"
elif env.debug_features:
- lib_type_dir = "debug"
+ if env.editor_build and env["store_release"]:
+ lib_type_dir = "release"
+ else:
+ lib_type_dir = "debug"
else: # Release
lib_type_dir = "release"
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
index 63045237e9..8bf5eae2f8 100644
--- a/platform/android/android_input_handler.cpp
+++ b/platform/android/android_input_handler.cpp
@@ -64,7 +64,7 @@ void AndroidInputHandler::_set_key_modifier_state(Ref<InputEventWithModifiers> e
}
}
-void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicode, int p_key_label, bool p_pressed) {
+void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicode, int p_key_label, bool p_pressed, bool p_echo) {
static char32_t prev_wc = 0;
char32_t unicode = p_unicode;
if ((p_unicode & 0xfffffc00) == 0xd800) {
@@ -125,6 +125,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod
ev->set_key_label(fix_key_label(p_key_label, keycode));
ev->set_unicode(fix_unicode(unicode));
ev->set_pressed(p_pressed);
+ ev->set_echo(p_echo);
_set_key_modifier_state(ev, keycode);
@@ -138,22 +139,19 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod
}
void AndroidInputHandler::_cancel_all_touch() {
- _parse_all_touch(false, false, true);
+ _parse_all_touch(false, true);
touch.clear();
}
-void AndroidInputHandler::_parse_all_touch(bool p_pressed, bool p_double_tap, bool reset_index) {
+void AndroidInputHandler::_parse_all_touch(bool p_pressed, bool p_canceled, bool p_double_tap) {
if (touch.size()) {
//end all if exist
for (int i = 0; i < touch.size(); i++) {
Ref<InputEventScreenTouch> ev;
ev.instantiate();
- if (reset_index) {
- ev->set_index(-1);
- } else {
- ev->set_index(touch[i].id);
- }
+ ev->set_index(touch[i].id);
ev->set_pressed(p_pressed);
+ ev->set_canceled(p_canceled);
ev->set_position(touch[i].pos);
ev->set_double_tap(p_double_tap);
Input::get_singleton()->parse_input_event(ev);
@@ -180,7 +178,7 @@ void AndroidInputHandler::process_touch_event(int p_event, int p_pointer, const
}
//send touch
- _parse_all_touch(true, p_double_tap);
+ _parse_all_touch(true, false, p_double_tap);
} break;
case AMOTION_EVENT_ACTION_MOVE: { //motion
@@ -257,11 +255,11 @@ void AndroidInputHandler::process_touch_event(int p_event, int p_pointer, const
void AndroidInputHandler::_cancel_mouse_event_info(bool p_source_mouse_relative) {
buttons_state = BitField<MouseButtonMask>();
- _parse_mouse_event_info(BitField<MouseButtonMask>(), false, false, p_source_mouse_relative);
+ _parse_mouse_event_info(BitField<MouseButtonMask>(), false, true, false, p_source_mouse_relative);
mouse_event_info.valid = false;
}
-void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_double_click, bool p_source_mouse_relative) {
+void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_canceled, bool p_double_click, bool p_source_mouse_relative) {
if (!mouse_event_info.valid) {
return;
}
@@ -278,6 +276,7 @@ void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> even
hover_prev_pos = mouse_event_info.pos;
}
ev->set_pressed(p_pressed);
+ ev->set_canceled(p_canceled);
BitField<MouseButtonMask> changed_button_mask = BitField<MouseButtonMask>(buttons_state.operator int64_t() ^ event_buttons_mask.operator int64_t());
buttons_state = event_buttons_mask;
@@ -289,7 +288,7 @@ void AndroidInputHandler::_parse_mouse_event_info(BitField<MouseButtonMask> even
}
void AndroidInputHandler::_release_mouse_event_info(bool p_source_mouse_relative) {
- _parse_mouse_event_info(BitField<MouseButtonMask>(), false, false, p_source_mouse_relative);
+ _parse_mouse_event_info(BitField<MouseButtonMask>(), false, false, false, p_source_mouse_relative);
mouse_event_info.valid = false;
}
@@ -318,7 +317,7 @@ void AndroidInputHandler::process_mouse_event(int p_event_action, int p_event_an
mouse_event_info.valid = true;
mouse_event_info.pos = p_event_pos;
- _parse_mouse_event_info(event_buttons_mask, true, p_double_click, p_source_mouse_relative);
+ _parse_mouse_event_info(event_buttons_mask, true, false, p_double_click, p_source_mouse_relative);
} break;
case AMOTION_EVENT_ACTION_CANCEL: {
diff --git a/platform/android/android_input_handler.h b/platform/android/android_input_handler.h
index 2badd32636..c74c5020e3 100644
--- a/platform/android/android_input_handler.h
+++ b/platform/android/android_input_handler.h
@@ -83,13 +83,13 @@ private:
void _wheel_button_click(BitField<MouseButtonMask> event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor);
- void _parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_double_click, bool p_source_mouse_relative);
+ void _parse_mouse_event_info(BitField<MouseButtonMask> event_buttons_mask, bool p_pressed, bool p_canceled, bool p_double_click, bool p_source_mouse_relative);
void _release_mouse_event_info(bool p_source_mouse_relative = false);
void _cancel_mouse_event_info(bool p_source_mouse_relative = false);
- void _parse_all_touch(bool p_pressed, bool p_double_tap, bool reset_index = false);
+ void _parse_all_touch(bool p_pressed, bool p_canceled = false, bool p_double_tap = false);
void _release_all_touch();
@@ -101,7 +101,7 @@ public:
void process_magnify(Point2 p_pos, float p_factor);
void process_pan(Point2 p_pos, Vector2 p_delta);
void process_joy_event(JoypadEvent p_event);
- void process_key_event(int p_physical_keycode, int p_unicode, int p_key_label, bool p_pressed);
+ void process_key_event(int p_physical_keycode, int p_unicode, int p_key_label, bool p_pressed, bool p_echo);
};
#endif // ANDROID_INPUT_HANDLER_H
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h
index 3a587dd680..5cf5628a8b 100644
--- a/platform/android/android_keys_utils.h
+++ b/platform/android/android_keys_utils.h
@@ -31,8 +31,9 @@
#ifndef ANDROID_KEYS_UTILS_H
#define ANDROID_KEYS_UTILS_H
+#include "core/os/keyboard.h"
+
#include <android/input.h>
-#include <core/os/keyboard.h>
#define AKEYCODE_MAX 0xFFFF
@@ -60,6 +61,7 @@ static AndroidGodotCodePair android_godot_code_pairs[] = {
{ AKEYCODE_DPAD_DOWN, Key::DOWN }, // (20) Directional Pad Down key.
{ AKEYCODE_DPAD_LEFT, Key::LEFT }, // (21) Directional Pad Left key.
{ AKEYCODE_DPAD_RIGHT, Key::RIGHT }, // (22) Directional Pad Right key.
+ { AKEYCODE_DPAD_CENTER, Key::ENTER }, // (23) Directional Pad Center key.
{ AKEYCODE_VOLUME_UP, Key::VOLUMEUP }, // (24) Volume Up key.
{ AKEYCODE_VOLUME_DOWN, Key::VOLUMEDOWN }, // (25) Volume Down key.
{ AKEYCODE_POWER, Key::STANDBY }, // (26) Power key.
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index 757ca315a7..60c369951c 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -30,10 +30,11 @@
#include "api.h"
-#include "core/config/engine.h"
#include "java_class_wrapper.h"
#include "jni_singleton.h"
+#include "core/config/engine.h"
+
#if !defined(ANDROID_ENABLED)
static JavaClassWrapper *java_class_wrapper = nullptr;
#endif
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index 455ed259ec..a2d1c08168 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -33,8 +33,9 @@
#include "core/config/engine.h"
#include "core/variant/variant.h"
+
#ifdef ANDROID_ENABLED
-#include "platform/android/jni_utils.h"
+#include "jni_utils.h"
#endif
class JNISingleton : public Object {
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index 5fc32132e3..51e89c720d 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -272,7 +272,8 @@ Error AudioDriverOpenSL::input_start() {
return init_input_device();
}
- return OK;
+ WARN_PRINT("Unable to start audio capture - No RECORD_AUDIO permission");
+ return ERR_UNAUTHORIZED;
}
Error AudioDriverOpenSL::input_stop() {
diff --git a/platform/android/detect.py b/platform/android/detect.py
index ec36a40941..2860898e5c 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -9,10 +9,6 @@ if TYPE_CHECKING:
from SCons import Environment
-def is_active():
- return True
-
-
def get_name():
return "Android"
@@ -22,6 +18,8 @@ def can_build():
def get_opts():
+ from SCons.Variables import BoolVariable
+
return [
("ANDROID_SDK_ROOT", "Path to the Android SDK", get_env_android_sdk_root()),
(
@@ -29,9 +27,20 @@ def get_opts():
'Target platform (android-<api>, e.g. "android-' + str(get_min_target_api()) + '")',
"android-" + str(get_min_target_api()),
),
+ BoolVariable("store_release", "Editor build for Google Play Store (for official builds only)", False),
]
+def get_doc_classes():
+ return [
+ "EditorExportPlatformAndroid",
+ ]
+
+
+def get_doc_path():
+ return "doc_classes"
+
+
# Return the ANDROID_SDK_ROOT environment variable.
def get_env_android_sdk_root():
return os.environ.get("ANDROID_SDK_ROOT", -1)
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 7b41ad87bd..6bd09fe00a 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -30,10 +30,11 @@
#include "dir_access_jandroid.h"
-#include "core/string/print_string.h"
#include "string_android.h"
#include "thread_jandroid.h"
+#include "core/string/print_string.h"
+
jobject DirAccessJAndroid::dir_access_handler = nullptr;
jclass DirAccessJAndroid::cls = nullptr;
jmethodID DirAccessJAndroid::_dir_open = nullptr;
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index 05b7d47957..5ee4c85659 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -31,9 +31,11 @@
#ifndef DIR_ACCESS_JANDROID_H
#define DIR_ACCESS_JANDROID_H
+#include "java_godot_lib_jni.h"
+
#include "core/io/dir_access.h"
#include "drivers/unix/dir_access_unix.h"
-#include "java_godot_lib_jni.h"
+
#include <stdio.h>
/// Android implementation of the DirAccess interface used to provide access to
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index af4ba1255b..f02b292868 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -30,19 +30,23 @@
#include "display_server_android.h"
-#include "core/config/project_settings.h"
#include "java_godot_io_wrapper.h"
#include "java_godot_wrapper.h"
#include "os_android.h"
#include "tts_android.h"
+#include "core/config/project_settings.h"
+
#if defined(VULKAN_ENABLED)
+#include "vulkan_context_android.h"
+
#include "drivers/vulkan/rendering_device_vulkan.h"
-#include "platform/android/vulkan/vulkan_context_android.h"
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#endif
+
#ifdef GLES3_ENABLED
#include "drivers/gles3/rasterizer_gles3.h"
+
#include <EGL/egl.h>
#endif
@@ -449,6 +453,10 @@ void DisplayServerAndroid::window_move_to_foreground(DisplayServer::WindowID p_w
// Not supported on Android.
}
+bool DisplayServerAndroid::window_is_focused(WindowID p_window) const {
+ return true;
+}
+
bool DisplayServerAndroid::window_can_draw(DisplayServer::WindowID p_window) const {
return true;
}
@@ -676,16 +684,19 @@ void DisplayServerAndroid::cursor_set_custom_image(const Ref<Resource> &p_cursor
void DisplayServerAndroid::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
#if defined(VULKAN_ENABLED)
- context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
+ if (context_vulkan) {
+ context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
+ }
#endif
}
DisplayServer::VSyncMode DisplayServerAndroid::window_get_vsync_mode(WindowID p_window) const {
#if defined(VULKAN_ENABLED)
- return context_vulkan->get_vsync_mode(p_window);
-#else
- return DisplayServer::VSYNC_ENABLED;
+ if (context_vulkan) {
+ return context_vulkan->get_vsync_mode(p_window);
+ }
#endif
+ return DisplayServer::VSYNC_ENABLED;
}
void DisplayServerAndroid::reset_swap_buffers_flag() {
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index b2400d81dc..e0ad2cb916 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -39,6 +39,8 @@ class RenderingDeviceVulkan;
#endif
class DisplayServerAndroid : public DisplayServer {
+ // No need to register with GDCLASS, it's platform-specific and nothing is added.
+
String rendering_driver;
// https://developer.android.com/reference/android/view/PointerIcon
@@ -176,6 +178,7 @@ public:
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
new file mode 100644
index 0000000000..6d3affed15
--- /dev/null
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -0,0 +1,590 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPlatformAndroid" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Exporter for Android.
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ <link title="Exporting for Android">$DOCS_URL/tutorials/export/exporting_for_android.html</link>
+ <link title="Custom builds for Android">$DOCS_URL/tutorials/export/android_custom_build.html</link>
+ </tutorials>
+ <members>
+ <member name="apk_expansion/SALT" type="String" setter="" getter="">
+ Array of random bytes that the licensing Policy uses to create an [url=https://developer.android.com/google/play/licensing/adding-licensing#impl-Obfuscator]Obfuscator[/url].
+ </member>
+ <member name="apk_expansion/enable" type="bool" setter="" getter="">
+ If [code]true[/code], project resources are stored in the separate APK expansion file, instead APK.
+ [b]Note:[/b] APK expansion should be enabled to use PCK encryption.
+ </member>
+ <member name="apk_expansion/public_key" type="String" setter="" getter="">
+ Base64 encoded RSA public key for your publisher account, available from the profile page on the "Play Console".
+ </member>
+ <member name="architectures/arm64-v8a" type="bool" setter="" getter="">
+ If [code]true[/code], [code]arm64[/code] binaries are included into exported project.
+ </member>
+ <member name="architectures/armeabi-v7a" type="bool" setter="" getter="">
+ If [code]true[/code], [code]arm32[/code] binaries are included into exported project.
+ </member>
+ <member name="architectures/x86" type="bool" setter="" getter="">
+ If [code]true[/code], [code]x86_32[/code] binaries are included into exported project.
+ </member>
+ <member name="architectures/x86_64" type="bool" setter="" getter="">
+ If [code]true[/code], [code]x86_64[/code] binaries are included into exported project.
+ </member>
+ <member name="command_line/extra_args" type="String" setter="" getter="">
+ A list of additional command line arguments, exported project will receive when started.
+ </member>
+ <member name="custom_template/debug" type="String" setter="" getter="">
+ Path to the custom export template. If left empty, default template is used.
+ </member>
+ <member name="custom_template/release" type="String" setter="" getter="">
+ Path to the custom export template. If left empty, default template is used.
+ </member>
+ <member name="gradle_build/export_format" type="int" setter="" getter="">
+ Export format for Gradle build.
+ </member>
+ <member name="gradle_build/min_sdk" type="String" setter="" getter="">
+ Minimal Android SDK version for Gradle build.
+ </member>
+ <member name="gradle_build/target_sdk" type="String" setter="" getter="">
+ Target Android SDK version for Gradle build.
+ </member>
+ <member name="gradle_build/use_gradle_build" type="bool" setter="" getter="">
+ If [code]true[/code], Gradle build is used instead of pre-built APK.
+ </member>
+ <member name="graphics/opengl_debug" type="bool" setter="" getter="">
+ If [code]true[/code], OpenGL ES debug context will be created (additional runtime checking, validation, and logging).
+ </member>
+ <member name="keystore/debug" type="String" setter="" getter="">
+ Path of the debug keystore file.
+ Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_PATH[/code].
+ Fallbacks to [code]EditorSettings.export/android/debug_keystore[/code] if empty.
+ </member>
+ <member name="keystore/debug_password" type="String" setter="" getter="">
+ Password for the debug keystore file.
+ Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_PASSWORD[/code].
+ Fallbacks to [code]EditorSettings.export/android/debug_keystore_pass[/code] if both it and [member keystore/debug] are empty.
+ </member>
+ <member name="keystore/debug_user" type="String" setter="" getter="">
+ User name for the debug keystore file.
+ Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_USER[/code].
+ Fallbacks to [code]EditorSettings.export/android/debug_keystore_user[/code] if both it and [member keystore/debug] are empty.
+ </member>
+ <member name="keystore/release" type="String" setter="" getter="">
+ Path of the release keystore file.
+ Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_RELEASE_PATH[/code].
+ </member>
+ <member name="keystore/release_password" type="String" setter="" getter="">
+ Password for the release keystore file.
+ Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD[/code].
+ </member>
+ <member name="keystore/release_user" type="String" setter="" getter="">
+ User name for the release keystore file.
+ Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_RELEASE_USER[/code].
+ </member>
+ <member name="launcher_icons/adaptive_background_432x432" type="String" setter="" getter="">
+ Background layer of the application adaptive icon file.
+ </member>
+ <member name="launcher_icons/adaptive_foreground_432x432" type="String" setter="" getter="">
+ Foreground layer of the application adaptive icon file.
+ </member>
+ <member name="launcher_icons/main_192x192" type="String" setter="" getter="">
+ Application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/icon].
+ </member>
+ <member name="package/app_category" type="int" setter="" getter="">
+ Application category for the Play Store.
+ </member>
+ <member name="package/exclude_from_recents" type="bool" setter="" getter="">
+ If [code]true[/code], task initiated by main activity will be excluded from the list of recently used applications.
+ </member>
+ <member name="package/name" type="String" setter="" getter="">
+ Name of the application.
+ </member>
+ <member name="package/retain_data_on_uninstall" type="bool" setter="" getter="">
+ If [code]true[/code], when the user uninstalls an app, a prompt to keep the app's data will be shown.
+ </member>
+ <member name="package/signed" type="bool" setter="" getter="">
+ If [code]true[/code], package signing is enabled.
+ </member>
+ <member name="package/unique_name" type="String" setter="" getter="">
+ Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]).
+ </member>
+ <member name="permissions/access_checkin_properties" type="bool" setter="" getter="">
+ Allows read/write access to the "properties" table in the checkin database. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_CHECKIN_PROPERTIES]ACCESS_CHECKIN_PROPERTIES[/url].
+ </member>
+ <member name="permissions/access_coarse_location" type="bool" setter="" getter="">
+ Allows access to the approximate location information. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_COARSE_LOCATION]ACCESS_COARSE_LOCATION[/url].
+ </member>
+ <member name="permissions/access_fine_location" type="bool" setter="" getter="">
+ Allows access to the precise location information. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_FINE_LOCATION]ACCESS_FINE_LOCATION[/url].
+ </member>
+ <member name="permissions/access_location_extra_commands" type="bool" setter="" getter="">
+ Allows access to the extra location provider commands. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_LOCATION_EXTRA_COMMANDS]ACCESS_LOCATION_EXTRA_COMMANDS[/url].
+ </member>
+ <member name="permissions/access_mock_location" type="bool" setter="" getter="">
+ Allows an application to create mock location providers for testing.
+ </member>
+ <member name="permissions/access_network_state" type="bool" setter="" getter="">
+ Allows access to the information about networks. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_NETWORK_STATE]ACCESS_NETWORK_STATE[/url].
+ </member>
+ <member name="permissions/access_surface_flinger" type="bool" setter="" getter="">
+ Allows an application to use SurfaceFlinger's low level features.
+ </member>
+ <member name="permissions/access_wifi_state" type="bool" setter="" getter="">
+ Allows access to the information about Wi-Fi networks. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_WIFI_STATE]ACCESS_WIFI_STATE[/url].
+ </member>
+ <member name="permissions/account_manager" type="bool" setter="" getter="">
+ Allows applications to call into AccountAuthenticators. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCOUNT_MANAGER]ACCOUNT_MANAGER[/url].
+ </member>
+ <member name="permissions/add_voicemail" type="bool" setter="" getter="">
+ Allows an application to add voicemails into the system. See [url=https://developer.android.com/reference/android/Manifest.permission#ADD_VOICEMAIL]ADD_VOICEMAIL[/url].
+ </member>
+ <member name="permissions/authenticate_accounts" type="bool" setter="" getter="">
+ Allows an application to act as an AccountAuthenticator for the AccountManager.
+ </member>
+ <member name="permissions/battery_stats" type="bool" setter="" getter="">
+ Allows an application to collect battery statistics. Sett [url=https://developer.android.com/reference/android/Manifest.permission#BATTERY_STATS]BATTERY_STATS[/url].
+ </member>
+ <member name="permissions/bind_accessibility_service" type="bool" setter="" getter="">
+ Must be required by an AccessibilityService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_ACCESSIBILITY_SERVICE]BIND_ACCESSIBILITY_SERVICE[/url].
+ </member>
+ <member name="permissions/bind_appwidget" type="bool" setter="" getter="">
+ Allows an application to tell the AppWidget service which application can access AppWidget's data. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_APPWIDGET]BIND_APPWIDGET[/url].
+ </member>
+ <member name="permissions/bind_device_admin" type="bool" setter="" getter="">
+ Must be required by device administration receiver, to ensure that only the system can interact with it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_DEVICE_ADMIN]BIND_DEVICE_ADMIN[/url].
+ </member>
+ <member name="permissions/bind_input_method" type="bool" setter="" getter="">
+ Must be required by an InputMethodService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_INPUT_METHOD]BIND_INPUT_METHOD[/url].
+ </member>
+ <member name="permissions/bind_nfc_service" type="bool" setter="" getter="">
+ Must be required by a HostApduService or OffHostApduService to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_NFC_SERVICE]BIND_NFC_SERVICE[/url].
+ </member>
+ <member name="permissions/bind_notification_listener_service" type="bool" setter="" getter="">
+ Must be required by a NotificationListenerService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE]BIND_NOTIFICATION_LISTENER_SERVICE[/url].
+ </member>
+ <member name="permissions/bind_print_service" type="bool" setter="" getter="">
+ Must be required by a PrintService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_PRINT_SERVICE]BIND_PRINT_SERVICE[/url].
+ </member>
+ <member name="permissions/bind_remoteviews" type="bool" setter="" getter="">
+ Must be required by a RemoteViewsService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_REMOTEVIEWS]BIND_REMOTEVIEWS[/url].
+ </member>
+ <member name="permissions/bind_text_service" type="bool" setter="" getter="">
+ Must be required by a TextService (e.g. SpellCheckerService) to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_TEXT_SERVICE]BIND_TEXT_SERVICE[/url].
+ </member>
+ <member name="permissions/bind_vpn_service" type="bool" setter="" getter="">
+ Must be required by a VpnService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_VPN_SERVICE]BIND_VPN_SERVICE[/url].
+ </member>
+ <member name="permissions/bind_wallpaper" type="bool" setter="" getter="">
+ Must be required by a WallpaperService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_WALLPAPER]BIND_WALLPAPER[/url].
+ </member>
+ <member name="permissions/bluetooth" type="bool" setter="" getter="">
+ Allows applications to connect to paired bluetooth devices. See [url=https://developer.android.com/reference/android/Manifest.permission#BLUETOOTH]BLUETOOTH[/url].
+ </member>
+ <member name="permissions/bluetooth_admin" type="bool" setter="" getter="">
+ Allows applications to discover and pair bluetooth devices. See [url=https://developer.android.com/reference/android/Manifest.permission#BLUETOOTH_ADMIN]BLUETOOTH_ADMIN[/url].
+ </member>
+ <member name="permissions/bluetooth_privileged" type="bool" setter="" getter="">
+ Allows applications to pair bluetooth devices without user interaction, and to allow or disallow phonebook access or message access. See [url=https://developer.android.com/reference/android/Manifest.permission#BLUETOOTH_PRIVILEGED]BLUETOOTH_PRIVILEGED[/url].
+ </member>
+ <member name="permissions/brick" type="bool" setter="" getter="">
+ Required to be able to disable the device (very dangerous!).
+ </member>
+ <member name="permissions/broadcast_package_removed" type="bool" setter="" getter="">
+ Allows an application to broadcast a notification that an application package has been removed. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_PACKAGE_REMOVED]BROADCAST_PACKAGE_REMOVED[/url].
+ </member>
+ <member name="permissions/broadcast_sms" type="bool" setter="" getter="">
+ Allows an application to broadcast an SMS receipt notification. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_SMS]BROADCAST_SMS[/url].
+ </member>
+ <member name="permissions/broadcast_sticky" type="bool" setter="" getter="">
+ Allows an application to broadcast sticky intents. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_STICKY]BROADCAST_STICKY[/url].
+ </member>
+ <member name="permissions/broadcast_wap_push" type="bool" setter="" getter="">
+ Allows an application to broadcast a WAP PUSH receipt notification. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_WAP_PUSH]BROADCAST_WAP_PUSH[/url].
+ </member>
+ <member name="permissions/call_phone" type="bool" setter="" getter="">
+ Allows an application to initiate a phone call without going through the Dialer user interface. See [url=https://developer.android.com/reference/android/Manifest.permission#CALL_PHONE]CALL_PHONE[/url].
+ </member>
+ <member name="permissions/call_privileged" type="bool" setter="" getter="">
+ Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface. See [url=https://developer.android.com/reference/android/Manifest.permission#CALL_PRIVILEGED]CALL_PRIVILEGED[/url].
+ </member>
+ <member name="permissions/camera" type="bool" setter="" getter="">
+ Required to be able to access the camera device. See [url=https://developer.android.com/reference/android/Manifest.permission#CAMERA]CAMERA[/url].
+ </member>
+ <member name="permissions/capture_audio_output" type="bool" setter="" getter="">
+ Allows an application to capture audio output. See [url=https://developer.android.com/reference/android/Manifest.permission#CAPTURE_AUDIO_OUTPUT]CAPTURE_AUDIO_OUTPUT[/url].
+ </member>
+ <member name="permissions/capture_secure_video_output" type="bool" setter="" getter="">
+ Allows an application to capture secure video output.
+ </member>
+ <member name="permissions/capture_video_output" type="bool" setter="" getter="">
+ Allows an application to capture video output.
+ </member>
+ <member name="permissions/change_component_enabled_state" type="bool" setter="" getter="">
+ Allows an application to change whether an application component (other than its own) is enabled or not. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_COMPONENT_ENABLED_STATE]CHANGE_COMPONENT_ENABLED_STATE[/url].
+ </member>
+ <member name="permissions/change_configuration" type="bool" setter="" getter="">
+ Allows an application to modify the current configuration, such as locale. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_CONFIGURATION]CHANGE_CONFIGURATION[/url].
+ </member>
+ <member name="permissions/change_network_state" type="bool" setter="" getter="">
+ Allows applications to change network connectivity state. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_NETWORK_STATE]CHANGE_NETWORK_STATE[/url].
+ </member>
+ <member name="permissions/change_wifi_multicast_state" type="bool" setter="" getter="">
+ Allows applications to enter Wi-Fi Multicast mode. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_WIFI_MULTICAST_STATE]CHANGE_WIFI_MULTICAST_STATE[/url].
+ </member>
+ <member name="permissions/change_wifi_state" type="bool" setter="" getter="">
+ Allows applications to change Wi-Fi connectivity state. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_WIFI_STATE]CHANGE_WIFI_STATE[/url].
+ </member>
+ <member name="permissions/clear_app_cache" type="bool" setter="" getter="">
+ Allows an application to clear the caches of all installed applications on the device. See [url=https://developer.android.com/reference/android/Manifest.permission#CLEAR_APP_CACHE]CLEAR_APP_CACHE[/url].
+ </member>
+ <member name="permissions/clear_app_user_data" type="bool" setter="" getter="">
+ Allows an application to clear user data.
+ </member>
+ <member name="permissions/control_location_updates" type="bool" setter="" getter="">
+ Allows enabling/disabling location update notifications from the radio. See [url=https://developer.android.com/reference/android/Manifest.permission#CONTROL_LOCATION_UPDATES]CONTROL_LOCATION_UPDATES[/url].
+ </member>
+ <member name="permissions/custom_permissions" type="PackedStringArray" setter="" getter="">
+ Array of custom permission strings.
+ </member>
+ <member name="permissions/delete_cache_files" type="bool" setter="" getter="">
+ Deprecated.
+ </member>
+ <member name="permissions/delete_packages" type="bool" setter="" getter="">
+ Allows an application to delete packages. See [url=https://developer.android.com/reference/android/Manifest.permission#DELETE_PACKAGES]DELETE_PACKAGES[/url].
+ </member>
+ <member name="permissions/device_power" type="bool" setter="" getter="">
+ Allows low-level access to power management.
+ </member>
+ <member name="permissions/diagnostic" type="bool" setter="" getter="">
+ Allows applications to RW to diagnostic resources. See [url=https://developer.android.com/reference/android/Manifest.permission#DIAGNOSTIC]DIAGNOSTIC[/url].
+ </member>
+ <member name="permissions/disable_keyguard" type="bool" setter="" getter="">
+ Allows applications to disable the keyguard if it is not secure. See [url=https://developer.android.com/reference/android/Manifest.permission#DISABLE_KEYGUARD]DISABLE_KEYGUARD[/url].
+ </member>
+ <member name="permissions/dump" type="bool" setter="" getter="">
+ Allows an application to retrieve state dump information from system services. See [url=https://developer.android.com/reference/android/Manifest.permission#DUMP]DUMP[/url].
+ </member>
+ <member name="permissions/expand_status_bar" type="bool" setter="" getter="">
+ Allows an application to expand or collapse the status bar. See [url=https://developer.android.com/reference/android/Manifest.permission#EXPAND_STATUS_BAR]EXPAND_STATUS_BAR[/url].
+ </member>
+ <member name="permissions/factory_test" type="bool" setter="" getter="">
+ Run as a manufacturer test application, running as the root user. See [url=https://developer.android.com/reference/android/Manifest.permission#FACTORY_TEST]FACTORY_TEST[/url].
+ </member>
+ <member name="permissions/flashlight" type="bool" setter="" getter="">
+ Allows access to the flashlight.
+ </member>
+ <member name="permissions/force_back" type="bool" setter="" getter="">
+ Allows an application to force a BACK operation on whatever is the top activity.
+ </member>
+ <member name="permissions/get_accounts" type="bool" setter="" getter="">
+ Allows access to the list of accounts in the Accounts Service. See [url=https://developer.android.com/reference/android/Manifest.permission#GET_ACCOUNTS]GET_ACCOUNTS[/url].
+ </member>
+ <member name="permissions/get_package_size" type="bool" setter="" getter="">
+ Allows an application to find out the space used by any package. See [url=https://developer.android.com/reference/android/Manifest.permission#GET_PACKAGE_SIZE]GET_PACKAGE_SIZE[/url].
+ </member>
+ <member name="permissions/get_tasks" type="bool" setter="" getter="">
+ Deprecated in API level 21.
+ </member>
+ <member name="permissions/get_top_activity_info" type="bool" setter="" getter="">
+ Allows an application to retrieve private information about the current top activity.
+ </member>
+ <member name="permissions/global_search" type="bool" setter="" getter="">
+ Used on content providers to allow the global search system to access their data. See [url=https://developer.android.com/reference/android/Manifest.permission#GLOBAL_SEARCH]GLOBAL_SEARCH[/url].
+ </member>
+ <member name="permissions/hardware_test" type="bool" setter="" getter="">
+ Allows access to hardware peripherals.
+ </member>
+ <member name="permissions/inject_events" type="bool" setter="" getter="">
+ Allows an application to inject user events (keys, touch, trackball) into the event stream and deliver them to ANY window.
+ </member>
+ <member name="permissions/install_location_provider" type="bool" setter="" getter="">
+ Allows an application to install a location provider into the Location Manager. See [url=https://developer.android.com/reference/android/Manifest.permission#INSTALL_LOCATION_PROVIDER]INSTALL_LOCATION_PROVIDER[/url].
+ </member>
+ <member name="permissions/install_packages" type="bool" setter="" getter="">
+ Allows an application to install packages. See [url=https://developer.android.com/reference/android/Manifest.permission#INSTALL_PACKAGES]INSTALL_PACKAGES[/url].
+ </member>
+ <member name="permissions/install_shortcut" type="bool" setter="" getter="">
+ Allows an application to install a shortcut in Launcher. See [url=https://developer.android.com/reference/android/Manifest.permission#INSTALL_SHORTCUT]INSTALL_SHORTCUT[/url].
+ </member>
+ <member name="permissions/internal_system_window" type="bool" setter="" getter="">
+ Allows an application to open windows that are for use by parts of the system user interface.
+ </member>
+ <member name="permissions/internet" type="bool" setter="" getter="">
+ Allows applications to open network sockets. See [url=https://developer.android.com/reference/android/Manifest.permission#INTERNET]INTERNET[/url].
+ </member>
+ <member name="permissions/kill_background_processes" type="bool" setter="" getter="">
+ Allows an application to call ActivityManager.killBackgroundProcesses(String). See [url=https://developer.android.com/reference/android/Manifest.permission#KILL_BACKGROUND_PROCESSES]KILL_BACKGROUND_PROCESSES[/url].
+ </member>
+ <member name="permissions/location_hardware" type="bool" setter="" getter="">
+ Allows an application to use location features in hardware, such as the geofencing api. See [url=https://developer.android.com/reference/android/Manifest.permission#LOCATION_HARDWARE]LOCATION_HARDWARE[/url].
+ </member>
+ <member name="permissions/manage_accounts" type="bool" setter="" getter="">
+ Allows an application to manage the list of accounts in the AccountManager.
+ </member>
+ <member name="permissions/manage_app_tokens" type="bool" setter="" getter="">
+ Allows an application to manage (create, destroy, Z-order) application tokens in the window manager.
+ </member>
+ <member name="permissions/manage_documents" type="bool" setter="" getter="">
+ Allows an application to manage access to documents, usually as part of a document picker. See [url=https://developer.android.com/reference/android/Manifest.permission#MANAGE_DOCUMENTS]MANAGE_DOCUMENTS[/url].
+ </member>
+ <member name="permissions/manage_external_storage" type="bool" setter="" getter="">
+ Allows an application a broad access to external storage in scoped storage. See [url=https://developer.android.com/reference/android/Manifest.permission#MANAGE_EXTERNAL_STORAGE]MANAGE_EXTERNAL_STORAGE[/url].
+ </member>
+ <member name="permissions/master_clear" type="bool" setter="" getter="">
+ See [url=https://developer.android.com/reference/android/Manifest.permission#MASTER_CLEAR]MASTER_CLEAR[/url].
+ </member>
+ <member name="permissions/media_content_control" type="bool" setter="" getter="">
+ Allows an application to know what content is playing and control its playback. See [url=https://developer.android.com/reference/android/Manifest.permission#MEDIA_CONTENT_CONTROL]MEDIA_CONTENT_CONTROL[/url].
+ </member>
+ <member name="permissions/modify_audio_settings" type="bool" setter="" getter="">
+ Allows an application to modify global audio settings. See [url=https://developer.android.com/reference/android/Manifest.permission#MODIFY_AUDIO_SETTINGS]MODIFY_AUDIO_SETTINGS[/url].
+ </member>
+ <member name="permissions/modify_phone_state" type="bool" setter="" getter="">
+ Allows modification of the telephony state - power on, mmi, etc. Does not include placing calls. See [url=https://developer.android.com/reference/android/Manifest.permission#MODIFY_PHONE_STATE]MODIFY_PHONE_STATE[/url].
+ </member>
+ <member name="permissions/mount_format_filesystems" type="bool" setter="" getter="">
+ Allows formatting file systems for removable storage. See [url=https://developer.android.com/reference/android/Manifest.permission#MOUNT_FORMAT_FILESYSTEMS]MOUNT_FORMAT_FILESYSTEMS[/url].
+ </member>
+ <member name="permissions/mount_unmount_filesystems" type="bool" setter="" getter="">
+ Allows mounting and unmounting file systems for removable storage. See [url=https://developer.android.com/reference/android/Manifest.permission#MOUNT_UNMOUNT_FILESYSTEMS]MOUNT_UNMOUNT_FILESYSTEMS[/url].
+ </member>
+ <member name="permissions/nfc" type="bool" setter="" getter="">
+ Allows applications to perform I/O operations over NFC. See [url=https://developer.android.com/reference/android/Manifest.permission#NFC]NFC[/url].
+ </member>
+ <member name="permissions/persistent_activity" type="bool" setter="" getter="">
+ Allow an application to make its activities persistent.
+ Deprecated in API level 15.
+ </member>
+ <member name="permissions/process_outgoing_calls" type="bool" setter="" getter="">
+ Allows an application to see the number being dialed during an outgoing call with the option to redirect the call to a different number or abort the call altogether. See [url=https://developer.android.com/reference/android/Manifest.permission#PROCESS_OUTGOING_CALLS]PROCESS_OUTGOING_CALLS[/url].
+ Deprecated in API level 29.
+ </member>
+ <member name="permissions/read_calendar" type="bool" setter="" getter="">
+ Allows an application to read the user's calendar data. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_CALENDAR]READ_CALENDAR[/url].
+ </member>
+ <member name="permissions/read_call_log" type="bool" setter="" getter="">
+ Allows an application to read the user's call log. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_CALL_LOG]READ_CALL_LOG[/url].
+ </member>
+ <member name="permissions/read_contacts" type="bool" setter="" getter="">
+ Allows an application to read the user's contacts data. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_CONTACTS]READ_CONTACTS[/url].
+ </member>
+ <member name="permissions/read_external_storage" type="bool" setter="" getter="">
+ Allows an application to read from external storage. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE]READ_EXTERNAL_STORAGE[/url].
+ Deprecated in API level 33.
+ </member>
+ <member name="permissions/read_frame_buffer" type="bool" setter="" getter="">
+ Allows an application to take screen shots and more generally get access to the frame buffer data.
+ </member>
+ <member name="permissions/read_history_bookmarks" type="bool" setter="" getter="">
+ Allows an application to read (but not write) the user's browsing history and bookmarks.
+ </member>
+ <member name="permissions/read_input_state" type="bool" setter="" getter="">
+ Deprecated in API level 16.
+ </member>
+ <member name="permissions/read_logs" type="bool" setter="" getter="">
+ Allows an application to read the low-level system log files. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_LOGS]READ_LOGS[/url].
+ </member>
+ <member name="permissions/read_phone_state" type="bool" setter="" getter="">
+ Allows read only access to phone state. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_PHONE_STATE]READ_PHONE_STATE[/url].
+ </member>
+ <member name="permissions/read_profile" type="bool" setter="" getter="">
+ Allows an application to read the user's personal profile data.
+ </member>
+ <member name="permissions/read_sms" type="bool" setter="" getter="">
+ Allows an application to read SMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_SMS]READ_SMS[/url].
+ </member>
+ <member name="permissions/read_social_stream" type="bool" setter="" getter="">
+ Allows an application to read from the user's social stream.
+ </member>
+ <member name="permissions/read_sync_settings" type="bool" setter="" getter="">
+ Allows applications to read the sync settings. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_SYNC_SETTINGS]READ_SYNC_SETTINGS[/url].
+ </member>
+ <member name="permissions/read_sync_stats" type="bool" setter="" getter="">
+ Allows applications to read the sync stats. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_SYNC_STATS]READ_SYNC_STATS[/url].
+ </member>
+ <member name="permissions/read_user_dictionary" type="bool" setter="" getter="">
+ Allows an application to read the user dictionary.
+ </member>
+ <member name="permissions/reboot" type="bool" setter="" getter="">
+ Required to be able to reboot the device. See [url=https://developer.android.com/reference/android/Manifest.permission#REBOOT]REBOOT[/url].
+ </member>
+ <member name="permissions/receive_boot_completed" type="bool" setter="" getter="">
+ Allows an application to receive the Intent.ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_BOOT_COMPLETED]RECEIVE_BOOT_COMPLETED[/url].
+ </member>
+ <member name="permissions/receive_mms" type="bool" setter="" getter="">
+ Allows an application to monitor incoming MMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_MMS]RECEIVE_MMS[/url].
+ </member>
+ <member name="permissions/receive_sms" type="bool" setter="" getter="">
+ Allows an application to receive SMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_SMS]RECEIVE_SMS[/url].
+ </member>
+ <member name="permissions/receive_wap_push" type="bool" setter="" getter="">
+ Allows an application to receive WAP push messages. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_WAP_PUSH]RECEIVE_WAP_PUSH[/url].
+ </member>
+ <member name="permissions/record_audio" type="bool" setter="" getter="">
+ Allows an application to record audio. See [url=https://developer.android.com/reference/android/Manifest.permission#RECORD_AUDIO]RECORD_AUDIO[/url].
+ </member>
+ <member name="permissions/reorder_tasks" type="bool" setter="" getter="">
+ Allows an application to change the Z-order of tasks. See [url=https://developer.android.com/reference/android/Manifest.permission#REORDER_TASKS]REORDER_TASKS[/url].
+ </member>
+ <member name="permissions/restart_packages" type="bool" setter="" getter="">
+ Deprecated in API level 15.
+ </member>
+ <member name="permissions/send_respond_via_message" type="bool" setter="" getter="">
+ Allows an application (Phone) to send a request to other applications to handle the respond-via-message action during incoming calls. See [url=https://developer.android.com/reference/android/Manifest.permission#SEND_RESPOND_VIA_MESSAGE]SEND_RESPOND_VIA_MESSAGE[/url].
+ </member>
+ <member name="permissions/send_sms" type="bool" setter="" getter="">
+ Allows an application to send SMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#SEND_SMS]SEND_SMS[/url].
+ </member>
+ <member name="permissions/set_activity_watcher" type="bool" setter="" getter="">
+ Allows an application to watch and control how activities are started globally in the system.
+ </member>
+ <member name="permissions/set_alarm" type="bool" setter="" getter="">
+ Allows an application to broadcast an Intent to set an alarm for the user. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_ALARM]SET_ALARM[/url].
+ </member>
+ <member name="permissions/set_always_finish" type="bool" setter="" getter="">
+ Allows an application to control whether activities are immediately finished when put in the background. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_ALWAYS_FINISH]SET_ALWAYS_FINISH[/url].
+ </member>
+ <member name="permissions/set_animation_scale" type="bool" setter="" getter="">
+ Allows to modify the global animation scaling factor. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_ANIMATION_SCALE]SET_ANIMATION_SCALE[/url].
+ </member>
+ <member name="permissions/set_debug_app" type="bool" setter="" getter="">
+ Configure an application for debugging. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_DEBUG_APP]SET_DEBUG_APP[/url].
+ </member>
+ <member name="permissions/set_orientation" type="bool" setter="" getter="">
+ Allows low-level access to setting the orientation (actually rotation) of the screen.
+ </member>
+ <member name="permissions/set_pointer_speed" type="bool" setter="" getter="">
+ Allows low-level access to setting the pointer speed.
+ </member>
+ <member name="permissions/set_preferred_applications" type="bool" setter="" getter="">
+ Deprecated in API level 15.
+ </member>
+ <member name="permissions/set_process_limit" type="bool" setter="" getter="">
+ Allows an application to set the maximum number of (not needed) application processes that can be running. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_PROCESS_LIMIT]SET_PROCESS_LIMIT[/url].
+ </member>
+ <member name="permissions/set_time" type="bool" setter="" getter="">
+ Allows applications to set the system time directly. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_TIME]SET_TIME[/url].
+ </member>
+ <member name="permissions/set_time_zone" type="bool" setter="" getter="">
+ Allows applications to set the system time zone directly. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_TIME_ZONE]SET_TIME_ZONE[/url].
+ </member>
+ <member name="permissions/set_wallpaper" type="bool" setter="" getter="">
+ Allows applications to set the wallpaper. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_WALLPAPER]SET_WALLPAPER[/url].
+ </member>
+ <member name="permissions/set_wallpaper_hints" type="bool" setter="" getter="">
+ Allows applications to set the wallpaper hints. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_WALLPAPER_HINTS]SET_WALLPAPER_HINTS[/url].
+ </member>
+ <member name="permissions/signal_persistent_processes" type="bool" setter="" getter="">
+ Allow an application to request that a signal be sent to all persistent processes. See [url=https://developer.android.com/reference/android/Manifest.permission#SIGNAL_PERSISTENT_PROCESSES]SIGNAL_PERSISTENT_PROCESSES[/url].
+ </member>
+ <member name="permissions/status_bar" type="bool" setter="" getter="">
+ Allows an application to open, close, or disable the status bar and its icons. See [url=https://developer.android.com/reference/android/Manifest.permission#STATUS_BAR]STATUS_BAR[/url].
+ </member>
+ <member name="permissions/subscribed_feeds_read" type="bool" setter="" getter="">
+ Allows an application to allow access the subscribed feeds ContentProvider.
+ </member>
+ <member name="permissions/subscribed_feeds_write" type="bool" setter="" getter="">
+ Deprecated.
+ </member>
+ <member name="permissions/system_alert_window" type="bool" setter="" getter="">
+ Allows an app to create windows using the type WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, shown on top of all other apps. See [url=https://developer.android.com/reference/android/Manifest.permission#SYSTEM_ALERT_WINDOW]SYSTEM_ALERT_WINDOW[/url].
+ </member>
+ <member name="permissions/transmit_ir" type="bool" setter="" getter="">
+ Allows using the device's IR transmitter, if available. See [url=https://developer.android.com/reference/android/Manifest.permission#TRANSMIT_IR]TRANSMIT_IR[/url].
+ </member>
+ <member name="permissions/uninstall_shortcut" type="bool" setter="" getter="">
+ Deprecated.
+ </member>
+ <member name="permissions/update_device_stats" type="bool" setter="" getter="">
+ Allows an application to update device statistics. See [url=https://developer.android.com/reference/android/Manifest.permission#UPDATE_DEVICE_STATS]UPDATE_DEVICE_STATS[/url].
+ </member>
+ <member name="permissions/use_credentials" type="bool" setter="" getter="">
+ Allows an application to request authtokens from the AccountManager.
+ </member>
+ <member name="permissions/use_sip" type="bool" setter="" getter="">
+ Allows an application to use SIP service. See [url=https://developer.android.com/reference/android/Manifest.permission#USE_SIP]USE_SIP[/url].
+ </member>
+ <member name="permissions/vibrate" type="bool" setter="" getter="">
+ Allows access to the vibrator. See [url=https://developer.android.com/reference/android/Manifest.permission#VIBRATE]VIBRATE[/url].
+ </member>
+ <member name="permissions/wake_lock" type="bool" setter="" getter="">
+ Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming. See [url=https://developer.android.com/reference/android/Manifest.permission#WAKE_LOCK]WAKE_LOCK[/url].
+ </member>
+ <member name="permissions/write_apn_settings" type="bool" setter="" getter="">
+ Allows applications to write the apn settings and read sensitive fields of an existing apn settings like user and password. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_APN_SETTINGS]WRITE_APN_SETTINGS[/url].
+ </member>
+ <member name="permissions/write_calendar" type="bool" setter="" getter="">
+ Allows an application to write the user's calendar data. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_CALENDAR]WRITE_CALENDAR[/url].
+ </member>
+ <member name="permissions/write_call_log" type="bool" setter="" getter="">
+ Allows an application to write (but not read) the user's call log data. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_CALL_LOG]WRITE_CALL_LOG[/url].
+ </member>
+ <member name="permissions/write_contacts" type="bool" setter="" getter="">
+ Allows an application to write the user's contacts data. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_CONTACTS]WRITE_CONTACTS[/url].
+ </member>
+ <member name="permissions/write_external_storage" type="bool" setter="" getter="">
+ Allows an application to write to external storage. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE]WRITE_EXTERNAL_STORAGE[/url].
+ </member>
+ <member name="permissions/write_gservices" type="bool" setter="" getter="">
+ Allows an application to modify the Google service map. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_GSERVICES]WRITE_GSERVICES[/url].
+ </member>
+ <member name="permissions/write_history_bookmarks" type="bool" setter="" getter="">
+ Allows an application to write (but not read) the user's browsing history and bookmarks.
+ </member>
+ <member name="permissions/write_profile" type="bool" setter="" getter="">
+ Allows an application to write (but not read) the user's personal profile data.
+ </member>
+ <member name="permissions/write_secure_settings" type="bool" setter="" getter="">
+ Allows an application to read or write the secure system settings. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_SECURE_SETTINGS]WRITE_SECURE_SETTINGS[/url].
+ </member>
+ <member name="permissions/write_settings" type="bool" setter="" getter="">
+ Allows an application to read or write the system settings. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_SETTINGS]WRITE_SETTINGS[/url].
+ </member>
+ <member name="permissions/write_sms" type="bool" setter="" getter="">
+ Allows an application to write SMS messages.
+ </member>
+ <member name="permissions/write_social_stream" type="bool" setter="" getter="">
+ Allows an application to write (but not read) the user's social stream data.
+ </member>
+ <member name="permissions/write_sync_settings" type="bool" setter="" getter="">
+ Allows applications to write the sync settings. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_SYNC_SETTINGS]WRITE_SYNC_SETTINGS[/url].
+ </member>
+ <member name="permissions/write_user_dictionary" type="bool" setter="" getter="">
+ Allows an application to write to the user dictionary.
+ </member>
+ <member name="screen/immersive_mode" type="bool" setter="" getter="">
+ If [code]true[/code], hides navigation and status bar.
+ </member>
+ <member name="screen/support_large" type="bool" setter="" getter="">
+ Indicates whether the application supports larger screen form-factors.
+ </member>
+ <member name="screen/support_normal" type="bool" setter="" getter="">
+ Indicates whether an application supports the "normal" screen form-factors.
+ </member>
+ <member name="screen/support_small" type="bool" setter="" getter="">
+ Indicates whether the application supports smaller screen form-factors.
+ </member>
+ <member name="screen/support_xlarge" type="bool" setter="" getter="">
+ Indicates whether the application supports extra large screen form-factors.
+ </member>
+ <member name="user_data_backup/allow" type="bool" setter="" getter="">
+ If [code]true[/code], allows the application to participate in the backup and restore infrastructure.
+ </member>
+ <member name="version/code" type="int" setter="" getter="">
+ Machine-readable application version.
+ </member>
+ <member name="version/name" type="String" setter="" getter="">
+ Application version visible to the user.
+ </member>
+ <member name="xr_features/hand_tracking" type="int" setter="" getter="">
+ </member>
+ <member name="xr_features/hand_tracking_frequency" type="int" setter="" getter="">
+ </member>
+ <member name="xr_features/passthrough" type="int" setter="" getter="">
+ </member>
+ <member name="xr_features/xr_mode" type="int" setter="" getter="">
+ </member>
+ </members>
+</class>
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index d7fe89d97d..32b46271fd 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -30,10 +30,15 @@
#include "export.h"
+#include "export_plugin.h"
+
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/export/editor_export.h"
-#include "export_plugin.h"
+
+void register_android_exporter_types() {
+ GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformAndroid);
+}
void register_android_exporter() {
#ifndef ANDROID_ENABLED
diff --git a/platform/android/export/export.h b/platform/android/export/export.h
index 7ada91aec9..bd689a400d 100644
--- a/platform/android/export/export.h
+++ b/platform/android/export/export.h
@@ -31,6 +31,7 @@
#ifndef ANDROID_EXPORT_H
#define ANDROID_EXPORT_H
+void register_android_exporter_types();
void register_android_exporter();
#endif // ANDROID_EXPORT_H
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 641258a26c..c94119e13d 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -31,6 +31,8 @@
#include "export_plugin.h"
#include "gradle_export_util.h"
+#include "logo_svg.gen.h"
+#include "run_icon_svg.gen.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
@@ -46,10 +48,8 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "main/splash.gen.h"
-#include "platform/android/logo_svg.gen.h"
-#include "platform/android/run_icon_svg.gen.h"
-#include "modules/modules_enabled.gen.h" // For svg.
+#include "modules/modules_enabled.gen.h" // For mono and svg.
#ifdef MODULE_SVG_ENABLED
#include "modules/svg/image_loader_svg.h"
#endif
@@ -253,7 +253,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal
static const int OPENGL_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
static const int VULKAN_MIN_SDK_VERSION = 24;
-static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
+static const int DEFAULT_TARGET_SDK_VERSION = 33; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
#ifndef ANDROID_ENABLED
void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
@@ -1066,8 +1066,13 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
if (_uses_vulkan()) {
// Require vulkan hardware level 1 support
feature_names.push_back("android.hardware.vulkan.level");
- feature_required_list.push_back(true);
+ feature_required_list.push_back(false);
feature_versions.push_back(1);
+
+ // Require vulkan version 1.0
+ feature_names.push_back("android.hardware.vulkan.version");
+ feature_required_list.push_back(true);
+ feature_versions.push_back(0x400003); // Encoded value for api version 1.0
}
if (feature_names.size() > 0) {
@@ -1698,16 +1703,109 @@ void EditorExportPlatformAndroid::get_preset_features(const Ref<EditorExportPres
}
}
-void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_options) {
+String EditorExportPlatformAndroid::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
+ if (p_preset) {
+ if (p_name == ("apk_expansion/public_key")) {
+ bool apk_expansion = p_preset->get("apk_expansion/enable");
+ String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
+ if (apk_expansion && apk_expansion_pkey.is_empty()) {
+ return TTR("Invalid public key for APK expansion.");
+ }
+ } else if (p_name == "package/unique_name") {
+ String pn = p_preset->get("package/unique_name");
+ String pn_err;
+
+ if (!is_package_name_valid(pn, &pn_err)) {
+ return TTR("Invalid package name:") + " " + pn_err;
+ }
+ } else if (p_name == "gradle_build/use_gradle_build") {
+ bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
+ String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(Ref<EditorExportPreset>(p_preset)));
+ if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) {
+ return TTR("\"Use Gradle Build\" must be enabled to use the plugins.");
+ }
+ } else if (p_name == "xr_features/xr_mode") {
+ bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) {
+ return TTR("OpenXR requires \"Use Gradle Build\" to be enabled");
+ }
+ } else if (p_name == "xr_features/hand_tracking") {
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ int hand_tracking = p_preset->get("xr_features/hand_tracking");
+ if (xr_mode_index != XR_MODE_OPENXR) {
+ if (hand_tracking > XR_HAND_TRACKING_NONE) {
+ return TTR("\"Hand Tracking\" is only valid when \"XR Mode\" is \"OpenXR\".");
+ }
+ }
+ } else if (p_name == "xr_features/passthrough") {
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ int passthrough_mode = p_preset->get("xr_features/passthrough");
+ if (xr_mode_index != XR_MODE_OPENXR) {
+ if (passthrough_mode > XR_PASSTHROUGH_NONE) {
+ return TTR("\"Passthrough\" is only valid when \"XR Mode\" is \"OpenXR\".");
+ }
+ }
+ } else if (p_name == "gradle_build/export_format") {
+ bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
+ if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB && !gradle_build_enabled) {
+ return TTR("\"Export AAB\" is only valid when \"Use Gradle Build\" is enabled.");
+ }
+ } else if (p_name == "gradle_build/min_sdk") {
+ String min_sdk_str = p_preset->get("gradle_build/min_sdk");
+ int min_sdk_int = VULKAN_MIN_SDK_VERSION;
+ bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
+ if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do.
+ if (!gradle_build_enabled) {
+ return TTR("\"Min SDK\" can only be overridden when \"Use Gradle Build\" is enabled.");
+ }
+ if (!min_sdk_str.is_valid_int()) {
+ return vformat(TTR("\"Min SDK\" should be a valid integer, but got \"%s\" which is invalid."), min_sdk_str);
+ } else {
+ min_sdk_int = min_sdk_str.to_int();
+ if (min_sdk_int < OPENGL_MIN_SDK_VERSION) {
+ return vformat(TTR("\"Min SDK\" cannot be lower than %d, which is the version needed by the Godot library."), OPENGL_MIN_SDK_VERSION);
+ }
+ }
+ }
+ } else if (p_name == "gradle_build/target_sdk") {
+ String target_sdk_str = p_preset->get("gradle_build/target_sdk");
+ int target_sdk_int = DEFAULT_TARGET_SDK_VERSION;
+
+ String min_sdk_str = p_preset->get("gradle_build/min_sdk");
+ int min_sdk_int = VULKAN_MIN_SDK_VERSION;
+ if (min_sdk_str.is_valid_int()) {
+ min_sdk_int = min_sdk_str.to_int();
+ }
+ bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
+ if (!target_sdk_str.is_empty()) { // Empty means no override, nothing to do.
+ if (!gradle_build_enabled) {
+ return TTR("\"Target SDK\" can only be overridden when \"Use Gradle Build\" is enabled.");
+ }
+ if (!target_sdk_str.is_valid_int()) {
+ return vformat(TTR("\"Target SDK\" should be a valid integer, but got \"%s\" which is invalid."), target_sdk_str);
+ } else {
+ target_sdk_int = target_sdk_str.to_int();
+ if (target_sdk_int < min_sdk_int) {
+ return TTR("\"Target SDK\" version must be greater or equal to \"Min SDK\" version.");
+ }
+ }
+ }
+ }
+ }
+ return String();
+}
+
+void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_options) const {
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, false, true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK, false, true));
// Using String instead of int to default to an empty string (no override) with placeholder for instructions (see GH-62465).
// This implies doing validation that the string is a proper int.
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", VULKAN_MIN_SDK_VERSION)), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", VULKAN_MIN_SDK_VERSION)), "", false, true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true));
Vector<PluginConfigAndroid> plugins_configs = get_plugins();
for (int i = 0; i < plugins_configs.size(); i++) {
@@ -1727,17 +1825,17 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), abi)), is_default));
}
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname", false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "package/app_category", PROPERTY_HINT_ENUM, "Accessibility,Audio,Game,Image,Maps,News,Productivity,Social,Video"), APP_CATEGORY_GAME));
@@ -1750,10 +1848,10 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/opengl_debug"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,OpenXR"), XR_MODE_REGULAR));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_HAND_TRACKING_NONE));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,OpenXR"), XR_MODE_REGULAR, false, true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_HAND_TRACKING_NONE, false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking_frequency", PROPERTY_HINT_ENUM, "Low,High"), XR_HAND_TRACKING_FREQUENCY_LOW));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/passthrough", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_PASSTHROUGH_NONE));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/passthrough", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_PASSTHROUGH_NONE, false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true));
@@ -1765,9 +1863,9 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false, false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/SALT"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT), "", false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "permissions/custom_permissions"), PackedStringArray()));
@@ -2129,11 +2227,19 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_
return apksigner_path;
}
-bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
String err;
bool valid = false;
const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
+#ifdef MODULE_MONO_ENABLED
+ err += TTR("Exporting to Android is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target Android with C#/Mono instead.") + "\n";
+ err += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n";
+ // Don't check for additional errors, as this particular error cannot be resolved.
+ r_error = err;
+ return false;
+#endif
+
// Look for export templates (first official, and if defined custom templates).
if (!gradle_build_enabled) {
@@ -2179,16 +2285,17 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
// Validate the rest of the export configuration.
- String dk = p_preset->get("keystore/debug");
- String dk_user = p_preset->get("keystore/debug_user");
- String dk_password = p_preset->get("keystore/debug_password");
+ String dk = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ String dk_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
+ String dk_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
if ((dk.is_empty() || dk_user.is_empty() || dk_password.is_empty()) && (!dk.is_empty() || !dk_user.is_empty() || !dk_password.is_empty())) {
valid = false;
err += TTR("Either Debug Keystore, Debug User AND Debug Password settings must be configured OR none of them.") + "\n";
}
- if (!FileAccess::exists(dk)) {
+ // Use OR to make the export UI able to show this error.
+ if ((p_debug || !dk.is_empty()) && !FileAccess::exists(dk)) {
dk = EDITOR_GET("export/android/debug_keystore");
if (!FileAccess::exists(dk)) {
valid = false;
@@ -2196,16 +2303,16 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
}
}
- String rk = p_preset->get("keystore/release");
- String rk_user = p_preset->get("keystore/release_user");
- String rk_password = p_preset->get("keystore/release_password");
+ String rk = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String rk_user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
+ String rk_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
if ((rk.is_empty() || rk_user.is_empty() || rk_password.is_empty()) && (!rk.is_empty() || !rk_user.is_empty() || !rk_password.is_empty())) {
valid = false;
err += TTR("Either Release Keystore, Release User AND Release Password settings must be configured OR none of them.") + "\n";
}
- if (!rk.is_empty() && !FileAccess::exists(rk)) {
+ if (!p_debug && !rk.is_empty() && !FileAccess::exists(rk)) {
valid = false;
err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
}
@@ -2225,7 +2332,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
valid = false;
}
- // Validate that adb is available
+ // Validate that adb is available.
String adb_path = get_adb_path();
if (!FileAccess::exists(adb_path)) {
err += TTR("Unable to find Android SDK platform-tools' adb command.");
@@ -2247,7 +2354,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
if (!target_sdk_version.is_valid_int()) {
target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION);
}
- // Validate that apksigner is available
+ // Validate that apksigner is available.
String apksigner_path = get_apksigner_path(target_sdk_version.to_int());
if (!FileAccess::exists(apksigner_path)) {
err += TTR("Unable to find Android SDK build-tools' apksigner command.");
@@ -2267,111 +2374,39 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
String err;
bool valid = true;
- const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
-
- // Validate the project configuration.
- bool apk_expansion = p_preset->get("apk_expansion/enable");
- if (apk_expansion) {
- String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
-
- if (apk_expansion_pkey.is_empty()) {
- valid = false;
-
- err += TTR("Invalid public key for APK expansion.") + "\n";
+ List<ExportOption> options;
+ get_export_options(&options);
+ for (const EditorExportPlatform::ExportOption &E : options) {
+ if (get_export_option_visibility(p_preset.ptr(), E.option.name)) {
+ String warn = get_export_option_warning(p_preset.ptr(), E.option.name);
+ if (!warn.is_empty()) {
+ err += warn + "\n";
+ if (E.required) {
+ valid = false;
+ }
+ }
}
}
- String pn = p_preset->get("package/unique_name");
- String pn_err;
-
- if (!is_package_name_valid(pn, &pn_err)) {
- valid = false;
- err += TTR("Invalid package name:") + " " + pn_err + "\n";
- }
-
String etc_error = test_etc2();
if (!etc_error.is_empty()) {
valid = false;
err += etc_error;
}
- // Ensure that `Use Gradle Build` is enabled if a plugin is selected.
- String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(p_preset));
- if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) {
- valid = false;
- err += TTR("\"Use Gradle Build\" must be enabled to use the plugins.");
- err += "\n";
- }
-
- // Validate the Xr features are properly populated
- int xr_mode_index = p_preset->get("xr_features/xr_mode");
- int hand_tracking = p_preset->get("xr_features/hand_tracking");
- int passthrough_mode = p_preset->get("xr_features/passthrough");
- if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) {
- valid = false;
- err += TTR("OpenXR requires \"Use Gradle Build\" to be enabled");
- err += "\n";
- }
-
- if (xr_mode_index != XR_MODE_OPENXR) {
- if (hand_tracking > XR_HAND_TRACKING_NONE) {
- valid = false;
- err += TTR("\"Hand Tracking\" is only valid when \"XR Mode\" is \"OpenXR\".");
- err += "\n";
- }
-
- if (passthrough_mode > XR_PASSTHROUGH_NONE) {
- valid = false;
- err += TTR("\"Passthrough\" is only valid when \"XR Mode\" is \"OpenXR\".");
- err += "\n";
- }
- }
-
- if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB &&
- !gradle_build_enabled) {
- valid = false;
- err += TTR("\"Export AAB\" is only valid when \"Use Gradle Build\" is enabled.");
- err += "\n";
- }
-
- // Check the min sdk version.
String min_sdk_str = p_preset->get("gradle_build/min_sdk");
int min_sdk_int = VULKAN_MIN_SDK_VERSION;
if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do.
- if (!gradle_build_enabled) {
- valid = false;
- err += TTR("\"Min SDK\" can only be overridden when \"Use Gradle Build\" is enabled.");
- err += "\n";
- }
- if (!min_sdk_str.is_valid_int()) {
- valid = false;
- err += vformat(TTR("\"Min SDK\" should be a valid integer, but got \"%s\" which is invalid."), min_sdk_str);
- err += "\n";
- } else {
+ if (min_sdk_str.is_valid_int()) {
min_sdk_int = min_sdk_str.to_int();
- if (min_sdk_int < OPENGL_MIN_SDK_VERSION) {
- valid = false;
- err += vformat(TTR("\"Min SDK\" cannot be lower than %d, which is the version needed by the Godot library."), OPENGL_MIN_SDK_VERSION);
- err += "\n";
- }
}
}
- // Check the target sdk version.
String target_sdk_str = p_preset->get("gradle_build/target_sdk");
int target_sdk_int = DEFAULT_TARGET_SDK_VERSION;
if (!target_sdk_str.is_empty()) { // Empty means no override, nothing to do.
- if (!gradle_build_enabled) {
- valid = false;
- err += TTR("\"Target SDK\" can only be overridden when \"Use Gradle Build\" is enabled.");
- err += "\n";
- }
- if (!target_sdk_str.is_valid_int()) {
- valid = false;
- err += vformat(TTR("\"Target SDK\" should be a valid integer, but got \"%s\" which is invalid."), target_sdk_str);
- err += "\n";
- } else {
+ if (target_sdk_str.is_valid_int()) {
target_sdk_int = target_sdk_str.to_int();
if (target_sdk_int > DEFAULT_TARGET_SDK_VERSION) {
// Warning only, so don't override `valid`.
@@ -2381,18 +2416,13 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
}
}
- if (target_sdk_int < min_sdk_int) {
- valid = false;
- err += TTR("\"Target SDK\" version must be greater or equal to \"Min SDK\" version.");
- err += "\n";
- }
-
String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile");
if (current_renderer == "forward_plus") {
// Warning only, so don't override `valid`.
err += vformat(TTR("The \"%s\" renderer is designed for Desktop devices, and is not suitable for Android devices."), current_renderer);
err += "\n";
}
+
if (_uses_vulkan() && min_sdk_int < VULKAN_MIN_SDK_VERSION) {
// Warning only, so don't override `valid`.
err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer);
@@ -2486,9 +2516,9 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) {
int export_format = int(p_preset->get("gradle_build/export_format"));
String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK";
- String release_keystore = p_preset->get("keystore/release");
- String release_username = p_preset->get("keystore/release_user");
- String release_password = p_preset->get("keystore/release_password");
+ String release_keystore = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String release_username = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
+ String release_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
String target_sdk_version = p_preset->get("gradle_build/target_sdk");
if (!target_sdk_version.is_valid_int()) {
target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION);
@@ -2508,9 +2538,9 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
String password;
String user;
if (p_debug) {
- keystore = p_preset->get("keystore/debug");
- password = p_preset->get("keystore/debug_password");
- user = p_preset->get("keystore/debug_user");
+ keystore = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
+ user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
if (keystore.is_empty()) {
keystore = EDITOR_GET("export/android/debug_keystore");
@@ -2521,7 +2551,6 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
if (ep.step(vformat(TTR("Signing debug %s..."), export_label), 104)) {
return ERR_SKIP;
}
-
} else {
keystore = release_keystore;
password = release_password;
@@ -2770,7 +2799,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
CustomExportData user_data;
user_data.assets_directory = assets_directory;
user_data.debug = p_debug;
- err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
+ if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so);
+ } else {
+ err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
+ }
if (err != OK) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not export project files to gradle project."));
return err;
@@ -2861,9 +2894,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
if (should_sign) {
if (p_debug) {
- String debug_keystore = p_preset->get("keystore/debug");
- String debug_password = p_preset->get("keystore/debug_password");
- String debug_user = p_preset->get("keystore/debug_user");
+ String debug_keystore = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ String debug_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
+ String debug_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
if (debug_keystore.is_empty()) {
debug_keystore = EDITOR_GET("export/android/debug_keystore");
@@ -2883,9 +2916,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
cmdline.push_back("-Pdebug_keystore_password=" + debug_password); // argument to specify the debug keystore password.
} else {
// Pass the release keystore info as well
- String release_keystore = p_preset->get("keystore/release");
- String release_username = p_preset->get("keystore/release_user");
- String release_password = p_preset->get("keystore/release_password");
+ String release_keystore = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String release_username = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
+ String release_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
if (release_keystore.is_relative_path()) {
release_keystore = OS::get_singleton()->get_resource_dir().path_join(release_keystore).simplify_path();
}
@@ -3261,28 +3294,32 @@ void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref<
}
EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
+ if (EditorNode::get_singleton()) {
#ifdef MODULE_SVG_ENABLED
- Ref<Image> img = memnew(Image);
- const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE);
+ Ref<Image> img = memnew(Image);
+ const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE);
- ImageLoaderSVG img_loader;
- img_loader.create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false);
- logo = ImageTexture::create_from_image(img);
+ ImageLoaderSVG img_loader;
+ img_loader.create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false);
+ logo = ImageTexture::create_from_image(img);
- img_loader.create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false);
- run_icon = ImageTexture::create_from_image(img);
+ img_loader.create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false);
+ run_icon = ImageTexture::create_from_image(img);
#endif
- devices_changed.set();
- plugins_changed.set();
+ devices_changed.set();
+ plugins_changed.set();
#ifndef ANDROID_ENABLED
- check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+ check_for_changes_thread.start(_check_for_changes_poll_thread, this);
#endif
+ }
}
EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
#ifndef ANDROID_ENABLED
quit_request.set();
- check_for_changes_thread.wait_to_finish();
+ if (check_for_changes_thread.is_started()) {
+ check_for_changes_thread.wait_to_finish();
+ }
#endif
}
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 337a0228d0..0ac0fbb10b 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -49,6 +49,15 @@ const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding="
</layer-list>
)SPLASH";
+// Optional environment variables for defining confidential information. If any
+// of these is set, they will override the values set in the credentials file.
+const String ENV_ANDROID_KEYSTORE_DEBUG_PATH = "GODOT_ANDROID_KEYSTORE_DEBUG_PATH";
+const String ENV_ANDROID_KEYSTORE_DEBUG_USER = "GODOT_ANDROID_KEYSTORE_DEBUG_USER";
+const String ENV_ANDROID_KEYSTORE_DEBUG_PASS = "GODOT_ANDROID_KEYSTORE_DEBUG_PASSWORD";
+const String ENV_ANDROID_KEYSTORE_RELEASE_PATH = "GODOT_ANDROID_KEYSTORE_RELEASE_PATH";
+const String ENV_ANDROID_KEYSTORE_RELEASE_USER = "GODOT_ANDROID_KEYSTORE_RELEASE_USER";
+const String ENV_ANDROID_KEYSTORE_RELEASE_PASS = "GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD";
+
struct LauncherIcon {
const char *export_path;
int dimensions = 0;
@@ -72,10 +81,10 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
EditorProgress *ep = nullptr;
};
- Vector<PluginConfigAndroid> plugins;
+ mutable Vector<PluginConfigAndroid> plugins;
String last_plugin_names;
uint64_t last_gradle_build_time = 0;
- SafeFlag plugins_changed;
+ mutable SafeFlag plugins_changed;
Mutex plugins_lock;
Vector<Device> devices;
SafeFlag devices_changed;
@@ -180,7 +189,9 @@ public:
public:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
- virtual void get_export_options(List<ExportOption> *r_options) override;
+ virtual void get_export_options(List<ExportOption> *r_options) const override;
+
+ virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override;
virtual String get_name() const override;
@@ -208,7 +219,7 @@ public:
static String get_apksigner_path(int p_target_sdk = -1, bool p_check_executes = false);
- virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ 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;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
diff --git a/platform/android/export/godot_plugin_config.cpp b/platform/android/export/godot_plugin_config.cpp
index 56431c25de..b64cca3254 100644
--- a/platform/android/export/godot_plugin_config.cpp
+++ b/platform/android/export/godot_plugin_config.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "godot_plugin_config.h"
+
/*
* Set of prebuilt plugins.
* Currently unused, this is just for future reference:
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index b889d58199..ba4487cc4d 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -275,7 +275,8 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses
}
if (p_uses_vulkan) {
- manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"true\" android:version=\"1\" />\n";
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"false\" android:version=\"1\" />\n";
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.version\" android:required=\"true\" android:version=\"0x400003\" />\n";
}
return manifest_xr_features;
}
@@ -305,6 +306,9 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_xr
" <!-- OpenXR category tag to indicate the activity starts in an immersive OpenXR mode. \n"
" See https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#android-runtime-category. -->\n"
" <category android:name=\"org.khronos.openxr.intent.category.IMMERSIVE_HMD\" />\n"
+ "\n"
+ " <!-- Enable VR access on HTC Vive Focus devices. -->\n"
+ " <category android:name=\"com.htc.intent.category.VRAPP\" />\n"
" </intent-filter>\n";
} else {
manifest_activity_text += " <intent-filter>\n"
diff --git a/platform/android/logo.svg b/platform/android/export/logo.svg
index f154e55d11..f154e55d11 100644
--- a/platform/android/logo.svg
+++ b/platform/android/export/logo.svg
diff --git a/platform/android/run_icon.svg b/platform/android/export/run_icon.svg
index 24d930fece..24d930fece 100644
--- a/platform/android/run_icon.svg
+++ b/platform/android/export/run_icon.svg
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index b8f45628e5..7ff5c4d117 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -32,6 +32,7 @@
#define FILE_ACCESS_ANDROID_H
#include "core/io/file_access.h"
+
#include <android/asset_manager.h>
#include <android/log.h>
#include <stdio.h>
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index ea8459d1ed..a1865fb1d4 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -30,9 +30,10 @@
#include "file_access_filesystem_jandroid.h"
+#include "thread_jandroid.h"
+
#include "core/os/os.h"
#include "core/templates/local_vector.h"
-#include "thread_jandroid.h"
#include <unistd.h>
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index 5e74d9de24..739c8a0925 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -31,9 +31,10 @@
#ifndef FILE_ACCESS_FILESYSTEM_JANDROID_H
#define FILE_ACCESS_FILESYSTEM_JANDROID_H
-#include "core/io/file_access.h"
#include "java_godot_lib_jni.h"
+#include "core/io/file_access.h"
+
class FileAccessFilesystemJAndroid : public FileAccess {
static jobject file_access_handler;
static jclass cls;
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index ce4a2ecfe4..542ab51660 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -35,7 +35,7 @@
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
android:theme="@style/GodotAppSplashTheme"
- android:launchMode="singleTask"
+ android:launchMode="singleInstance"
android:excludeFromRecents="false"
android:exported="true"
android:screenOrientation="landscape"
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index f1b4bfd534..e7c06628c8 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,11 +1,11 @@
ext.versions = [
androidGradlePlugin: '7.2.1',
- compileSdk : 32,
- // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
+ compileSdk : 33,
+ // Also update 'platform/android/export/export_plugin.cpp#OPENGL_MIN_SDK_VERSION'
minSdk : 21,
// Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
- targetSdk : 32,
- buildTools : '32.0.0',
+ targetSdk : 33,
+ buildTools : '33.0.2',
kotlinVersion : '1.7.0',
fragmentVersion : '1.3.6',
nexusPublishVersion: '1.1.0',
@@ -135,14 +135,16 @@ ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
String statusValue = map["status"]
if (statusValue == null) {
statusCode = 0
- } else if (statusValue.startsWith("alpha")) {
+ } else if (statusValue.startsWith("dev")) {
statusCode = 1
- } else if (statusValue.startsWith("beta")) {
+ } else if (statusValue.startsWith("alpha")) {
statusCode = 2
- } else if (statusValue.startsWith("rc")) {
+ } else if (statusValue.startsWith("beta")) {
statusCode = 3
- } else if (statusValue.startsWith("stable")) {
+ } else if (statusValue.startsWith("rc")) {
statusCode = 4
+ } else if (statusValue.startsWith("stable")) {
+ statusCode = 5
} else {
statusCode = 0
}
@@ -189,6 +191,9 @@ ext.getGodotPublishVersion = { ->
String versionName = ""
int versionCode = 1
(versionName, versionCode) = generateGodotLibraryVersion(requiredKeys)
+ if (!versionName.endsWith("stable")) {
+ versionName += "-SNAPSHOT"
+ }
return versionName
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index cffe0a33d9..f94454e2a7 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -9,7 +9,7 @@ buildscript {
dependencies {
classpath libraries.androidGradlePlugin
classpath libraries.kotlinGradlePlugin
- classpath 'io.github.gradle-nexus:publish-plugin:1.1.0'
+ classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
}
}
@@ -20,6 +20,13 @@ plugins {
apply from: 'app/config.gradle'
apply from: 'scripts/publish-root.gradle'
+ext {
+ PUBLISH_VERSION = getGodotPublishVersion()
+}
+
+group = ossrhGroupId
+version = PUBLISH_VERSION
+
allprojects {
repositories {
google()
@@ -31,9 +38,7 @@ ext {
supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]
supportedFlavors = ["editor", "template"]
supportedFlavorsBuildTypes = [
- // The editor can't be used with target=release as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
- "editor": ["dev", "debug"],
+ "editor": ["dev", "debug", "release"],
"template": ["dev", "debug", "release"]
]
@@ -47,6 +52,7 @@ ext {
def rootDir = "../../.."
def binDir = "$rootDir/bin/"
+def androidEditorBuildsDir = "$binDir/android_editor_builds/"
def getSconsTaskName(String flavor, String buildType, String abi) {
return "compileGodotNativeLibs" + flavor.capitalize() + buildType.capitalize() + abi.capitalize()
@@ -214,18 +220,46 @@ def isAndroidStudio() {
return sysProps != null && sysProps['idea.platform.prefix'] != null
}
-task copyEditorDebugBinaryToBin(type: Copy) {
+task copyEditorReleaseApkToBin(type: Copy) {
+ dependsOn ':editor:assembleRelease'
+ from('editor/build/outputs/apk/release')
+ into(androidEditorBuildsDir)
+ include('android_editor-release*.apk')
+}
+
+task copyEditorReleaseAabToBin(type: Copy) {
+ dependsOn ':editor:bundleRelease'
+ from('editor/build/outputs/bundle/release')
+ into(androidEditorBuildsDir)
+ include('android_editor-release*.aab')
+}
+
+task copyEditorDebugApkToBin(type: Copy) {
dependsOn ':editor:assembleDebug'
from('editor/build/outputs/apk/debug')
- into(binDir)
- include('android_editor.apk')
+ into(androidEditorBuildsDir)
+ include('android_editor-debug.apk')
+}
+
+task copyEditorDebugAabToBin(type: Copy) {
+ dependsOn ':editor:bundleDebug'
+ from('editor/build/outputs/bundle/debug')
+ into(androidEditorBuildsDir)
+ include('android_editor-debug.aab')
}
-task copyEditorDevBinaryToBin(type: Copy) {
+task copyEditorDevApkToBin(type: Copy) {
dependsOn ':editor:assembleDev'
from('editor/build/outputs/apk/dev')
- into(binDir)
- include('android_editor_dev.apk')
+ into(androidEditorBuildsDir)
+ include('android_editor-dev.apk')
+}
+
+task copyEditorDevAabToBin(type: Copy) {
+ dependsOn ':editor:bundleDev'
+ from('editor/build/outputs/bundle/dev')
+ into(androidEditorBuildsDir)
+ include('android_editor-dev.aab')
}
/**
@@ -246,7 +280,8 @@ task generateGodotEditor {
&& targetLibs.isDirectory()
&& targetLibs.listFiles() != null
&& targetLibs.listFiles().length > 0) {
- tasks += "copyEditor${target.capitalize()}BinaryToBin"
+ tasks += "copyEditor${target.capitalize()}ApkToBin"
+ tasks += "copyEditor${target.capitalize()}AabToBin"
}
}
@@ -294,9 +329,11 @@ task cleanGodotEditor(type: Delete) {
// Delete the generated binary apks
delete("editor/build/outputs/apk")
- // Delete the Godot editor apks in the Godot bin directory
- delete("$binDir/android_editor.apk")
- delete("$binDir/android_editor_dev.apk")
+ // Delete the generated aab binaries
+ delete("editor/build/outputs/bundle")
+
+ // Delete the Godot editor apks & aabs in the Godot bin directory
+ delete(androidEditorBuildsDir)
}
/**
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index 9152492e9d..38034aa47c 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -13,22 +13,67 @@ dependencies {
}
ext {
- // Build number added as a suffix to the version code, and incremented for each build/upload to
- // the Google Play store.
- // This should be reset on each stable release of Godot.
- editorBuildNumber = 0
+ // Retrieve the build number from the environment variable; default to 0 if none is specified.
+ // The build number is added as a suffix to the version code for upload to the Google Play store.
+ getEditorBuildNumber = { ->
+ int buildNumber = 0
+ String versionStatus = System.getenv("GODOT_VERSION_STATUS")
+ if (versionStatus != null && !versionStatus.isEmpty()) {
+ try {
+ buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", ""));
+ } catch (NumberFormatException ignored) {
+ buildNumber = 0
+ }
+ }
+
+ return buildNumber
+ }
// Value by which the Godot version code should be offset by to make room for the build number
editorBuildNumberOffset = 100
+
+ // Return the keystore file used for signing the release build.
+ getGodotKeystoreFile = { ->
+ def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE")
+ if (keyStore == null) {
+ return null
+ }
+ return file(keyStore)
+ }
+
+ // Return the key alias used for signing the release build.
+ getGodotKeyAlias = { ->
+ def kAlias = System.getenv("GODOT_ANDROID_KEYSTORE_ALIAS")
+ return kAlias
+ }
+
+ // Return the password for the key used for signing the release build.
+ getGodotSigningPassword = { ->
+ def signingPassword = System.getenv("GODOT_ANDROID_SIGN_PASSWORD")
+ return signingPassword
+ }
+
+ // Returns true if the environment variables contains the configuration for signing the release
+ // build.
+ hasReleaseSigningConfigs = { ->
+ def keystoreFile = getGodotKeystoreFile()
+ def keyAlias = getGodotKeyAlias()
+ def signingPassword = getGodotSigningPassword()
+
+ return keystoreFile != null && keystoreFile.isFile()
+ && keyAlias != null && !keyAlias.isEmpty()
+ && signingPassword != null && !signingPassword.isEmpty()
+ }
}
def generateVersionCode() {
int libraryVersionCode = getGodotLibraryVersionCode()
- return (libraryVersionCode * editorBuildNumberOffset) + editorBuildNumber
+ return (libraryVersionCode * editorBuildNumberOffset) + getEditorBuildNumber()
}
def generateVersionName() {
String libraryVersionName = getGodotLibraryVersionName()
- return libraryVersionName + ".$editorBuildNumber"
+ int buildNumber = getEditorBuildNumber()
+ return buildNumber == 0 ? libraryVersionName : libraryVersionName + ".$buildNumber"
}
android {
@@ -45,6 +90,7 @@ android {
targetSdkVersion versions.targetSdk
missingDimensionStrategy 'products', 'editor'
+ setProperty("archivesBaseName", "android_editor")
}
compileOptions {
@@ -56,6 +102,15 @@ android {
jvmTarget = versions.javaVersion
}
+ signingConfigs {
+ release {
+ storeFile getGodotKeystoreFile()
+ storePassword getGodotSigningPassword()
+ keyAlias getGodotKeyAlias()
+ keyPassword getGodotSigningPassword()
+ }
+ }
+
buildTypes {
dev {
initWith debug
@@ -64,15 +119,14 @@ android {
debug {
initWith release
-
- // Need to swap with the release signing config when this is ready for public release.
+ applicationIdSuffix ".debug"
signingConfig signingConfigs.debug
}
release {
- // This buildtype is disabled below.
- // The editor can't be used with target=release only, as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
+ if (hasReleaseSigningConfigs()) {
+ signingConfig signingConfigs.release
+ }
}
}
@@ -82,20 +136,4 @@ android {
doNotStrip '**/*.so'
}
}
-
- // Disable 'release' buildtype.
- // The editor can't be used with target=release only, as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
- variantFilter { variant ->
- if (variant.buildType.name == "release") {
- setIgnore(true)
- }
- }
-
- applicationVariants.all { variant ->
- variant.outputs.all { output ->
- def suffix = variant.name == "dev" ? "_dev" : ""
- output.outputFileName = "android_editor${suffix}.apk"
- }
- }
}
diff --git a/platform/android/java/editor/src/debug/res/values/strings.xml b/platform/android/java/editor/src/debug/res/values/strings.xml
new file mode 100644
index 0000000000..09ee2d77e1
--- /dev/null
+++ b/platform/android/java/editor/src/debug/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="godot_editor_name_string">Godot Editor 4 (debug)</string>
+</resources>
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index 80ef10b6a4..c7b2c8ad67 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="false"
@@ -33,7 +35,7 @@
<activity
android:name=".GodotProjectManager"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
- android:launchMode="singleTask"
+ android:launchMode="singleInstance"
android:screenOrientation="userLandscape"
android:exported="true"
android:process=":GodotProjectManager">
@@ -51,7 +53,7 @@
android:name=".GodotEditor"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:process=":GodotEditor"
- android:launchMode="singleTask"
+ android:launchMode="singleInstance"
android:screenOrientation="userLandscape"
android:exported="false">
<layout android:defaultHeight="@dimen/editor_default_window_height"
@@ -63,7 +65,7 @@
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:label="@string/godot_project_name_string"
android:process=":GodotGame"
- android:launchMode="singleTask"
+ android:launchMode="singleInstance"
android:exported="false"
android:screenOrientation="userLandscape">
<layout android:defaultHeight="@dimen/editor_default_window_height"
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index 71385315ae..64d3d4eca1 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -40,6 +40,7 @@ import android.util.Log
import android.widget.Toast
import androidx.window.layout.WindowMetricsCalculator
import org.godotengine.godot.FullScreenGodotApp
+import org.godotengine.godot.GodotLib
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.ProcessPhoenix
import java.util.*
@@ -61,7 +62,7 @@ open class GodotEditor : FullScreenGodotApp() {
private const val WAIT_FOR_DEBUGGER = false
- private const val COMMAND_LINE_PARAMS = "command_line_params"
+ private const val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
private const val EDITOR_ID = 777
private const val EDITOR_ARG = "--editor"
@@ -75,14 +76,27 @@ open class GodotEditor : FullScreenGodotApp() {
private const val PROJECT_MANAGER_ARG = "--project-manager"
private const val PROJECT_MANAGER_ARG_SHORT = "-p"
private const val PROJECT_MANAGER_PROCESS_NAME_SUFFIX = ":GodotProjectManager"
+
+ /**
+ * Sets of constants to specify the window to use to run the project.
+ *
+ * Should match the values in 'editor/editor_settings.cpp' for the
+ * 'run/window_placement/android_window' setting.
+ */
+ private const val ANDROID_WINDOW_AUTO = 0
+ private const val ANDROID_WINDOW_SAME_AS_EDITOR = 1
+ private const val ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR = 2
}
private val commandLineParams = ArrayList<String>()
override fun onCreate(savedInstanceState: Bundle?) {
- PermissionsUtil.requestManifestPermissions(this)
+ // We exclude certain permissions from the set we request at startup, as they'll be
+ // requested on demand based on use-cases.
+ PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
- val params = intent.getStringArrayExtra(COMMAND_LINE_PARAMS)
+ val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
+ Log.d(TAG, "Received parameters ${params.contentToString()}")
updateCommandLineParams(params)
if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) {
@@ -90,20 +104,44 @@ open class GodotEditor : FullScreenGodotApp() {
}
super.onCreate(savedInstanceState)
+ }
+
+ override fun onGodotSetupCompleted() {
+ super.onGodotSetupCompleted()
+ val longPressEnabled = enableLongPressGestures()
+ val panScaleEnabled = enablePanAndScaleGestures()
+
+ checkForProjectPermissionsToEnable()
- // Enable long press, panning and scaling gestures
- godotFragment?.renderView?.inputHandler?.apply {
- enableLongPress(enableLongPressGestures())
- enablePanningAndScalingGestures(enablePanAndScaleGestures())
+ runOnUiThread {
+ // Enable long press, panning and scaling gestures
+ godotFragment?.renderView?.inputHandler?.apply {
+ enableLongPress(longPressEnabled)
+ enablePanningAndScalingGestures(panScaleEnabled)
+ }
+ }
+ }
+
+ /**
+ * Check for project permissions to enable
+ */
+ protected open fun checkForProjectPermissionsToEnable() {
+ // Check for RECORD_AUDIO permission
+ val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"));
+ if (audioInputEnabled) {
+ PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this)
}
}
private fun updateCommandLineParams(args: Array<String>?) {
// Update the list of command line params with the new args
commandLineParams.clear()
- if (args != null && args.isNotEmpty()) {
+ if (!args.isNullOrEmpty()) {
commandLineParams.addAll(listOf(*args))
}
+ if (BuildConfig.BUILD_TYPE == "dev") {
+ commandLineParams.add("--benchmark")
+ }
}
override fun getCommandLine() = commandLineParams
@@ -115,8 +153,7 @@ open class GodotEditor : FullScreenGodotApp() {
// Whether we should launch the new godot instance in an adjacent window
// https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
- var launchAdjacent =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && (isInMultiWindowMode || isLargeScreen)
+ var launchAdjacent = shouldGameLaunchAdjacent()
for (arg in args) {
if (EDITOR_ARG == arg || EDITOR_ARG_SHORT == arg) {
@@ -137,33 +174,49 @@ open class GodotEditor : FullScreenGodotApp() {
// Launch a new activity
val newInstance = Intent(this, targetClass)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(COMMAND_LINE_PARAMS, args)
+ .putExtra(EXTRA_COMMAND_LINE_PARAMS, args)
if (launchAdjacent) {
newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
}
if (targetClass == javaClass) {
- Log.d(TAG, "Restarting $targetClass")
+ Log.d(TAG, "Restarting $targetClass with parameters ${args.contentToString()}")
ProcessPhoenix.triggerRebirth(this, newInstance)
} else {
- Log.d(TAG, "Starting $targetClass")
+ Log.d(TAG, "Starting $targetClass with parameters ${args.contentToString()}")
+ newInstance.putExtra(EXTRA_NEW_LAUNCH, true)
startActivity(newInstance)
}
return instanceId
}
override fun onGodotForceQuit(godotInstanceId: Int): Boolean {
- val processNameSuffix = when (godotInstanceId) {
+ val targetClass: Class<*>?
+ val processNameSuffix: String
+ when (godotInstanceId) {
GAME_ID -> {
- GAME_PROCESS_NAME_SUFFIX
+ processNameSuffix = GAME_PROCESS_NAME_SUFFIX
+ targetClass = GodotGame::class.java
}
EDITOR_ID -> {
- EDITOR_PROCESS_NAME_SUFFIX
+ processNameSuffix = EDITOR_PROCESS_NAME_SUFFIX
+ targetClass = GodotEditor::class.java
}
PROJECT_MANAGER_ID -> {
- PROJECT_MANAGER_PROCESS_NAME_SUFFIX
+ processNameSuffix = PROJECT_MANAGER_PROCESS_NAME_SUFFIX
+ targetClass = GodotProjectManager::class.java
}
- else -> ""
+ else -> {
+ processNameSuffix = ""
+ targetClass = null
+ }
+ }
+
+ if (targetClass == javaClass) {
+ Log.d(TAG, "Force quitting $targetClass")
+ ProcessPhoenix.forceQuit(this)
+ return true
}
+
if (processNameSuffix.isBlank()) {
return false
}
@@ -172,8 +225,16 @@ open class GodotEditor : FullScreenGodotApp() {
val runningProcesses = activityManager.runningAppProcesses
for (runningProcess in runningProcesses) {
if (runningProcess.processName.endsWith(processNameSuffix)) {
- Log.v(TAG, "Killing Godot process ${runningProcess.processName}")
- Process.killProcess(runningProcess.pid)
+ if (targetClass == null) {
+ // Killing process directly
+ Log.v(TAG, "Killing Godot process ${runningProcess.processName}")
+ Process.killProcess(runningProcess.pid)
+ } else {
+ // Activity is running; sending a request for self termination.
+ Log.v(TAG, "Sending force quit request to $targetClass running on process ${runningProcess.processName}")
+ val forceQuitIntent = Intent(this, targetClass).putExtra(EXTRA_FORCE_QUIT, true)
+ startActivity(forceQuitIntent)
+ }
return true
}
}
@@ -210,12 +271,34 @@ open class GodotEditor : FullScreenGodotApp() {
/**
* Enable long press gestures for the Godot Android editor.
*/
- protected open fun enableLongPressGestures() = true
+ protected open fun enableLongPressGestures() =
+ java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_long_press_as_right_click"))
/**
* Enable pan and scale gestures for the Godot Android editor.
*/
- protected open fun enablePanAndScaleGestures() = true
+ protected open fun enablePanAndScaleGestures() =
+ java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures"))
+
+ private fun shouldGameLaunchAdjacent(): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ try {
+ when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
+ ANDROID_WINDOW_SAME_AS_EDITOR -> false
+ ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> true
+ else -> {
+ // ANDROID_WINDOW_AUTO
+ isInMultiWindowMode || isLargeScreen
+ }
+ }
+ } catch (e: NumberFormatException) {
+ // Fall-back to the 'Auto' behavior
+ isInMultiWindowMode || isLargeScreen
+ }
+ } else {
+ false
+ }
+ }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
index 104af0fcff..aa4d02b5b2 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
@@ -39,4 +39,9 @@ class GodotGame : GodotEditor() {
override fun enableLongPressGestures() = false
override fun enablePanAndScaleGestures() = false
+
+ override fun checkForProjectPermissionsToEnable() {
+ // Nothing to do.. by the time we get here, the project permissions will have already
+ // been requested by the Editor window.
+ }
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt
index 62a9384f2e..d0e4279eeb 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt
@@ -37,4 +37,9 @@ package org.godotengine.editor
* Upon selection of a project, this activity (via its parent logic) starts the
* [GodotEditor] activity.
*/
-class GodotProjectManager : GodotEditor()
+class GodotProjectManager : GodotEditor() {
+ override fun checkForProjectPermissionsToEnable() {
+ // Nothing to do here.. we have yet to select a project to load.
+ }
+}
+
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
index 41dfb87909..aa991fceae 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 841656a240..4340250ad3 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -4,7 +4,6 @@ plugins {
}
ext {
- PUBLISH_VERSION = getGodotPublishVersion()
PUBLISH_ARTIFACT_ID = 'godot'
}
@@ -81,19 +80,11 @@ android {
release.jniLibs.srcDirs = ['libs/release']
// Editor jni library
+ editorRelease.jniLibs.srcDirs = ['libs/tools/release']
editorDebug.jniLibs.srcDirs = ['libs/tools/debug']
editorDev.jniLibs.srcDirs = ['libs/tools/dev']
}
- // Disable 'editorRelease'.
- // The editor can't be used with target=release as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
- variantFilter { variant ->
- if (variant.name == "editorRelease") {
- setIgnore(true)
- }
- }
-
libraryVariants.all { variant ->
def flavorName = variant.getFlavorName()
if (flavorName == null || flavorName == "") {
@@ -106,9 +97,14 @@ android {
}
boolean devBuild = buildType == "dev"
+ boolean runTests = devBuild
+ boolean productionBuild = !devBuild
+ boolean storeRelease = buildType == "release"
def sconsTarget = flavorName
if (sconsTarget == "template") {
+ // Tests are not supported on template builds
+ runTests = false
switch (buildType) {
case "release":
sconsTarget += "_release"
@@ -136,10 +132,10 @@ android {
def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows()
? [".bat", ".cmd", ".ps1", ".exe"]
: [""])
- logger.lifecycle("Looking for $sconsName executable path")
+ logger.debug("Looking for $sconsName executable path")
for (ext in sconsExts) {
String sconsNameExt = sconsName + ext
- logger.lifecycle("Checking $sconsNameExt")
+ logger.debug("Checking $sconsNameExt")
sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt)
if (sconsExecutableFile != null) {
// We're done!
@@ -156,7 +152,7 @@ android {
if (sconsExecutableFile == null) {
throw new GradleException("Unable to find executable path for the '$sconsName' command.")
} else {
- logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
+ logger.debug("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
}
for (String selectedAbi : selectedAbis) {
@@ -168,7 +164,7 @@ android {
def taskName = getSconsTaskName(flavorName, buildType, selectedAbi)
tasks.create(name: taskName, type: Exec) {
executable sconsExecutableFile.absolutePath
- args "--directory=${pathToRootDir}", "platform=android", "dev_mode=${devBuild}", "dev_build=${devBuild}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
+ args "--directory=${pathToRootDir}", "platform=android", "store_release=${storeRelease}", "production=${productionBuild}", "dev_mode=${devBuild}", "dev_build=${devBuild}", "tests=${runTests}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
}
// Schedule the tasks so the generated libs are present before the aar file is packaged.
diff --git a/platform/android/java/lib/res/xml/godot_provider_paths.xml b/platform/android/java/lib/res/xml/godot_provider_paths.xml
index 1255f576bf..8ad16da879 100644
--- a/platform/android/java/lib/res/xml/godot_provider_paths.xml
+++ b/platform/android/java/lib/res/xml/godot_provider_paths.xml
@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <files-path
+ name="filesRoot"
+ path="/" />
+
<external-path
name="public"
path="." />
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 677c9d8f13..3e975449d8 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -51,6 +51,9 @@ import androidx.fragment.app.FragmentActivity;
public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost {
private static final String TAG = FullScreenGodotApp.class.getSimpleName();
+ protected static final String EXTRA_FORCE_QUIT = "force_quit_requested";
+ protected static final String EXTRA_NEW_LAUNCH = "new_launch_requested";
+
@Nullable
private Godot godotFragment;
@@ -59,6 +62,8 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
super.onCreate(savedInstanceState);
setContentView(R.layout.godot_app_layout);
+ handleStartIntent(getIntent(), true);
+
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.godot_fragment_container);
if (currentFragment instanceof Godot) {
Log.v(TAG, "Reusing existing Godot fragment instance.");
@@ -109,11 +114,35 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
+ setIntent(intent);
+
+ handleStartIntent(intent, false);
+
if (godotFragment != null) {
godotFragment.onNewIntent(intent);
}
}
+ private void handleStartIntent(Intent intent, boolean newLaunch) {
+ boolean forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false);
+ if (forceQuitRequested) {
+ Log.d(TAG, "Force quit requested, terminating..");
+ ProcessPhoenix.forceQuit(this);
+ return;
+ }
+
+ if (!newLaunch) {
+ boolean newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false);
+ if (newLaunchRequested) {
+ Log.d(TAG, "New launch requested, restarting..");
+
+ Intent restartIntent = new Intent(intent).putExtra(EXTRA_NEW_LAUNCH, false);
+ ProcessPhoenix.triggerRebirth(this, restartIntent);
+ return;
+ }
+ }
+ }
+
@CallSuper
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
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 a03da7292b..9f2dec7317 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -39,6 +39,7 @@ import org.godotengine.godot.io.file.FileAccessHandler;
import org.godotengine.godot.plugin.GodotPlugin;
import org.godotengine.godot.plugin.GodotPluginRegistry;
import org.godotengine.godot.tts.GodotTTS;
+import org.godotengine.godot.utils.BenchmarkUtils;
import org.godotengine.godot.utils.GodotNetUtils;
import org.godotengine.godot.utils.PermissionsUtil;
import org.godotengine.godot.xr.XRMode;
@@ -74,10 +75,14 @@ import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Surface;
+import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -176,7 +181,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
public GodotIO io;
public GodotNetUtils netUtils;
public GodotTTS tts;
- DirectoryAccessHandler directoryAccessHandler;
+ private DirectoryAccessHandler directoryAccessHandler;
+ private FileAccessHandler fileAccessHandler;
public interface ResultCallback {
void callback(int requestCode, int resultCode, Intent data);
@@ -270,7 +276,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
// ...add to FrameLayout
containerLayout.addView(editText);
- if (!GodotLib.setup(command_line)) {
+ tts = new GodotTTS(activity);
+
+ if (!GodotLib.setup(command_line, tts)) {
Log.e(TAG, "Unable to setup the Godot engine! Aborting...");
alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit);
return false;
@@ -278,7 +286,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
if (usesVulkan()) {
if (!meetsVulkanRequirements(activity.getPackageManager())) {
- Log.w(TAG, "Missing requirements for vulkan support!");
+ alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit);
+ return false;
}
mRenderView = new GodotVulkanRenderView(activity, this);
} else {
@@ -291,14 +300,64 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
editText.setView(mRenderView);
io.setEdit(editText);
- view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
- Point fullSize = new Point();
- activity.getWindowManager().getDefaultDisplay().getSize(fullSize);
- Rect gameSize = new Rect();
- mRenderView.getView().getWindowVisibleDisplayFrame(gameSize);
- final int keyboardHeight = fullSize.y - gameSize.bottom;
- GodotLib.setVirtualKeyboardHeight(keyboardHeight);
- });
+ // Listeners for keyboard height.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ // Report the height of virtual keyboard as it changes during the animation.
+ final View decorView = activity.getWindow().getDecorView();
+ decorView.setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP) {
+ int startBottom, endBottom;
+ @Override
+ public void onPrepare(@NonNull WindowInsetsAnimation animation) {
+ startBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
+ }
+
+ @NonNull
+ @Override
+ public WindowInsetsAnimation.Bounds onStart(@NonNull WindowInsetsAnimation animation, @NonNull WindowInsetsAnimation.Bounds bounds) {
+ endBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
+ return bounds;
+ }
+
+ @NonNull
+ @Override
+ public WindowInsets onProgress(@NonNull WindowInsets windowInsets, @NonNull List<WindowInsetsAnimation> list) {
+ // Find the IME animation.
+ WindowInsetsAnimation imeAnimation = null;
+ for (WindowInsetsAnimation animation : list) {
+ if ((animation.getTypeMask() & WindowInsets.Type.ime()) != 0) {
+ imeAnimation = animation;
+ break;
+ }
+ }
+ // Update keyboard height based on IME animation.
+ if (imeAnimation != null) {
+ float interpolatedFraction = imeAnimation.getInterpolatedFraction();
+ // Linear interpolation between start and end values.
+ float keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction;
+ GodotLib.setVirtualKeyboardHeight((int)keyboardHeight);
+ }
+ return windowInsets;
+ }
+
+ @Override
+ public void onEnd(@NonNull WindowInsetsAnimation animation) {
+ }
+ });
+ } else {
+ // Infer the virtual keyboard height using visible area.
+ view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ // Don't allocate a new Rect every time the callback is called.
+ final Rect visibleSize = new Rect();
+
+ @Override
+ public void onGlobalLayout() {
+ final SurfaceView view = mRenderView.getView();
+ view.getWindowVisibleDisplayFrame(visibleSize);
+ final int keyboardHeight = view.getHeight() - visibleSize.bottom;
+ GodotLib.setVirtualKeyboardHeight(keyboardHeight);
+ }
+ });
+ }
mRenderView.queueOnRenderThread(() -> {
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
@@ -338,7 +397,17 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
return false;
}
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1)) {
+ // Optional requirements.. log as warning if missing
+ Log.w(TAG, "The vulkan hardware level does not meet the minimum requirement: 1");
+ }
+
+ // Check for api version 1.0
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x400003);
+ }
+
+ return false;
}
public void setKeepScreenOn(final boolean p_enabled) {
@@ -455,7 +524,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
return cmdline;
} catch (Exception e) {
- e.printStackTrace();
+ // The _cl_ file can be missing with no adverse effect
return new String[0];
}
}
@@ -509,10 +578,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final Activity activity = getActivity();
io = new GodotIO(activity);
netUtils = new GodotNetUtils(activity);
- tts = new GodotTTS(activity);
Context context = getContext();
directoryAccessHandler = new DirectoryAccessHandler(context);
- FileAccessHandler fileAccessHandler = new FileAccessHandler(context);
+ fileAccessHandler = new FileAccessHandler(context);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
@@ -526,8 +594,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
netUtils,
directoryAccessHandler,
fileAccessHandler,
- use_apk_expansion,
- tts);
+ use_apk_expansion);
result_callback = null;
}
@@ -540,6 +607,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
@Override
public void onCreate(Bundle icicle) {
+ BenchmarkUtils.beginBenchmarkMeasure("Godot::onCreate");
super.onCreate(icicle);
final Activity activity = getActivity();
@@ -588,6 +656,18 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
editor.apply();
i++;
+ } else if (command_line[i].equals("--benchmark")) {
+ BenchmarkUtils.setUseBenchmark(true);
+ new_args.add(command_line[i]);
+ } else if (has_extra && command_line[i].equals("--benchmark-file")) {
+ BenchmarkUtils.setUseBenchmark(true);
+ new_args.add(command_line[i]);
+
+ // Retrieve the filepath
+ BenchmarkUtils.setBenchmarkFile(command_line[i + 1]);
+ new_args.add(command_line[i + 1]);
+
+ i++;
} else if (command_line[i].trim().length() != 0) {
new_args.add(command_line[i]);
}
@@ -631,8 +711,14 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
Intent notifierIntent = new Intent(activity, activity.getClass());
notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0,
- notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent pendingIntent;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ pendingIntent = PendingIntent.getActivity(activity, 0,
+ notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ } else {
+ pendingIntent = PendingIntent.getActivity(activity, 0,
+ notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
int startResult;
try {
@@ -658,6 +744,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
mCurrentIntent = activity.getIntent();
initializeGodot();
+ BenchmarkUtils.endBenchmarkMeasure("Godot::onCreate");
}
@Override
@@ -863,20 +950,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
// Do something here if sensor accuracy changes.
}
- /*
- @Override public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) {
- System.out.printf("** BACK REQUEST!\n");
-
- GodotLib.quit();
- return true;
- }
- System.out.printf("** OTHER KEY!\n");
-
- return false;
- }
- */
-
public void onBackPressed() {
boolean shouldQuit = true;
@@ -979,6 +1052,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
return PermissionsUtil.getGrantedPermissions(getActivity());
}
+ @Keep
+ private String getCACertificates() {
+ return GodotNetUtils.getCACertificates();
+ }
+
/**
* The download state should trigger changes in the UI --- it may be useful
* to show the state as being indeterminate at times. This sample can be
@@ -1083,10 +1161,35 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
@Keep
+ public DirectoryAccessHandler getDirectoryAccessHandler() {
+ return directoryAccessHandler;
+ }
+
+ @Keep
+ public FileAccessHandler getFileAccessHandler() {
+ return fileAccessHandler;
+ }
+
+ @Keep
private int createNewGodotInstance(String[] args) {
if (godotHost != null) {
return godotHost.onNewGodotInstanceRequested(args);
}
return 0;
}
+
+ @Keep
+ private void beginBenchmarkMeasure(String label) {
+ BenchmarkUtils.beginBenchmarkMeasure(label);
+ }
+
+ @Keep
+ private void endBenchmarkMeasure(String label) {
+ BenchmarkUtils.endBenchmarkMeasure(label);
+ }
+
+ @Keep
+ private void dumpBenchmark(String benchmarkFile) {
+ BenchmarkUtils.dumpBenchmark(fileAccessHandler, benchmarkFile);
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index 330e2ede76..b465377743 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -166,8 +166,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
@Override
public void requestPointerCapture() {
- super.requestPointerCapture();
- inputHandler.onPointerCaptureChange(true);
+ if (canCapturePointer()) {
+ super.requestPointerCapture();
+ inputHandler.onPointerCaptureChange(true);
+ }
}
@Override
@@ -188,10 +190,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
try {
Bitmap bitmap = null;
if (!TextUtils.isEmpty(imagePath)) {
- if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) {
+ if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) {
// Try to load the bitmap from the file system
bitmap = BitmapFactory.decodeFile(imagePath);
- } else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) {
+ } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) {
// Try to load the bitmap from the assets directory
AssetManager am = getContext().getAssets();
InputStream imageInputStream = am.open(imagePath);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index 75a01dc787..b9ecd6971d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -61,8 +61,7 @@ public class GodotLib {
GodotNetUtils netUtils,
DirectoryAccessHandler directoryAccessHandler,
FileAccessHandler fileAccessHandler,
- boolean use_apk_expansion,
- GodotTTS tts);
+ boolean use_apk_expansion);
/**
* Invoked on the main thread to clean up Godot native layer.
@@ -74,7 +73,7 @@ public class GodotLib {
* Invoked on the GL thread to complete setup for the Godot native layer logic.
* @param p_cmdline Command line arguments used to configure Godot native layer components.
*/
- public static native boolean setup(String[] p_cmdline);
+ public static native boolean setup(String[] p_cmdline, GodotTTS tts);
/**
* Invoked on the GL thread when the underlying Android surface has changed size.
@@ -148,7 +147,7 @@ public class GodotLib {
/**
* Forward regular key events.
*/
- public static native void key(int p_physical_keycode, int p_unicode, int p_key_label, boolean p_pressed);
+ public static native void key(int p_physical_keycode, int p_unicode, int p_key_label, boolean p_pressed, boolean p_echo);
/**
* Forward game device's key events.
@@ -190,6 +189,13 @@ public class GodotLib {
public static native String getGlobal(String p_key);
/**
+ * Used to access Godot's editor settings.
+ * @param settingKey Setting key
+ * @return String value of the setting
+ */
+ public static native String getEditorSetting(String settingKey);
+
+ /**
* Invoke method |p_method| on the Godot object specified by |p_id|
* @param p_id Id of the Godot object to invoke
* @param p_method Name of the method to invoke
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 02c0d67fff..00243dab2a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -51,4 +51,8 @@ public interface GodotRenderView {
void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY);
void setPointerIcon(int pointerType);
+
+ default boolean canCapturePointer() {
+ return getInputHandler().canCapturePointer();
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index 34490d4625..681e182adb 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -134,8 +134,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
@Override
public void requestPointerCapture() {
- super.requestPointerCapture();
- mInputHandler.onPointerCaptureChange(true);
+ if (canCapturePointer()) {
+ super.requestPointerCapture();
+ mInputHandler.onPointerCaptureChange(true);
+ }
}
@Override
@@ -162,10 +164,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
try {
Bitmap bitmap = null;
if (!TextUtils.isEmpty(imagePath)) {
- if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) {
+ if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) {
// Try to load the bitmap from the file system
bitmap = BitmapFactory.decodeFile(imagePath);
- } else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) {
+ } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) {
// Try to load the bitmap from the assets directory
AssetManager am = getContext().getAssets();
InputStream imageInputStream = am.open(imagePath);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
index 1be009b6dc..e26c9d39c2 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
@@ -197,7 +197,10 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (event.actionMasked == MotionEvent.ACTION_UP) {
nextDownIsDoubleTap = false
GodotInputHandler.handleMotionEvent(event)
+ } else if (event.actionMasked == MotionEvent.ACTION_MOVE && panningAndScalingEnabled == false) {
+ GodotInputHandler.handleMotionEvent(event)
}
+
return true
}
@@ -224,42 +227,43 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
)
dragInProgress = false
}
- return true
}
- dragInProgress = true
-
val x = terminusEvent.x
val y = terminusEvent.y
- if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress) {
+ if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress && !dragInProgress) {
GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f)
- } else {
+ } else if (!scaleInProgress){
+ dragInProgress = true
GodotInputHandler.handleMotionEvent(terminusEvent)
}
return true
}
- override fun onScale(detector: ScaleGestureDetector?): Boolean {
- if (detector == null || !panningAndScalingEnabled || pointerCaptureInProgress) {
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ if (!panningAndScalingEnabled || pointerCaptureInProgress || dragInProgress) {
return false
}
- GodotLib.magnify(
- detector.focusX,
- detector.focusY,
- detector.scaleFactor
- )
+
+ if (detector.scaleFactor >= 0.8f && detector.scaleFactor != 1f && detector.scaleFactor <= 1.2f) {
+ GodotLib.magnify(
+ detector.focusX,
+ detector.focusY,
+ detector.scaleFactor
+ )
+ }
return true
}
- override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
- if (detector == null || !panningAndScalingEnabled || pointerCaptureInProgress) {
+ override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
+ if (!panningAndScalingEnabled || pointerCaptureInProgress || dragInProgress) {
return false
}
scaleInProgress = true
return true
}
- override fun onScaleEnd(detector: ScaleGestureDetector?) {
+ override fun onScaleEnd(detector: ScaleGestureDetector) {
scaleInProgress = false
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index cedbbfb7c3..185d03fe39 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -66,6 +66,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
private final ScaleGestureDetector scaleGestureDetector;
private final GodotGestureHandler godotGestureHandler;
+ /**
+ * Used to decide whether mouse capture can be enabled.
+ */
+ private int lastSeenToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
public GodotInputHandler(GodotRenderView godotView) {
final Context context = godotView.getView().getContext();
mRenderView = godotView;
@@ -105,6 +110,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
}
+ public boolean canCapturePointer() {
+ return lastSeenToolType == MotionEvent.TOOL_TYPE_MOUSE;
+ }
+
public void onPointerCaptureChange(boolean hasCapture) {
godotGestureHandler.onPointerCaptureChange(hasCapture);
}
@@ -132,7 +141,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
final int physical_keycode = event.getKeyCode();
final int unicode = event.getUnicodeChar();
final int key_label = event.getDisplayLabel();
- GodotLib.key(physical_keycode, unicode, key_label, false);
+ GodotLib.key(physical_keycode, unicode, key_label, false, event.getRepeatCount() > 0);
};
return true;
@@ -167,13 +176,15 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
final int physical_keycode = event.getKeyCode();
final int unicode = event.getUnicodeChar();
final int key_label = event.getDisplayLabel();
- GodotLib.key(physical_keycode, unicode, key_label, true);
+ GodotLib.key(physical_keycode, unicode, key_label, true, event.getRepeatCount() > 0);
}
return true;
}
public boolean onTouchEvent(final MotionEvent event) {
+ lastSeenToolType = event.getToolType(0);
+
this.scaleGestureDetector.onTouchEvent(event);
if (this.gestureDetector.onTouchEvent(event)) {
// The gesture detector has handled the event.
@@ -198,6 +209,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
public boolean onGenericMotionEvent(MotionEvent event) {
+ lastSeenToolType = event.getToolType(0);
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && gestureDetector.onGenericMotionEvent(event)) {
// The gesture detector has handled the event.
return true;
@@ -471,15 +484,27 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative) {
+ // Fix the buttonsMask
+ switch (eventAction) {
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // Zero-up the button state
+ buttonsMask = 0;
+ break;
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ if (buttonsMask == 0) {
+ buttonsMask = MotionEvent.BUTTON_PRIMARY;
+ }
+ break;
+ }
+
// We don't handle ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE events as they typically
// follow ACTION_DOWN and ACTION_UP events. As such, handling them would result in duplicate
// stream of events to the engine.
switch (eventAction) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- // Zero-up the button state
- buttonsMask = 0;
- // FALL THROUGH
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_EXIT:
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
index 7b628e25ed..06b565c30f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -93,8 +93,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
@Override
public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) {
for (int i = 0; i < count; ++i) {
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, true);
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, false);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, true, false);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, false, false);
if (mHasSelection) {
mHasSelection = false;
@@ -115,8 +115,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
// Return keys are handled through action events
continue;
}
- GodotLib.key(0, character, 0, true);
- GodotLib.key(0, character, 0, false);
+ GodotLib.key(0, character, 0, true, false);
+ GodotLib.key(0, character, 0, false, false);
}
}
@@ -124,19 +124,20 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) {
if (mEdit == pTextView && isFullScreenEdit() && pKeyEvent != null) {
final String characters = pKeyEvent.getCharacters();
-
- for (int i = 0; i < characters.length(); i++) {
- final int character = characters.codePointAt(i);
- GodotLib.key(0, character, 0, true);
- GodotLib.key(0, character, 0, false);
+ if (characters != null) {
+ for (int i = 0; i < characters.length(); i++) {
+ final int character = characters.codePointAt(i);
+ GodotLib.key(0, character, 0, true, false);
+ GodotLib.key(0, character, 0, false, false);
+ }
}
}
if (pActionID == EditorInfo.IME_ACTION_DONE) {
// Enter key has been pressed
mRenderView.queueOnRenderThread(() -> {
- GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, true);
- GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, false);
+ GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, true, false);
+ GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, false, false);
});
mRenderView.getView().requestFocus();
return true;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
index 833ab40af0..8ee3d5f48f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
@@ -76,6 +76,13 @@ internal enum class StorageScope {
return UNKNOWN
}
+ // If we have 'All Files Access' permission, we can access all directories without
+ // restriction.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+ && Environment.isExternalStorageManager()) {
+ return APP
+ }
+
val canonicalPathFile = pathFile.canonicalPath
if (internalAppDir != null && canonicalPathFile.startsWith(internalAppDir)) {
@@ -90,7 +97,7 @@ internal enum class StorageScope {
return APP
}
- var rootDir: String? = System.getenv("ANDROID_ROOT")
+ val rootDir: String? = System.getenv("ANDROID_ROOT")
if (rootDir != null && canonicalPathFile.startsWith(rootDir)) {
return APP
}
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 357008ca66..984bf607d0 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
@@ -46,7 +46,7 @@ class FileAccessHandler(val context: Context) {
private val TAG = FileAccessHandler::class.java.simpleName
private const val FILE_NOT_FOUND_ERROR_ID = -1
- private const val INVALID_FILE_ID = 0
+ internal const val INVALID_FILE_ID = 0
private const val STARTING_FILE_ID = 1
internal fun fileExists(context: Context, storageScopeIdentifier: StorageScope.Identifier, path: String?): Boolean {
@@ -96,13 +96,17 @@ class FileAccessHandler(val context: Context) {
private fun hasFileId(fileId: Int) = files.indexOfKey(fileId) >= 0
fun fileOpen(path: String?, modeFlags: Int): Int {
+ val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID
+ return fileOpen(path, accessFlag)
+ }
+
+ internal fun fileOpen(path: String?, accessFlag: FileAccessFlags): Int {
val storageScope = storageScopeIdentifier.identifyStorageScope(path)
if (storageScope == StorageScope.UNKNOWN) {
return INVALID_FILE_ID
}
try {
- val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID
val dataAccess = DataAccess.generateDataAccess(storageScope, context, path!!, accessFlag) ?: return INVALID_FILE_ID
files.put(++lastFileId, dataAccess)
diff --git a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
index ebab8398de..edace53e7f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
@@ -62,8 +62,9 @@ public class GodotTTS extends UtteranceProgressListener {
final private static int EVENT_CANCEL = 2;
final private static int EVENT_BOUNDARY = 3;
- final private TextToSpeech synth;
- final private LinkedList<GodotUtterance> queue;
+ final private Activity activity;
+ private TextToSpeech synth;
+ private LinkedList<GodotUtterance> queue;
final private Object lock = new Object();
private GodotUtterance lastUtterance;
@@ -71,10 +72,7 @@ public class GodotTTS extends UtteranceProgressListener {
private boolean paused;
public GodotTTS(Activity p_activity) {
- synth = new TextToSpeech(p_activity, null);
- queue = new LinkedList<GodotUtterance>();
-
- synth.setOnUtteranceProgressListener(this);
+ activity = p_activity;
}
private void updateTTS() {
@@ -187,6 +185,16 @@ public class GodotTTS extends UtteranceProgressListener {
}
/**
+ * Initialize synth and query.
+ */
+ public void init() {
+ synth = new TextToSpeech(activity, null);
+ queue = new LinkedList<GodotUtterance>();
+
+ synth.setOnUtteranceProgressListener(this);
+ }
+
+ /**
* Adds an utterance to the queue.
*/
public void speak(String text, String voice, int volume, float pitch, float rate, int utterance_id, boolean interrupt) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
new file mode 100644
index 0000000000..1552c8f082
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
@@ -0,0 +1,122 @@
+/**************************************************************************/
+/* BenchmarkUtils.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+@file:JvmName("BenchmarkUtils")
+
+package org.godotengine.godot.utils
+
+import android.os.Build
+import android.os.SystemClock
+import android.os.Trace
+import android.util.Log
+import org.godotengine.godot.BuildConfig
+import org.godotengine.godot.io.file.FileAccessFlags
+import org.godotengine.godot.io.file.FileAccessHandler
+import org.json.JSONObject
+import java.nio.ByteBuffer
+import java.util.concurrent.ConcurrentSkipListMap
+
+/**
+ * Contains benchmark related utilities methods
+ */
+private const val TAG = "GodotBenchmark"
+
+var useBenchmark = false
+var benchmarkFile = ""
+
+private val startBenchmarkFrom = ConcurrentSkipListMap<String, Long>()
+private val benchmarkTracker = ConcurrentSkipListMap<String, Double>()
+
+/**
+ * Start measuring and tracing the execution of a given section of code using the given label.
+ *
+ * Must be followed by a call to [endBenchmarkMeasure].
+ *
+ * Note: Only enabled on 'editorDev' build variant.
+ */
+fun beginBenchmarkMeasure(label: String) {
+ if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
+ return
+ }
+ startBenchmarkFrom[label] = SystemClock.elapsedRealtime()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Trace.beginAsyncSection(label, 0)
+ }
+}
+
+/**
+ * End measuring and tracing of the section of code with the given label.
+ *
+ * Must be preceded by a call [beginBenchmarkMeasure]
+ *
+ * * Note: Only enabled on 'editorDev' build variant.
+ */
+fun endBenchmarkMeasure(label: String) {
+ if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
+ return
+ }
+ val startTime = startBenchmarkFrom[label] ?: return
+ val total = SystemClock.elapsedRealtime() - startTime
+ benchmarkTracker[label] = total / 1000.0
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Trace.endAsyncSection(label, 0)
+ }
+}
+
+/**
+ * Dump the benchmark measurements.
+ * If [filepath] is valid, the data is also written in json format to the specified file.
+ *
+ * * Note: Only enabled on 'editorDev' build variant.
+ */
+@JvmOverloads
+fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) {
+ if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
+ return
+ }
+ if (!useBenchmark) {
+ return
+ }
+
+ val printOut =
+ benchmarkTracker.map { "\t- ${it.key} : ${it.value} sec." }.joinToString("\n")
+ Log.i(TAG, "BENCHMARK:\n$printOut")
+
+ if (fileAccessHandler != null && !filepath.isNullOrBlank()) {
+ val fileId = fileAccessHandler.fileOpen(filepath, FileAccessFlags.WRITE)
+ if (fileId != FileAccessHandler.INVALID_FILE_ID) {
+ val jsonOutput = JSONObject(benchmarkTracker.toMap()).toString(4)
+ fileAccessHandler.fileWrite(fileId, ByteBuffer.wrap(jsonOutput.toByteArray()))
+ fileAccessHandler.fileClose(fileId)
+ }
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
index 401c105cd7..c31d56a3e1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
@@ -33,11 +33,17 @@ package org.godotengine.godot.utils;
import android.app.Activity;
import android.content.Context;
import android.net.wifi.WifiManager;
+import android.util.Base64;
import android.util.Log;
+import java.io.StringWriter;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
/**
* This class handles Android-specific networking functions.
- * For now, it only provides access to WifiManager.MulticastLock, which is needed on some devices
+ * It provides access to the CA certificates KeyStore, and the WifiManager.MulticastLock, which is needed on some devices
* to receive broadcast and multicast packets.
*/
public class GodotNetUtils {
@@ -79,4 +85,34 @@ public class GodotNetUtils {
Log.e("Godot", "Exception during multicast lock release: " + e);
}
}
+
+ /**
+ * Retrieves the list of trusted CA certificates from the "AndroidCAStore" and returns them in PRM format.
+ * @see https://developer.android.com/reference/java/security/KeyStore .
+ * @return A string of concatenated X509 certificates in PEM format.
+ */
+ public static String getCACertificates() {
+ try {
+ KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+ StringBuilder writer = new StringBuilder();
+
+ if (ks != null) {
+ ks.load(null, null);
+ Enumeration<String> aliases = ks.aliases();
+
+ while (aliases.hasMoreElements()) {
+ String alias = (String)aliases.nextElement();
+
+ X509Certificate cert = (X509Certificate)ks.getCertificate(alias);
+ writer.append("-----BEGIN CERTIFICATE-----\n");
+ writer.append(Base64.encodeToString(cert.getEncoded(), Base64.DEFAULT));
+ writer.append("-----END CERTIFICATE-----\n");
+ }
+ }
+ return writer.toString();
+ } catch (Exception e) {
+ Log.e("Godot", "Exception while reading CA certificates: " + e);
+ return "";
+ }
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index e34c94975b..a94188c405 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -42,10 +42,12 @@ import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
* This class includes utility functions for Android permissions related operations.
@@ -58,6 +60,7 @@ public final class PermissionsUtil {
static final int REQUEST_CAMERA_PERMISSION = 2;
static final int REQUEST_VIBRATE_PERMISSION = 3;
public static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001;
+ public static final int REQUEST_SINGLE_PERMISSION_REQ_CODE = 1002;
public static final int REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE = 2002;
private PermissionsUtil() {
@@ -65,31 +68,57 @@ public final class PermissionsUtil {
/**
* Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a>
- * @param name the name of the requested permission.
+ * @param permissionName the name of the requested permission.
* @param activity the caller activity for this method.
* @return true/false. "true" if permission was granted otherwise returns "false".
*/
- public static boolean requestPermission(String name, Activity activity) {
+ public static boolean requestPermission(String permissionName, Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Not necessary, asked on install already
return true;
}
- if (name.equals("RECORD_AUDIO") && ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
- activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION);
- return false;
- }
+ switch (permissionName) {
+ case "RECORD_AUDIO":
+ case Manifest.permission.RECORD_AUDIO:
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION);
+ return false;
+ }
+ return true;
- if (name.equals("CAMERA") && ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
- activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION);
- return false;
- }
+ case "CAMERA":
+ case Manifest.permission.CAMERA:
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION);
+ return false;
+ }
+ return true;
- if (name.equals("VIBRATE") && ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
- activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION);
- return false;
+ case "VIBRATE":
+ case Manifest.permission.VIBRATE:
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION);
+ return false;
+ }
+ return true;
+
+ default:
+ // Check if the given permission is a dangerous permission
+ try {
+ PermissionInfo permissionInfo = getPermissionInfo(activity, permissionName);
+ int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
+ if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permissionName) != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(new String[] { permissionName }, REQUEST_SINGLE_PERMISSION_REQ_CODE);
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Unknown permission - return false as it can't be granted.
+ Log.w(TAG, "Unable to identify permission " + permissionName, e);
+ return false;
+ }
+ return true;
}
- return true;
}
/**
@@ -98,6 +127,16 @@ public final class PermissionsUtil {
* @return true/false. "true" if all permissions were granted otherwise returns "false".
*/
public static boolean requestManifestPermissions(Activity activity) {
+ return requestManifestPermissions(activity, null);
+ }
+
+ /**
+ * Request dangerous permissions which are defined in the Android manifest file from the user.
+ * @param activity the caller activity for this method.
+ * @param excludes Set of permissions to exclude from the request
+ * @return true/false. "true" if all permissions were granted otherwise returns "false".
+ */
+ public static boolean requestManifestPermissions(Activity activity, @Nullable Set<String> excludes) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
@@ -115,6 +154,9 @@ public final class PermissionsUtil {
List<String> requestedPermissions = new ArrayList<>();
for (String manifestPermission : manifestPermissions) {
+ if (excludes != null && excludes.contains(manifestPermission)) {
+ continue;
+ }
try {
if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
index 3ee3478fcb..b1bce45fbb 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
@@ -90,7 +90,7 @@ public final class ProcessPhoenix extends Activity {
*/
public static void forceQuit(Activity activity, int pid) {
Process.killProcess(pid); // Kill original main process
- activity.finish();
+ activity.finishAndRemoveTask();
Runtime.getRuntime().exit(0); // Kill kill kill!
}
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 99e29bb53d..c113a13040 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -31,12 +31,13 @@
#ifndef JAVA_GODOT_IO_WRAPPER_H
#define JAVA_GODOT_IO_WRAPPER_H
-#include <android/log.h>
-#include <jni.h>
+#include "string_android.h"
#include "core/math/rect2i.h"
#include "core/variant/typed_array.h"
-#include "string_android.h"
+
+#include <android/log.h>
+#include <jni.h>
// Class that makes functions in java/src/org/godotengine/godot/GodotIO.java callable from C++
class GodotIOJavaWrapper {
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index e7abe580f1..b54491e0e1 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -30,32 +30,35 @@
#include "java_godot_lib_jni.h"
-#include "java_godot_io_wrapper.h"
-#include "java_godot_wrapper.h"
-
-#include "android/asset_manager_jni.h"
#include "android_input_handler.h"
#include "api/java_class_wrapper.h"
#include "api/jni_singleton.h"
-#include "core/config/engine.h"
-#include "core/config/project_settings.h"
-#include "core/input/input.h"
#include "dir_access_jandroid.h"
#include "display_server_android.h"
#include "file_access_android.h"
#include "file_access_filesystem_jandroid.h"
+#include "java_godot_io_wrapper.h"
+#include "java_godot_wrapper.h"
#include "jni_utils.h"
-#include "main/main.h"
#include "net_socket_android.h"
#include "os_android.h"
#include "string_android.h"
#include "thread_jandroid.h"
#include "tts_android.h"
-#include <android/input.h>
-#include <unistd.h>
+#include "core/config/engine.h"
+#include "core/config/project_settings.h"
+#include "core/input/input.h"
+#include "main/main.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+#include <android/asset_manager_jni.h>
+#include <android/input.h>
#include <android/native_window_jni.h>
+#include <unistd.h>
static JavaClassWrapper *java_class_wrapper = nullptr;
static OS_Android *os_android = nullptr;
@@ -112,7 +115,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei
}
}
-JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion, jobject p_godot_tts) {
+JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion) {
JavaVM *jvm;
env->GetJavaVM(&jvm);
@@ -129,7 +132,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv
DirAccessJAndroid::setup(p_directory_access_handler);
FileAccessFilesystemJAndroid::setup(p_file_access_handler);
NetSocketAndroid::setup(p_net_utils);
- TTS_Android::setup(p_godot_tts);
os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion);
@@ -140,7 +142,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env
_terminate(env, false);
}
-JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) {
+JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts) {
setup_android_thread();
const char **cmdline = nullptr;
@@ -181,6 +183,8 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env
return false;
}
+ TTS_Android::setup(p_godot_tts);
+
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
GDREGISTER_CLASS(JNISingleton);
return true;
@@ -195,10 +199,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j
if (p_surface) {
ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
os_android->set_native_window(native_window);
-
- DisplayServerAndroid::get_singleton()->reset_window();
- DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height);
}
+ DisplayServerAndroid::get_singleton()->reset_window();
+ DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height);
}
}
}
@@ -240,7 +243,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
if (step.get() == 0) {
// Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id,
// but for Godot purposes, the main thread is the one running the game loop
- Main::setup2(Thread::get_caller_id());
+ Main::setup2();
input_handler = new AndroidInputHandler();
step.increment();
return true;
@@ -382,11 +385,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(
}
// Called on the UI thread
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_physical_keycode, jint p_unicode, jint p_key_label, jboolean p_pressed) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_physical_keycode, jint p_unicode, jint p_key_label, jboolean p_pressed, jboolean p_echo) {
if (step.get() <= 0) {
return;
}
- input_handler->process_key_event(p_physical_keycode, p_unicode, p_key_label, p_pressed);
+ input_handler->process_key_event(p_physical_keycode, p_unicode, p_key_label, p_pressed, p_echo);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
@@ -427,43 +430,45 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *
return env->NewStringUTF(GLOBAL_GET(js).operator String().utf8().get_data());
}
+JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key) {
+ String editor_setting = "";
+#ifdef TOOLS_ENABLED
+ String godot_setting_key = jstring_to_string(p_setting_key, env);
+ editor_setting = EDITOR_GET(godot_setting_key).operator String();
+#else
+ WARN_PRINT("Access to the Editor Settings in only available on Editor builds");
+#endif
+
+ return env->NewStringUTF(editor_setting.utf8().get_data());
+}
+
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) {
Object *obj = ObjectDB::get_instance(ObjectID(ID));
ERR_FAIL_NULL(obj);
- int res = env->PushLocalFrame(16);
- ERR_FAIL_COND(res != 0);
-
String str_method = jstring_to_string(method, env);
int count = env->GetArrayLength(params);
+
Variant *vlist = (Variant *)alloca(sizeof(Variant) * count);
- Variant **vptr = (Variant **)alloca(sizeof(Variant *) * count);
+ const Variant **vptr = (const Variant **)alloca(sizeof(Variant *) * count);
+
for (int i = 0; i < count; i++) {
jobject jobj = env->GetObjectArrayElement(params, i);
- Variant v;
- if (jobj) {
- v = _jobject_to_variant(env, jobj);
- }
- memnew_placement(&vlist[i], Variant);
- vlist[i] = v;
+ ERR_FAIL_NULL(jobj);
+ memnew_placement(&vlist[i], Variant(_jobject_to_variant(env, jobj)));
vptr[i] = &vlist[i];
env->DeleteLocalRef(jobj);
}
Callable::CallError err;
- obj->callp(str_method, (const Variant **)vptr, count, err);
-
- env->PopLocalFrame(nullptr);
+ obj->callp(str_method, vptr, count, err);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) {
Object *obj = ObjectDB::get_instance(ObjectID(ID));
ERR_FAIL_NULL(obj);
- int res = env->PushLocalFrame(16);
- ERR_FAIL_COND(res != 0);
-
String str_method = jstring_to_string(method, env);
int count = env->GetArrayLength(params);
@@ -473,16 +478,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
for (int i = 0; i < count; i++) {
jobject jobj = env->GetObjectArrayElement(params, i);
- if (jobj) {
- args[i] = _jobject_to_variant(env, jobj);
- }
- env->DeleteLocalRef(jobj);
+ ERR_FAIL_NULL(jobj);
+ memnew_placement(&args[i], Variant(_jobject_to_variant(env, jobj)));
argptrs[i] = &args[i];
+ env->DeleteLocalRef(jobj);
}
- MessageQueue::get_singleton()->push_callp(obj, str_method, (const Variant **)argptrs, count);
-
- env->PopLocalFrame(nullptr);
+ MessageQueue::get_singleton()->push_callp(obj, str_method, argptrs, count);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 0020ddffd2..ee6a19034c 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -37,9 +37,9 @@
// These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code.
// See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names)
extern "C" {
-JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion, jobject p_godot_tts);
+JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz);
-JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline);
+JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface);
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);
@@ -49,7 +49,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JN
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jboolean p_double_tap);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnify(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_factor);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_pan(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_physical_keycode, jint p_unicode, jint p_key_label, jboolean p_pressed);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_physical_keycode, jint p_unicode, jint p_key_label, jboolean p_pressed, jboolean p_echo);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y);
@@ -61,6 +61,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz);
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path);
+JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height);
diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp
index b50d4870bd..a95f762e01 100644
--- a/platform/android/java_godot_view_wrapper.cpp
+++ b/platform/android/java_godot_view_wrapper.cpp
@@ -49,6 +49,8 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
_request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V");
_release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V");
}
+
+ _can_capture_pointer = env->GetMethodID(_cls, "canCapturePointer", "()Z");
}
bool GodotJavaViewWrapper::can_update_pointer_icon() const {
@@ -56,7 +58,16 @@ bool GodotJavaViewWrapper::can_update_pointer_icon() const {
}
bool GodotJavaViewWrapper::can_capture_pointer() const {
- return _request_pointer_capture != nullptr && _release_pointer_capture != nullptr;
+ // We can capture the pointer if the other jni capture method ids are initialized,
+ // and GodotView#canCapturePointer() returns true.
+ if (_request_pointer_capture != nullptr && _release_pointer_capture != nullptr && _can_capture_pointer != nullptr) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+
+ return env->CallBooleanMethod(_godot_view, _can_capture_pointer);
+ }
+
+ return false;
}
void GodotJavaViewWrapper::request_pointer_capture() {
diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h
index 9b64ded29c..e5b04e4866 100644
--- a/platform/android/java_godot_view_wrapper.h
+++ b/platform/android/java_godot_view_wrapper.h
@@ -31,12 +31,13 @@
#ifndef JAVA_GODOT_VIEW_WRAPPER_H
#define JAVA_GODOT_VIEW_WRAPPER_H
+#include "string_android.h"
+
#include "core/math/vector2.h"
+
#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:
@@ -44,6 +45,7 @@ private:
jobject _godot_view;
+ jmethodID _can_capture_pointer = 0;
jmethodID _request_pointer_capture = 0;
jmethodID _release_pointer_capture = 0;
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 9d9d087896..862d9f0436 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -70,6 +70,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
_request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z");
_get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;");
+ _get_ca_certificates = p_env->GetMethodID(godot_class, "getCACertificates", "()Ljava/lang/String;");
_init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V");
_get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;");
_is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z");
@@ -79,6 +80,9 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
_create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I");
_get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
+ _begin_benchmark_measure = p_env->GetMethodID(godot_class, "beginBenchmarkMeasure", "(Ljava/lang/String;)V");
+ _end_benchmark_measure = p_env->GetMethodID(godot_class, "endBenchmarkMeasure", "(Ljava/lang/String;)V");
+ _dump_benchmark = p_env->GetMethodID(godot_class, "dumpBenchmark", "(Ljava/lang/String;)V");
// get some Activity method pointers...
_get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;");
@@ -310,6 +314,17 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const {
return permissions_list;
}
+String GodotJavaWrapper::get_ca_certificates() const {
+ if (_get_ca_certificates) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, String());
+ jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_ca_certificates);
+ return jstring_to_string(s, env);
+ } else {
+ return String();
+ }
+}
+
void GodotJavaWrapper::init_input_devices() {
if (_init_input_devices) {
JNIEnv *env = get_jni_env();
@@ -359,3 +374,30 @@ int GodotJavaWrapper::create_new_godot_instance(List<String> args) {
return 0;
}
}
+
+void GodotJavaWrapper::begin_benchmark_measure(const String &p_label) {
+ if (_begin_benchmark_measure) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+ jstring j_label = env->NewStringUTF(p_label.utf8().get_data());
+ env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_label);
+ }
+}
+
+void GodotJavaWrapper::end_benchmark_measure(const String &p_label) {
+ if (_end_benchmark_measure) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+ jstring j_label = env->NewStringUTF(p_label.utf8().get_data());
+ env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_label);
+ }
+}
+
+void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) {
+ if (_dump_benchmark) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+ jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data());
+ env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file);
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 1bd79584d8..1efdffd71b 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -31,13 +31,14 @@
#ifndef JAVA_GODOT_WRAPPER_H
#define JAVA_GODOT_WRAPPER_H
-#include <android/log.h>
-#include <jni.h>
-
-#include "core/templates/list.h"
#include "java_godot_view_wrapper.h"
#include "string_android.h"
+#include "core/templates/list.h"
+
+#include <android/log.h>
+#include <jni.h>
+
// Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++
class GodotJavaWrapper {
private:
@@ -60,6 +61,7 @@ private:
jmethodID _request_permission = nullptr;
jmethodID _request_permissions = nullptr;
jmethodID _get_granted_permissions = nullptr;
+ jmethodID _get_ca_certificates = nullptr;
jmethodID _init_input_devices = nullptr;
jmethodID _get_surface = nullptr;
jmethodID _is_activity_resumed = nullptr;
@@ -70,6 +72,9 @@ private:
jmethodID _get_class_loader = nullptr;
jmethodID _create_new_godot_instance = nullptr;
jmethodID _get_render_view = nullptr;
+ jmethodID _begin_benchmark_measure = nullptr;
+ jmethodID _end_benchmark_measure = nullptr;
+ jmethodID _dump_benchmark = nullptr;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -98,12 +103,16 @@ public:
bool request_permission(const String &p_name);
bool request_permissions();
Vector<String> get_granted_permissions() const;
+ String get_ca_certificates() const;
void init_input_devices();
jobject get_surface();
bool is_activity_resumed();
void vibrate(int p_duration_ms);
String get_input_fallback_mapping();
int create_new_godot_instance(List<String> args);
+ void begin_benchmark_measure(const String &p_label);
+ void end_benchmark_measure(const String &p_label);
+ void dump_benchmark(const String &benchmark_file);
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h
index d1a4082ae5..c608f9ebaa 100644
--- a/platform/android/jni_utils.h
+++ b/platform/android/jni_utils.h
@@ -32,8 +32,10 @@
#define JNI_UTILS_H
#include "string_android.h"
-#include <core/config/engine.h>
-#include <core/variant/variant.h>
+
+#include "core/config/engine.h"
+#include "core/variant/variant.h"
+
#include <jni.h>
struct jvalret {
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 725fea8d54..c040d8c4c6 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -30,25 +30,24 @@
#include "os_android.h"
+#include "dir_access_jandroid.h"
+#include "display_server_android.h"
+#include "file_access_android.h"
+#include "file_access_filesystem_jandroid.h"
+#include "java_godot_io_wrapper.h"
+#include "java_godot_wrapper.h"
+#include "net_socket_android.h"
+
#include "core/config/project_settings.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "main/main.h"
-#include "platform/android/display_server_android.h"
#include "scene/main/scene_tree.h"
#include "servers/rendering_server.h"
-#include "dir_access_jandroid.h"
-#include "file_access_android.h"
-#include "file_access_filesystem_jandroid.h"
-#include "net_socket_android.h"
-
#include <dlfcn.h>
#include <sys/system_properties.h>
-#include "java_godot_io_wrapper.h"
-#include "java_godot_wrapper.h"
-
const char *OS_Android::ANDROID_EXEC_PATH = "apk";
String _remove_symlink(const String &dir) {
@@ -168,7 +167,7 @@ Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_han
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
- ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");
+ 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;
@@ -182,7 +181,7 @@ String OS_Android::get_name() const {
}
String OS_Android::get_system_property(const char *key) const {
- static String value;
+ String value;
char value_str[PROP_VALUE_MAX];
if (__system_property_get(key, value_str)) {
value = String(value_str);
@@ -230,20 +229,20 @@ String OS_Android::get_version() const {
"ro.potato.version", "ro.xtended.version", "org.evolution.version", "ro.corvus.version", "ro.pa.version",
"ro.crdroid.version", "ro.syberia.version", "ro.arrow.version", "ro.lineage.version" };
for (int i = 0; i < roms.size(); i++) {
- static String rom_version = get_system_property(roms[i]);
+ String rom_version = get_system_property(roms[i]);
if (!rom_version.is_empty()) {
return rom_version;
}
}
- static String mod_version = get_system_property("ro.modversion"); // Handles other Android custom ROMs.
+ String mod_version = get_system_property("ro.modversion"); // Handles other Android custom ROMs.
if (!mod_version.is_empty()) {
return mod_version;
}
// Handles stock Android.
- static String sdk_version = get_system_property("ro.build.version.sdk_int");
- static String build = get_system_property("ro.build.version.incremental");
+ String sdk_version = get_system_property("ro.build.version.sdk_int");
+ String build = get_system_property("ro.build.version.incremental");
if (!sdk_version.is_empty()) {
if (!build.is_empty()) {
return vformat("%s.%s", sdk_version, build);
@@ -311,7 +310,11 @@ String OS_Android::get_resource_dir() const {
#ifdef TOOLS_ENABLED
return OS_Unix::get_resource_dir();
#else
- return "/"; //android has its own filesystem for resources inside the APK
+ if (remote_fs_dir.is_empty()) {
+ return "/"; // Android has its own filesystem for resources inside the APK
+ } else {
+ return remote_fs_dir;
+ }
#endif
}
@@ -671,6 +674,27 @@ String OS_Android::get_config_path() const {
return get_user_data_dir().path_join("config");
}
+void OS_Android::benchmark_begin_measure(const String &p_what) {
+#ifdef TOOLS_ENABLED
+ godot_java->begin_benchmark_measure(p_what);
+#endif
+}
+
+void OS_Android::benchmark_end_measure(const String &p_what) {
+#ifdef TOOLS_ENABLED
+ godot_java->end_benchmark_measure(p_what);
+#endif
+}
+
+void OS_Android::benchmark_dump() {
+#ifdef TOOLS_ENABLED
+ if (!is_use_benchmark_set()) {
+ return;
+ }
+ godot_java->dump_benchmark(get_benchmark_file());
+#endif
+}
+
bool OS_Android::_check_internal_feature_support(const String &p_feature) {
if (p_feature == "system_fonts") {
return true;
@@ -753,5 +777,19 @@ Error OS_Android::kill(const ProcessID &p_pid) {
return OS_Unix::kill(p_pid);
}
+String OS_Android::get_system_ca_certificates() {
+ return godot_java->get_ca_certificates();
+}
+
+Error OS_Android::setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) {
+ r_project_path = get_user_data_dir();
+ Error err = OS_Unix::setup_remote_filesystem(p_server_host, p_port, p_password, r_project_path);
+ if (err == OK) {
+ remote_fs_dir = r_project_path;
+ FileAccess::make_default<FileAccessFilesystemJAndroid>(FileAccess::ACCESS_RESOURCES);
+ }
+ return err;
+}
+
OS_Android::~OS_Android() {
}
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index 53910b1498..abcc412588 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -32,6 +32,7 @@
#define OS_ANDROID_H
#include "audio_driver_opensl.h"
+
#include "core/os/main_loop.h"
#include "drivers/unix/os_unix.h"
#include "servers/audio_server.h"
@@ -57,6 +58,7 @@ private:
mutable String data_dir_cache;
mutable String cache_dir_cache;
+ mutable String remote_fs_dir;
AudioDriverOpenSL audio_driver_android;
@@ -159,6 +161,13 @@ public:
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
virtual Error kill(const ProcessID &p_pid) override;
+ virtual String get_system_ca_certificates() override;
+
+ virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) override;
+
+ virtual void benchmark_begin_measure(const String &p_what) override;
+ virtual void benchmark_end_measure(const String &p_what) override;
+ virtual void benchmark_dump() override;
virtual bool _check_internal_feature_support(const String &p_feature) override;
OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index 4bb90cb971..5d48c4e248 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -30,12 +30,13 @@
#include "godot_plugin_jni.h"
-#include <core/config/engine.h>
-#include <core/config/project_settings.h>
-#include <core/error/error_macros.h>
-#include <platform/android/api/jni_singleton.h>
-#include <platform/android/jni_utils.h>
-#include <platform/android/string_android.h>
+#include "api/jni_singleton.h"
+#include "jni_utils.h"
+#include "string_android.h"
+
+#include "core/config/engine.h"
+#include "core/config/project_settings.h"
+#include "core/error/error_macros.h"
static HashMap<String, JNISingleton *> jni_singletons;
@@ -120,7 +121,8 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS
for (int i = 0; i < count; i++) {
jobject j_param = env->GetObjectArrayElement(j_signal_params, i);
- variant_params[i] = _jobject_to_variant(env, j_param);
+ ERR_FAIL_NULL(j_param);
+ memnew_placement(&variant_params[i], Variant(_jobject_to_variant(env, j_param)));
args[i] = &variant_params[i];
env->DeleteLocalRef(j_param);
}
diff --git a/platform/android/string_android.h b/platform/android/string_android.h
index fe2f2e20a7..3f30b8ec3d 100644
--- a/platform/android/string_android.h
+++ b/platform/android/string_android.h
@@ -31,8 +31,10 @@
#ifndef STRING_ANDROID_H
#define STRING_ANDROID_H
-#include "core/string/ustring.h"
#include "thread_jandroid.h"
+
+#include "core/string/ustring.h"
+
#include <jni.h>
/**
diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp
index 5051c179ed..9f4140f70f 100644
--- a/platform/android/thread_jandroid.cpp
+++ b/platform/android/thread_jandroid.cpp
@@ -30,10 +30,10 @@
#include "thread_jandroid.h"
-#include <android/log.h>
-
#include "core/os/thread.h"
+#include <android/log.h>
+
static JavaVM *java_vm = nullptr;
static thread_local JNIEnv *env = nullptr;
diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp
index c08c1d4941..aef59c2584 100644
--- a/platform/android/tts_android.cpp
+++ b/platform/android/tts_android.cpp
@@ -35,9 +35,11 @@
#include "string_android.h"
#include "thread_jandroid.h"
+bool TTS_Android::initialized = false;
jobject TTS_Android::tts = nullptr;
jclass TTS_Android::cls = nullptr;
+jmethodID TTS_Android::_init = nullptr;
jmethodID TTS_Android::_is_speaking = nullptr;
jmethodID TTS_Android::_is_paused = nullptr;
jmethodID TTS_Android::_get_voices = nullptr;
@@ -49,23 +51,34 @@ jmethodID TTS_Android::_stop_speaking = nullptr;
HashMap<int, Char16String> TTS_Android::ids;
void TTS_Android::setup(jobject p_tts) {
- JNIEnv *env = get_jni_env();
+ bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
+ if (tts_enabled) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
+ tts = env->NewGlobalRef(p_tts);
- tts = env->NewGlobalRef(p_tts);
+ jclass c = env->GetObjectClass(tts);
+ cls = (jclass)env->NewGlobalRef(c);
- jclass c = env->GetObjectClass(tts);
- cls = (jclass)env->NewGlobalRef(c);
+ _init = env->GetMethodID(cls, "init", "()V");
+ _is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
+ _is_paused = env->GetMethodID(cls, "isPaused", "()Z");
+ _get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
+ _speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
+ _pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
+ _resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
+ _stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");
- _is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
- _is_paused = env->GetMethodID(cls, "isPaused", "()Z");
- _get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
- _speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
- _pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
- _resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
- _stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");
+ if (_init) {
+ env->CallVoidMethod(tts, _init);
+ initialized = true;
+ }
+ }
}
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)) {
int pos = 0;
if ((DisplayServer::TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {
@@ -86,6 +99,7 @@ void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
}
bool TTS_Android::is_speaking() {
+ ERR_FAIL_COND_V_MSG(!initialized, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (_is_speaking) {
JNIEnv *env = get_jni_env();
@@ -97,6 +111,7 @@ bool TTS_Android::is_speaking() {
}
bool TTS_Android::is_paused() {
+ ERR_FAIL_COND_V_MSG(!initialized, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (_is_paused) {
JNIEnv *env = get_jni_env();
@@ -108,6 +123,7 @@ bool TTS_Android::is_paused() {
}
Array TTS_Android::get_voices() {
+ ERR_FAIL_COND_V_MSG(!initialized, Array(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
Array list;
if (_get_voices) {
JNIEnv *env = get_jni_env();
@@ -135,6 +151,7 @@ Array TTS_Android::get_voices() {
}
void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (p_interrupt) {
stop();
}
@@ -157,6 +174,7 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum
}
void TTS_Android::pause() {
+ ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (_pause_speaking) {
JNIEnv *env = get_jni_env();
@@ -166,6 +184,7 @@ void TTS_Android::pause() {
}
void TTS_Android::resume() {
+ ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (_resume_speaking) {
JNIEnv *env = get_jni_env();
@@ -175,6 +194,7 @@ void TTS_Android::resume() {
}
void TTS_Android::stop() {
+ ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
for (const KeyValue<int, Char16String> &E : ids) {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);
}
diff --git a/platform/android/tts_android.h b/platform/android/tts_android.h
index 8e3bfccbdb..39efef6ed1 100644
--- a/platform/android/tts_android.h
+++ b/platform/android/tts_android.h
@@ -31,16 +31,20 @@
#ifndef TTS_ANDROID_H
#define TTS_ANDROID_H
+#include "core/config/project_settings.h"
#include "core/string/ustring.h"
+#include "core/templates/hash_map.h"
#include "core/variant/array.h"
#include "servers/display_server.h"
#include <jni.h>
class TTS_Android {
+ static bool initialized;
static jobject tts;
static jclass cls;
+ static jmethodID _init;
static jmethodID _is_speaking;
static jmethodID _is_paused;
static jmethodID _get_voices;
diff --git a/platform/android/vulkan/vulkan_context_android.cpp b/platform/android/vulkan_context_android.cpp
index ce4b1b7967..01e6d14438 100644
--- a/platform/android/vulkan/vulkan_context_android.cpp
+++ b/platform/android/vulkan_context_android.cpp
@@ -28,10 +28,10 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifdef VULKAN_ENABLED
-
#include "vulkan_context_android.h"
+#ifdef VULKAN_ENABLED
+
#ifdef USE_VOLK
#include <volk.h>
#else
diff --git a/platform/android/vulkan/vulkan_context_android.h b/platform/android/vulkan_context_android.h
index f253149ef6..f253149ef6 100644
--- a/platform/android/vulkan/vulkan_context_android.h
+++ b/platform/android/vulkan_context_android.h