summaryrefslogtreecommitdiff
path: root/platform/android
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/detect.py13
-rw-r--r--platform/android/display_server_android.cpp7
-rw-r--r--platform/android/display_server_android.h2
-rw-r--r--platform/android/export/export.cpp669
-rw-r--r--platform/android/export/gradle_export_util.h102
-rw-r--r--platform/android/java/app/AndroidManifest.xml2
-rw-r--r--platform/android/java/app/build.gradle31
-rw-r--r--platform/android/java/app/config.gradle47
-rw-r--r--platform/android/java/app/res/drawable/splash.pngbin0 -> 14766 bytes
-rw-r--r--platform/android/java/app/res/drawable/splash_bg_color.pngbin0 -> 1360 bytes
-rw-r--r--platform/android/java/app/res/drawable/splash_drawable.xml12
-rw-r--r--platform/android/java/app/res/values/themes.xml9
-rw-r--r--platform/android/java/app/src/com/godot/game/GodotApp.java7
-rw-r--r--platform/android/java/lib/res/values/dimens.xml4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java24
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt2
-rw-r--r--platform/android/java_godot_io_wrapper.cpp6
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/android/logo.pngbin1459 -> 951 bytes
23 files changed, 733 insertions, 241 deletions
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 6da1e5f3d6..0accacb679 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -115,7 +115,8 @@ def configure(env):
if env["android_arch"] == "x86_64":
if get_platform(env["ndk_platform"]) < 21:
print(
- "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21"
+ "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting"
+ " ndk_platform=android-21"
)
env["ndk_platform"] = "android-21"
env["ARCH"] = "arch-x86_64"
@@ -136,7 +137,8 @@ def configure(env):
elif env["android_arch"] == "arm64v8":
if get_platform(env["ndk_platform"]) < 21:
print(
- "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21"
+ "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting"
+ " ndk_platform=android-21"
)
env["ndk_platform"] = "android-21"
env["ARCH"] = "arch-arm64"
@@ -164,7 +166,7 @@ def configure(env):
elif env["target"] == "debug":
env.Append(LINKFLAGS=["-O0"])
env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"])
- env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"])
env.Append(CPPFLAGS=["-UNDEBUG"])
# Compiler configuration
@@ -231,7 +233,10 @@ def configure(env):
env.Append(CPPDEFINES=[("__ANDROID_API__", str(get_platform(env["ndk_platform"])))])
env.Append(
- CCFLAGS="-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split()
+ CCFLAGS=(
+ "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden"
+ " -fno-strict-aliasing".split()
+ )
)
env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"])
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 7193519a52..3aa2fb5451 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -36,9 +36,6 @@
#include "java_godot_wrapper.h"
#include "os_android.h"
-#if defined(OPENGL_ENABLED)
-#include "drivers/gles2/rasterizer_gles2.h"
-#endif
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "platform/android/vulkan/vulkan_context_android.h"
@@ -155,12 +152,12 @@ bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const {
return true;
}
-void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) {
+void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_COND(!godot_io_java);
if (godot_io_java->has_vk()) {
- godot_io_java->show_vk(p_existing_text, p_max_length, p_cursor_start, p_cursor_end);
+ godot_io_java->show_vk(p_existing_text, p_multiline, p_max_length, p_cursor_start, p_cursor_end);
} else {
ERR_PRINT("Virtual keyboard not available");
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 4cae52fa76..5cdc69ee83 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -113,7 +113,7 @@ public:
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void virtual_keyboard_hide();
virtual int virtual_keyboard_get_height() const;
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index ed85256695..5e6cc3e4e2 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "export.h"
+#include "gradle_export_util.h"
#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
@@ -43,6 +44,7 @@
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "main/splash.gen.h"
#include "platform/android/export/gradle_export_util.h"
#include "platform/android/logo.gen.h"
#include "platform/android/plugin/godot_plugin_config.h"
@@ -199,6 +201,9 @@ static const char *android_perms[] = {
nullptr
};
+static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable/splash.png";
+static const char *SPLASH_BG_COLOR_PATH = "res/drawable/splash_bg_color.png";
+
struct LauncherIcon {
const char *export_path;
int dimensions;
@@ -452,7 +457,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
String name;
bool first = true;
for (int i = 0; i < basename.length(); i++) {
- CharType c = basename[i];
+ char32_t c = basename[i];
if (c >= '0' && c <= '9' && first) {
continue;
}
@@ -483,7 +488,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int segments = 0;
bool first = true;
for (int i = 0; i < pname.length(); i++) {
- CharType c = pname[i];
+ char32_t c = pname[i];
if (first && c == '.') {
if (r_error) {
*r_error = TTR("Package segments must be of non-zero length.");
@@ -722,7 +727,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return OK;
}
- static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+ static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
APKExportData *ed = (APKExportData *)p_userdata;
String dst_path = p_path.replace_first("res://", "assets/");
@@ -730,7 +735,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return OK;
}
- static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+ static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
return OK;
}
@@ -767,6 +772,30 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
+ void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) {
+ String manifest_text =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\">\n";
+
+ manifest_text += _get_screen_sizes_tag(p_preset);
+ manifest_text += _get_gles_tag();
+
+ Vector<String> perms;
+ _get_permissions(p_preset, p_give_internet, perms);
+ for (int i = 0; i < perms.size(); i++) {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
+ }
+
+ manifest_text += _get_xr_features_tag(p_preset);
+ manifest_text += _get_instrumentation_tag(p_preset);
+ String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
+ manifest_text += _get_application_tag(p_preset, plugins_names);
+ manifest_text += "</manifest>\n";
+ String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
+ store_string_at_path(manifest_path, manifest_text);
+ }
+
void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
// Leaving the unused types commented because looking these constants up
// again later would be annoying
@@ -848,7 +877,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
if (string_flags & UTF8_FLAG) {
} else {
uint32_t len = decode_uint16(&p_manifest[string_at]);
- Vector<CharType> ucstring;
+ Vector<char32_t> ucstring;
ucstring.resize(len + 1);
for (uint32_t j = 0; j < len; j++) {
uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]);
@@ -1309,7 +1338,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
} else {
String str;
for (uint32_t i = 0; i < len; i++) {
- CharType c = decode_uint16(&p_bytes[offset + i * 2]);
+ char32_t c = decode_uint16(&p_bytes[offset + i * 2]);
if (c == 0) {
break;
}
@@ -1408,23 +1437,143 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
//printf("end\n");
}
- void _process_launcher_icons(const String &p_processing_file_name, const Ref<Image> &p_source_image, const LauncherIcon p_icon, Vector<uint8_t> &p_data) {
- if (p_processing_file_name == p_icon.export_path) {
- Ref<Image> working_image = p_source_image;
+ void _load_image_data(const Ref<Image> &p_splash_image, Vector<uint8_t> &p_data) {
+ Vector<uint8_t> png_buffer;
+ Error err = PNGDriverCommon::image_to_png(p_splash_image, png_buffer);
+ if (err == OK) {
+ p_data.resize(png_buffer.size());
+ memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
+ } else {
+ String err_str = String("Failed to convert splash image to png.");
+ WARN_PRINT(err_str.utf8().get_data());
+ }
+ }
+
+ void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) {
+ Ref<Image> working_image = p_source_image;
- if (p_source_image->get_width() != p_icon.dimensions || p_source_image->get_height() != p_icon.dimensions) {
- working_image = p_source_image->duplicate();
- working_image->resize(p_icon.dimensions, p_icon.dimensions, Image::Interpolation::INTERPOLATE_LANCZOS);
+ if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) {
+ working_image = p_source_image->duplicate();
+ working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS);
+ }
+
+ Vector<uint8_t> png_buffer;
+ Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
+ if (err == OK) {
+ p_data.resize(png_buffer.size());
+ memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
+ } else {
+ String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png.";
+ WARN_PRINT(err_str.utf8().get_data());
+ }
+ }
+
+ void load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) {
+ // TODO: Figure out how to handle remaining boot splash parameters (e.g: fullsize, filter)
+ String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+
+ if (!project_splash_path.empty()) {
+ splash_image.instance();
+ const Error err = ImageLoader::load_image(project_splash_path, splash_image);
+ if (err) {
+ splash_image.unref();
}
+ }
- Vector<uint8_t> png_buffer;
- Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
- if (err == OK) {
- p_data.resize(png_buffer.size());
- memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
- } else {
- String err_str = String("Failed to convert resized icon (") + p_processing_file_name + ") to png.";
- WARN_PRINT(err_str.utf8().get_data());
+ if (splash_image.is_null()) {
+ // Use the default
+ splash_image = Ref<Image>(memnew(Image(boot_splash_png)));
+ }
+
+ // Setup the splash bg color
+ bool bg_color_valid;
+ Color bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color", &bg_color_valid);
+ if (!bg_color_valid) {
+ bg_color = boot_splash_bg_color;
+ }
+
+ splash_bg_color_image.instance();
+ splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format());
+ splash_bg_color_image->fill(bg_color);
+ }
+
+ void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) {
+ String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
+
+ icon.instance();
+ foreground.instance();
+ background.instance();
+
+ // Regular icon: user selection -> project icon -> default.
+ String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
+ if (path.empty() || ImageLoader::load_image(path, icon) != OK) {
+ ImageLoader::load_image(project_icon_path, icon);
+ }
+
+ // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
+ if (path.empty() || ImageLoader::load_image(path, foreground) != OK) {
+ foreground = icon;
+ }
+
+ // Adaptive background: user selection -> default.
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
+ if (!path.empty()) {
+ ImageLoader::load_image(path, background);
+ }
+ }
+
+ void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) {
+ store_image(launcher_icon.export_path, data);
+ }
+
+ void store_image(const String &export_path, const Vector<uint8_t> &data) {
+ String img_path = export_path.insert(0, "res://android/build/");
+ store_file_at_path(img_path, data);
+ }
+
+ void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset,
+ const Ref<Image> &splash_image,
+ const Ref<Image> &splash_bg_color_image,
+ const Ref<Image> &main_image,
+ const Ref<Image> &foreground,
+ const Ref<Image> &background) {
+ // Store the splash image
+ if (splash_image.is_valid() && !splash_image->empty()) {
+ Vector<uint8_t> data;
+ _load_image_data(splash_image, data);
+ store_image(SPLASH_IMAGE_EXPORT_PATH, data);
+ }
+
+ // Store the splash bg color image
+ if (splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) {
+ Vector<uint8_t> data;
+ _load_image_data(splash_bg_color_image, data);
+ store_image(SPLASH_BG_COLOR_PATH, data);
+ }
+
+ // Prepare images to be resized for the icons. If some image ends up being uninitialized,
+ // the default image from the export template will be used.
+
+ for (int i = 0; i < icon_densities_count; ++i) {
+ if (main_image.is_valid() && !main_image->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data);
+ store_image(launcher_icons[i], data);
+ }
+
+ if (foreground.is_valid() && !foreground->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground,
+ launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_foregrounds[i], data);
+ }
+
+ if (background.is_valid() && !background->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background,
+ launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_backgrounds[i], data);
}
}
}
@@ -1442,7 +1591,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
public:
- typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
+ typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
public:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override {
@@ -1471,6 +1620,7 @@ public:
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, "custom_template/use_custom_build"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), 0));
Vector<PluginConfig> plugins_configs = get_plugins();
for (int i = 0; i < plugins_configs.size(); i++) {
@@ -1891,6 +2041,13 @@ public:
}
}
+ if (int(p_preset->get("custom_template/export_format")) == 1 && /*AAB*/
+ !bool(p_preset->get("custom_template/use_custom_build"))) {
+ valid = false;
+ err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled.");
+ err += "\n";
+ }
+
r_error = err;
return valid;
}
@@ -1898,6 +2055,7 @@ public:
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
List<String> list;
list.push_back("apk");
+ list.push_back("aab");
return list;
}
@@ -1924,7 +2082,21 @@ public:
return have_plugins_changed || first_build;
}
- Error get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
+ String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+ int version_code = p_preset->get("version/code");
+ String package_name = p_preset->get("package/unique_name");
+ String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
+ String fullpath = p_path.get_base_dir().plus_file(apk_file_name);
+ return fullpath;
+ }
+
+ Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+ String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
+ Error err = save_pack(p_preset, fullpath);
+ return err;
+ }
+
+ void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
String cmdline = p_preset->get("command_line/extra_args");
Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
for (int i = 0; i < command_line_strings.size(); i++) {
@@ -1938,17 +2110,8 @@ public:
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
- int version_code = p_preset->get("version/code");
- String package_name = p_preset->get("package/unique_name");
- String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
- String fullpath = p_path.get_base_dir().plus_file(apk_file_name);
+ String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
String apk_expansion_public_key = p_preset->get("apk_expansion/public_key");
- Error err = save_pack(p_preset, fullpath);
-
- if (err != OK) {
- EditorNode::add_io_error("Could not write expansion package file: " + apk_file_name);
- return err;
- }
command_line_strings.push_back("--use_apk_expansion");
command_line_strings.push_back("--apk_expansion_md5");
@@ -1994,6 +2157,92 @@ public:
copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length);
}
}
+ }
+
+ Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String apk_path, EditorProgress ep) {
+ 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 jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
+ if (!FileAccess::exists(jarsigner)) {
+ EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned.");
+ return OK;
+ }
+
+ String keystore;
+ 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");
+
+ if (keystore.empty()) {
+ keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
+ password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
+ user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
+ }
+
+ if (ep.step("Signing debug APK...", 103)) {
+ return ERR_SKIP;
+ }
+
+ } else {
+ keystore = release_keystore;
+ password = release_password;
+ user = release_username;
+
+ if (ep.step("Signing release APK...", 103)) {
+ return ERR_SKIP;
+ }
+ }
+
+ if (!FileAccess::exists(keystore)) {
+ EditorNode::add_io_error("Could not find keystore, unable to export.");
+ return ERR_FILE_CANT_OPEN;
+ }
+
+ List<String> args;
+ args.push_back("-digestalg");
+ args.push_back("SHA-256");
+ args.push_back("-sigalg");
+ args.push_back("SHA256withRSA");
+ String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url");
+ if (tsa_url != "") {
+ args.push_back("-tsa");
+ args.push_back(tsa_url);
+ }
+ args.push_back("-verbose");
+ args.push_back("-keystore");
+ args.push_back(keystore);
+ args.push_back("-storepass");
+ args.push_back(password);
+ args.push_back(apk_path);
+ args.push_back(user);
+ int retval;
+ OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
+ if (retval) {
+ EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
+ return ERR_CANT_CREATE;
+ }
+
+ if (ep.step("Verifying APK...", 104)) {
+ return ERR_SKIP;
+ }
+
+ args.clear();
+ args.push_back("-verify");
+ args.push_back("-keystore");
+ args.push_back(keystore);
+ args.push_back(apk_path);
+ args.push_back("-verbose");
+
+ OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
+ if (retval) {
+ EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8.");
+ return ERR_CANT_CREATE;
+ }
return OK;
}
@@ -2001,41 +2250,102 @@ public:
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
String src_apk;
+ Error err;
EditorProgress ep("export", "Exporting for Android", 105, true);
bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
+ int export_format = int(p_preset->get("custom_template/export_format"));
+ bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
+ bool _signed = p_preset->get("package/signed");
+ bool apk_expansion = p_preset->get("apk_expansion/enable");
+ Vector<String> enabled_abis = get_enabled_abis(p_preset);
+
+ Ref<Image> splash_image;
+ Ref<Image> splash_bg_color_image;
+ load_splash_refs(splash_image, splash_bg_color_image);
+
+ Ref<Image> main_image;
+ Ref<Image> foreground;
+ Ref<Image> background;
+
+ load_icon_refs(p_preset, main_image, foreground, background);
+
+ Vector<uint8_t> command_line_flags;
+ // Write command line flags into the command_line_flags variable.
+ get_command_line_flags(p_preset, p_path, p_flags, command_line_flags);
+
+ if (export_format == 1) {
+ if (!p_path.ends_with(".aab")) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension."));
+ return ERR_UNCONFIGURED;
+ }
+ if (apk_expansion) {
+ EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle."));
+ return ERR_UNCONFIGURED;
+ }
+ }
+ if (export_format == 0 && !p_path.ends_with(".apk")) {
+ EditorNode::get_singleton()->show_warning(
+ TTR("Invalid filename! Android APK requires the *.apk extension."));
+ return ERR_UNCONFIGURED;
+ }
+ if (export_format > 1 || export_format < 0) {
+ EditorNode::add_io_error("Unsupported export format!\n");
+ return ERR_UNCONFIGURED; //TODO: is this the right error?
+ }
if (use_custom_build) {
- //re-generate build.gradle and AndroidManifest.xml
- { //test that installed build version is alright
- FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
- if (!f) {
- EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
- return ERR_UNCONFIGURED;
- }
- String version = f->get_line().strip_edges();
- if (version != VERSION_FULL_CONFIG) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
- return ERR_UNCONFIGURED;
- }
+ //test that installed build version is alright
+ FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
+ if (!f) {
+ EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
+ return ERR_UNCONFIGURED;
+ }
+ String version = f->get_line().strip_edges();
+ if (version != VERSION_FULL_CONFIG) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
+ return ERR_UNCONFIGURED;
}
+ String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
+ ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
// TODO: should we use "package/name" or "application/config/name"?
String project_name = get_project_name(p_preset->get("package/name"));
- // instead of calling _fix_resources
- Error err = _create_project_name_strings_files(p_preset, project_name);
+ err = _create_project_name_strings_files(p_preset, project_name); //project name localization.
if (err != OK) {
EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");
}
- //build project if custom build is enabled
- String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
-
- ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
+ // Copies the project icon files into the appropriate Gradle project directory.
+ _copy_icons_to_gradle_project(p_preset, splash_image, splash_bg_color_image, main_image, foreground, background);
+ // Write an AndroidManifest.xml file into the Gradle project directory.
+ _write_tmp_manifest(p_preset, p_give_internet, p_debug);
+
+ //stores all the project files inside the Gradle project directory. Also includes all ABIs
+ if (!apk_expansion) {
+ DirAccess *da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ if (da_res->dir_exists("res://android/build/assets")) {
+ DirAccess *da_assets = DirAccess::open("res://android/build/assets");
+ da_assets->erase_contents_recursive();
+ da_res->remove("res://android/build/assets");
+ }
+ err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file);
+ if (err != OK) {
+ EditorNode::add_io_error("Could not export project files to gradle project\n");
+ return err;
+ }
+ } else {
+ err = save_apk_expansion_file(p_preset, p_path);
+ if (err != OK) {
+ EditorNode::add_io_error("Could not write expansion package file!");
+ return err;
+ }
+ }
+ store_file_at_path("res://android/build/assets/_cl_", command_line_flags);
OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
-
String build_command;
+
#ifdef WINDOWS_ENABLED
build_command = "gradlew.bat";
#else
@@ -2043,10 +2353,12 @@ public:
#endif
String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
-
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
+ String version_code = itos(p_preset->get("version/code"));
+ String version_name = p_preset->get("version/name");
+ String enabled_abi_string = String("|").join(enabled_abis);
Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
@@ -2058,8 +2370,20 @@ public:
if (clean_build_required) {
cmdline.push_back("clean");
}
- cmdline.push_back("build");
+
+ String build_type = p_debug ? "Debug" : "Release";
+ if (export_format == 1) {
+ String bundle_build_command = vformat("bundle%s", build_type);
+ cmdline.push_back(bundle_build_command);
+ } else if (export_format == 0) {
+ String apk_build_command = vformat("assemble%s", build_type);
+ cmdline.push_back(apk_build_command);
+ }
+
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
+ cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
+ cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.
+ cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs.
cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
@@ -2077,35 +2401,54 @@ public:
EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));
return ERR_CANT_CREATE;
}
- if (p_debug) {
- src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk");
- } else {
- src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk");
+
+ List<String> copy_args;
+ String copy_command;
+ if (export_format == 1) {
+ copy_command = vformat("copyAndRename%sAab", build_type);
+ } else if (export_format == 0) {
+ copy_command = vformat("copyAndRename%sApk", build_type);
}
- if (!FileAccess::exists(src_apk)) {
- EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk);
+ copy_args.push_back(copy_command);
+
+ copy_args.push_back("-p"); // argument to specify the start directory.
+ copy_args.push_back(build_path); // start directory.
+
+ String export_filename = p_path.get_file();
+ String export_path = p_path.get_base_dir();
+
+ copy_args.push_back("-Pexport_path=file:" + export_path);
+ copy_args.push_back("-Pexport_filename=" + export_filename);
+
+ int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args);
+ if (copy_result != 0) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs."));
return ERR_CANT_CREATE;
}
-
- } else {
+ if (_signed) {
+ err = sign_apk(p_preset, p_debug, p_path, ep);
+ if (err != OK) {
+ return err;
+ }
+ }
+ return OK;
+ }
+ // This is the start of the Legacy build system
+ if (p_debug)
+ src_apk = p_preset->get("custom_template/debug");
+ else
+ src_apk = p_preset->get("custom_template/release");
+ src_apk = src_apk.strip_edges();
+ if (src_apk == "") {
if (p_debug) {
- src_apk = p_preset->get("custom_template/debug");
+ src_apk = find_export_template("android_debug.apk");
} else {
- src_apk = p_preset->get("custom_template/release");
+ src_apk = find_export_template("android_release.apk");
}
-
- src_apk = src_apk.strip_edges();
if (src_apk == "") {
- if (p_debug) {
- src_apk = find_export_template("android_debug.apk");
- } else {
- src_apk = find_export_template("android_release.apk");
- }
- if (src_apk == "") {
- EditorNode::add_io_error("Package not found: " + src_apk);
- return ERR_FILE_NOT_FOUND;
- }
+ EditorNode::add_io_error("Package not found: " + src_apk);
+ return ERR_FILE_NOT_FOUND;
}
}
@@ -2142,50 +2485,13 @@ public:
zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
- bool _signed = p_preset->get("package/signed");
String cmdline = p_preset->get("command_line/extra_args");
String version_name = p_preset->get("version/name");
String package_name = p_preset->get("package/unique_name");
- bool apk_expansion = p_preset->get("apk_expansion/enable");
String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
- 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");
-
- Vector<String> enabled_abis = get_enabled_abis(p_preset);
-
- String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
-
- // Prepare images to be resized for the icons. If some image ends up being uninitialized, the default image from the export template will be used.
- Ref<Image> launcher_icon_image;
- Ref<Image> launcher_adaptive_icon_foreground_image;
- Ref<Image> launcher_adaptive_icon_background_image;
-
- launcher_icon_image.instance();
- launcher_adaptive_icon_foreground_image.instance();
- launcher_adaptive_icon_background_image.instance();
-
- // Regular icon: user selection -> project icon -> default.
- String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, launcher_icon_image) != OK) {
- ImageLoader::load_image(project_icon_path, launcher_icon_image);
- }
-
- // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, launcher_adaptive_icon_foreground_image) != OK) {
- launcher_adaptive_icon_foreground_image = launcher_icon_image;
- }
-
- // Adaptive background: user selection -> default.
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
- if (!path.empty()) {
- ImageLoader::load_image(path, launcher_adaptive_icon_background_image);
- }
-
Vector<String> invalid_abis(enabled_abis);
while (ret == UNZ_OK) {
//get filename
@@ -2206,26 +2512,38 @@ public:
unzCloseCurrentFile(pkg);
//write
-
if (file == "AndroidManifest.xml") {
- _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG));
+ _fix_manifest(p_preset, data, p_give_internet);
}
-
if (file == "resources.arsc") {
- if (!use_custom_build) {
- _fix_resources(p_preset, data);
- }
+ _fix_resources(p_preset, data);
+ }
+
+ // Process the splash image
+ if (file == SPLASH_IMAGE_EXPORT_PATH && splash_image.is_valid() && !splash_image->empty()) {
+ _load_image_data(splash_image, data);
+ }
+
+ // Process the splash bg color image
+ if (file == SPLASH_BG_COLOR_PATH && splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) {
+ _load_image_data(splash_bg_color_image, data);
}
for (int i = 0; i < icon_densities_count; ++i) {
- if (launcher_icon_image.is_valid() && !launcher_icon_image->empty()) {
- _process_launcher_icons(file, launcher_icon_image, launcher_icons[i], data);
+ if (main_image.is_valid() && !main_image->empty()) {
+ if (file == launcher_icons[i].export_path) {
+ _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data);
+ }
}
- if (launcher_adaptive_icon_foreground_image.is_valid() && !launcher_adaptive_icon_foreground_image->empty()) {
- _process_launcher_icons(file, launcher_adaptive_icon_foreground_image, launcher_adaptive_icon_foregrounds[i], data);
+ if (foreground.is_valid() && !foreground->empty()) {
+ if (file == launcher_adaptive_icon_foregrounds[i].export_path) {
+ _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ }
}
- if (launcher_adaptive_icon_background_image.is_valid() && !launcher_adaptive_icon_background_image->empty()) {
- _process_launcher_icons(file, launcher_adaptive_icon_background_image, launcher_adaptive_icon_backgrounds[i], data);
+ if (background.is_valid() && !background->empty()) {
+ if (file == launcher_adaptive_icon_backgrounds[i].export_path) {
+ _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ }
}
}
@@ -2283,18 +2601,26 @@ public:
if (ep.step("Adding files...", 1)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
- Error err = OK;
+ err = OK;
if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so);
- } else if (!apk_expansion) {
- APKExportData ed;
- ed.ep = &ep;
- ed.apk = unaligned_apk;
- err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
+ } else {
+ if (apk_expansion) {
+ err = save_apk_expansion_file(p_preset, p_path);
+ if (err != OK) {
+ EditorNode::add_io_error("Could not write expansion package file!");
+ return err;
+ }
+ } else {
+ APKExportData ed;
+ ed.ep = &ep;
+ ed.apk = unaligned_apk;
+ err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
+ }
}
if (err != OK) {
@@ -2303,10 +2629,6 @@ public:
CLEANUP_AND_RETURN(ERR_SKIP);
}
- Vector<uint8_t> command_line_flags;
- // Write command line flags into the command_line_flags variable.
- err = get_command_line_flags(p_preset, p_path, p_flags, command_line_flags);
-
zip_fileinfo zipfi = get_zip_fileinfo();
zipOpenNewFileInZip(unaligned_apk,
"assets/_cl_",
@@ -2328,84 +2650,9 @@ public:
}
if (_signed) {
- String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
- if (!FileAccess::exists(jarsigner)) {
- EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned.");
- CLEANUP_AND_RETURN(OK);
- }
-
- String keystore;
- 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");
-
- if (keystore.empty()) {
- keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
- password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
- user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
- }
-
- if (ep.step("Signing debug APK...", 103)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- } else {
- keystore = release_keystore;
- password = release_password;
- user = release_username;
-
- if (ep.step("Signing release APK...", 103)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
- }
-
- if (!FileAccess::exists(keystore)) {
- EditorNode::add_io_error("Could not find keystore, unable to export.");
- CLEANUP_AND_RETURN(ERR_FILE_CANT_OPEN);
- }
-
- List<String> args;
- args.push_back("-digestalg");
- args.push_back("SHA-256");
- args.push_back("-sigalg");
- args.push_back("SHA256withRSA");
- String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url");
- if (tsa_url != "") {
- args.push_back("-tsa");
- args.push_back(tsa_url);
- }
- args.push_back("-verbose");
- args.push_back("-keystore");
- args.push_back(keystore);
- args.push_back("-storepass");
- args.push_back(password);
- args.push_back(tmp_unaligned_path);
- args.push_back(user);
- int retval;
- OS::get_singleton()->execute(jarsigner, args, true, nullptr, nullptr, &retval);
- if (retval) {
- EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
- CLEANUP_AND_RETURN(ERR_CANT_CREATE);
- }
-
- if (ep.step("Verifying APK...", 104)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- args.clear();
- args.push_back("-verify");
- args.push_back("-keystore");
- args.push_back(keystore);
- args.push_back(tmp_unaligned_path);
- args.push_back("-verbose");
-
- OS::get_singleton()->execute(jarsigner, args, true, nullptr, nullptr, &retval);
- if (retval) {
- EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8.");
- CLEANUP_AND_RETURN(ERR_CANT_CREATE);
+ err = sign_apk(p_preset, p_debug, tmp_unaligned_path, ep);
+ if (err != OK) {
+ CLEANUP_AND_RETURN(err);
}
}
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index 622860c307..95f870bc35 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -99,7 +99,7 @@ Error store_string_at_path(const String &p_path, const String &p_data) {
// It is used by the export_project_files method to save all the asset files into the gradle project.
// It's functionality mirrors that of the method save_apk_file.
// This method will be called ONLY when custom build is enabled.
-Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
+Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
String dst_path = p_path.replace_first("res://", "res://android/build/assets/");
Error err = store_file_at_path(dst_path, p_data);
return err;
@@ -142,4 +142,104 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
return OK;
}
+String bool_to_string(bool v) {
+ return v ? "true" : "false";
+}
+
+String _get_gles_tag() {
+ bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" &&
+ !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2");
+ return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : "";
+}
+
+String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\"";
+ String sizes[] = { "small", "normal", "large", "xlarge" };
+ size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]);
+ for (size_t i = 0; i < num_sizes; i++) {
+ String feature_name = vformat("screen/support_%s", sizes[i]);
+ String feature_support = bool_to_string(p_preset->get(feature_name));
+ String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support);
+ manifest_screen_sizes += xml_entry;
+ }
+ manifest_screen_sizes += " />\n";
+ return manifest_screen_sizes;
+}
+
+String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_xr_features;
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ if (uses_xr) {
+ int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof
+ if (dof_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n";
+ } else if (dof_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
+ }
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
+ } else if (hand_tracking_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n";
+ }
+ }
+ return manifest_xr_features;
+}
+
+String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
+ String package_name = p_preset->get("package/unique_name");
+ String manifest_instrumentation_text = vformat(
+ " <instrumentation\n"
+ " tools:node=\"replace\"\n"
+ " android:name=\".GodotInstrumentation\"\n"
+ " android:icon=\"@mipmap/icon\"\n"
+ " android:label=\"@string/godot_project_name_string\"\n"
+ " android:targetPackage=\"%s\" />\n",
+ package_name);
+ return manifest_instrumentation_text;
+}
+
+String _get_plugins_tag(const String &plugins_names) {
+ if (!plugins_names.empty()) {
+ return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names);
+ } else {
+ return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n";
+ }
+}
+
+String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape";
+ String manifest_activity_text = vformat(
+ " <activity android:name=\"com.godot.game.GodotApp\" "
+ "tools:replace=\"android:screenOrientation\" "
+ "android:screenOrientation=\"%s\">\n",
+ orientation);
+ if (uses_xr) {
+ String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness"));
+ manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness);
+ } else {
+ manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
+ }
+ manifest_activity_text += " </activity>\n";
+ return manifest_activity_text;
+}
+
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String manifest_application_text =
+ " <application android:label=\"@string/godot_project_name_string\"\n"
+ " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n"
+ " android:icon=\"@mipmap/icon\">)\n\n"
+ " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n";
+
+ manifest_application_text += _get_plugins_tag(plugins_names);
+ if (uses_xr) {
+ manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n";
+ }
+ manifest_application_text += _get_activity_tag(p_preset);
+ manifest_application_text += " </application>\n";
+ return manifest_application_text;
+}
+
#endif //GODOT_GRADLE_EXPORT_UTIL_H
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 48c09552c1..e94681659c 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -38,7 +38,7 @@
<activity
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
- android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+ android:theme="@style/GodotAppSplashTheme"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 19202d2310..ceacfec9e1 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -80,8 +80,15 @@ android {
ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
+ ndk {
+ String[] export_abi_list = getExportEnabledABIs()
+ abiFilters export_abi_list
+ }
+
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
+ versionCode getExportVersionCode()
+ versionName getExportVersionName()
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
}
@@ -123,3 +130,27 @@ android {
}
}
}
+
+task copyAndRenameDebugApk(type: Copy) {
+ from "$buildDir/outputs/apk/debug/android_debug.apk"
+ into getExportPath()
+ rename "android_debug.apk", getExportFilename()
+}
+
+task copyAndRenameReleaseApk(type: Copy) {
+ from "$buildDir/outputs/apk/release/android_release.apk"
+ into getExportPath()
+ rename "android_release.apk", getExportFilename()
+}
+
+task copyAndRenameDebugAab(type: Copy) {
+ from "$buildDir/outputs/bundle/debug/build-debug.aab"
+ into getExportPath()
+ rename "build-debug.aab", getExportFilename()
+}
+
+task copyAndRenameReleaseAab(type: Copy) {
+ from "$buildDir/outputs/bundle/release/build-release.aab"
+ into getExportPath()
+ rename "build-release.aab", getExportFilename()
+}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index acfdef531e..d1176e6196 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -28,8 +28,55 @@ ext.getExportPackageName = { ->
return appId
}
+ext.getExportVersionCode = { ->
+ String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : ""
+ if (versionCode == null || versionCode.isEmpty()) {
+ versionCode = "1"
+ }
+ return Integer.parseInt(versionCode)
+}
+
+ext.getExportVersionName = { ->
+ String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : ""
+ if (versionName == null || versionName.isEmpty()) {
+ versionName = "1.0"
+ }
+ return versionName
+}
+
final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
+// get the list of ABIs the project should be exported to
+ext.getExportEnabledABIs = { ->
+ String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : "";
+ if (enabledABIs == null || enabledABIs.isEmpty()) {
+ enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
+ }
+ Set<String> exportAbiFilter = [];
+ for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ if (!abi_name.trim().isEmpty()){
+ exportAbiFilter.add(abi_name);
+ }
+ }
+ return exportAbiFilter;
+}
+
+ext.getExportPath = {
+ String exportPath = project.hasProperty("export_path") ? project.property("export_path") : ""
+ if (exportPath == null || exportPath.isEmpty()) {
+ exportPath = "."
+ }
+ return exportPath
+}
+
+ext.getExportFilename = {
+ String exportFilename = project.hasProperty("export_filename") ? project.property("export_filename") : ""
+ if (exportFilename == null || exportFilename.isEmpty()) {
+ exportFilename = "godot_android"
+ }
+ return exportFilename
+}
+
/**
* Parse the project properties for the 'plugins_maven_repos' property and return the list
* of maven repos.
diff --git a/platform/android/java/app/res/drawable/splash.png b/platform/android/java/app/res/drawable/splash.png
new file mode 100644
index 0000000000..7bddd4325a
--- /dev/null
+++ b/platform/android/java/app/res/drawable/splash.png
Binary files differ
diff --git a/platform/android/java/app/res/drawable/splash_bg_color.png b/platform/android/java/app/res/drawable/splash_bg_color.png
new file mode 100644
index 0000000000..004b6fd508
--- /dev/null
+++ b/platform/android/java/app/res/drawable/splash_bg_color.png
Binary files differ
diff --git a/platform/android/java/app/res/drawable/splash_drawable.xml b/platform/android/java/app/res/drawable/splash_drawable.xml
new file mode 100644
index 0000000000..2794a40817
--- /dev/null
+++ b/platform/android/java/app/res/drawable/splash_drawable.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:drawable="@drawable/splash_bg_color" />
+
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/splash" />
+ </item>
+
+</layer-list>
diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml
new file mode 100644
index 0000000000..26912538d3
--- /dev/null
+++ b/platform/android/java/app/res/values/themes.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <style name="GodotAppMainTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen"/>
+
+ <style name="GodotAppSplashTheme" parent="@style/GodotAppMainTheme">
+ <item name="android:windowBackground">@drawable/splash_drawable</item>
+ </style>
+</resources>
diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java
index 1af5950cbe..51df70969e 100644
--- a/platform/android/java/app/src/com/godot/game/GodotApp.java
+++ b/platform/android/java/app/src/com/godot/game/GodotApp.java
@@ -32,9 +32,16 @@ package com.godot.game;
import org.godotengine.godot.FullScreenGodotApp;
+import android.os.Bundle;
+
/**
* Template activity for Godot Android custom builds.
* Feel free to extend and modify this class for your custom logic.
*/
public class GodotApp extends FullScreenGodotApp {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(R.style.GodotAppMainTheme);
+ super.onCreate(savedInstanceState);
+ }
}
diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml
new file mode 100644
index 0000000000..9034dbbcc1
--- /dev/null
+++ b/platform/android/java/lib/res/values/dimens.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="text_edit_height">48dp</dimen>
+</resources>
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 138c2de94c..5aa48d87da 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -34,6 +34,8 @@ import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
/**
@@ -43,13 +45,18 @@ import androidx.fragment.app.FragmentActivity;
* within an Android app.
*/
public abstract class FullScreenGodotApp extends FragmentActivity {
- protected Godot godotFragment;
+ @Nullable
+ private Godot godotFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.godot_app_layout);
- godotFragment = new Godot();
+ godotFragment = initGodotInstance();
+ if (godotFragment == null) {
+ throw new IllegalStateException("Godot instance must be non-null.");
+ }
+
getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
}
@@ -76,4 +83,17 @@ public abstract class FullScreenGodotApp extends FragmentActivity {
}
return super.onKeyMultiple(inKeyCode, repeatCount, event);
}
+
+ /**
+ * Used to initialize the Godot fragment instance in {@link FullScreenGodotApp#onCreate(Bundle)}.
+ */
+ @NonNull
+ protected Godot initGodotInstance() {
+ return new Godot();
+ }
+
+ @Nullable
+ protected final Godot getGodotFragment() {
+ return godotFragment;
+ }
}
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 1ae400abb5..524f32bf5e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -151,8 +151,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
private void setButtonPausedState(boolean paused) {
mStatePaused = paused;
- int stringResourceID = paused ? R.string.text_button_resume :
- R.string.text_button_pause;
+ int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause;
mPauseButton.setText(stringResourceID);
}
@@ -221,7 +220,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
// GodotEditText layout
GodotEditText editText = new GodotEditText(activity);
- editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ (int)getResources().getDimension(R.dimen.text_edit_height)));
// ...add to FrameLayout
containerLayout.addView(editText);
@@ -467,7 +467,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final Activity activity = getActivity();
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 4dd228e53b..c2f3c88416 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -461,9 +461,9 @@ public class GodotIO {
return (int)(metrics.density * 160f);
}
- public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+ public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null)
- edit.showKeyboard(p_existing_text, p_max_input_length, p_cursor_start, p_cursor_end);
+ edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index c0defd008e..c95339c583 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -36,6 +36,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.InputFilter;
+import android.text.InputType;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
@@ -58,7 +59,8 @@ public class GodotEditText extends EditText {
private GodotTextInputWrapper mInputWrapper;
private EditHandler sHandler = new EditHandler(this);
private String mOriginText;
- private int mMaxInputLength;
+ private int mMaxInputLength = Integer.MAX_VALUE;
+ private boolean mMultiline = false;
private static class EditHandler extends Handler {
private final WeakReference<GodotEditText> mEdit;
@@ -95,7 +97,11 @@ public class GodotEditText extends EditText {
protected void initView() {
setPadding(0, 0, 0, 0);
- setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+ setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE);
+ }
+
+ public boolean isMultiline() {
+ return mMultiline;
}
private void handleMessage(final Message msg) {
@@ -115,6 +121,12 @@ public class GodotEditText extends EditText {
edit.mInputWrapper.setSelection(false);
}
+ int inputType = InputType.TYPE_CLASS_TEXT;
+ if (edit.isMultiline()) {
+ inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
+ edit.setInputType(inputType);
+
edit.mInputWrapper.setOriginText(text);
edit.addTextChangedListener(edit.mInputWrapper);
final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -189,7 +201,7 @@ public class GodotEditText extends EditText {
// ===========================================================
// Methods
// ===========================================================
- public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+ public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;
if (p_cursor_start == -1) { // cursor position not given
this.mOriginText = p_existing_text;
@@ -202,6 +214,8 @@ public class GodotEditText extends EditText {
this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end);
}
+ this.mMultiline = p_multiline;
+
final Message msg = new Message();
msg.what = HANDLER_OPEN_IME_KEYBOARD;
msg.obj = this;
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 9c7cf9f341..4dd1054738 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
@@ -123,7 +123,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
public void run() {
for (int i = 0; i < count; ++i) {
int key = newChars[i];
- if (key == '\n') {
+ if ((key == '\n') && !mEdit.isMultiline()) {
// Return keys are handled through action events
continue;
}
@@ -151,7 +151,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
});
}
- if (pActionID == EditorInfo.IME_NULL) {
+ if (pActionID == EditorInfo.IME_ACTION_DONE) {
// Enter key has been pressed
GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true);
GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
index aeb4628d5d..7fa8e3b4e5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
@@ -71,7 +71,7 @@ internal class VkRenderer {
*/
fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) {
GodotLib.resize(surface, width, height)
-
+
for (plugin in pluginRegistry.getAllPlugins()) {
plugin.onVkSurfaceChanged(surface, width, height)
}
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 0a42adeaf2..4ccbc6b97e 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -53,7 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
- _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;III)V");
+ _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
@@ -132,11 +132,11 @@ bool GodotIOJavaWrapper::has_vk() {
return (_show_keyboard != 0) && (_hide_keyboard != 0);
}
-void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) {
JNIEnv *env = ThreadAndroid::get_env();
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
- env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length, p_cursor_start, p_cursor_end);
+ env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
}
}
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 1742021379..6465ded985 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -70,7 +70,7 @@ public:
int get_screen_dpi();
String get_unique_id();
bool has_vk();
- void show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end);
+ void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);
void hide_vk();
int get_vk_height();
void set_vk_height(int p_height);
diff --git a/platform/android/logo.png b/platform/android/logo.png
index df445f6a9c..f44d360a25 100644
--- a/platform/android/logo.png
+++ b/platform/android/logo.png
Binary files differ