diff options
Diffstat (limited to 'platform/android/export/export_plugin.cpp')
-rw-r--r-- | platform/android/export/export_plugin.cpp | 457 |
1 files changed, 351 insertions, 106 deletions
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 661f7cbc80..19e215bfaf 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -45,9 +45,10 @@ #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" -#include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/export/export_template_manager.h" #include "editor/import/resource_importer_texture_settings.h" +#include "editor/themes/editor_scale.h" #include "main/splash.gen.h" #include "scene/resources/image_texture.h" @@ -208,12 +209,14 @@ static const char *android_perms[] = { nullptr }; +static const char *MISMATCHED_VERSIONS_MESSAGE = "Android build version mismatch:\n| Template installed: %s\n| Requested version: %s\nPlease reinstall Android build template from 'Project' menu."; + static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi/splash.png"; static const char *LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi-v4/splash.png"; static const char *SPLASH_BG_COLOR_PATH = "res/drawable-nodpi/splash_bg_color.png"; static const char *LEGACY_BUILD_SPLASH_BG_COLOR_PATH = "res/drawable-nodpi-v4/splash_bg_color.png"; -static const char *SPLASH_CONFIG_PATH = "res://android/build/res/drawable/splash_drawable.xml"; -static const char *GDEXTENSION_LIBS_PATH = "res://android/build/libs/gdextensionlibs.json"; +static const char *SPLASH_CONFIG_PATH = "res/drawable/splash_drawable.xml"; +static const char *GDEXTENSION_LIBS_PATH = "libs/gdextensionlibs.json"; static const int icon_densities_count = 6; static const char *launcher_icon_option = PNAME("launcher_icons/main_192x192"); @@ -250,12 +253,12 @@ static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_coun static const int EXPORT_FORMAT_APK = 0; static const int EXPORT_FORMAT_AAB = 1; -static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets"; -static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets"; +static const char *APK_ASSETS_DIRECTORY = "assets"; +static const char *AAB_ASSETS_DIRECTORY = "assetPacks/installTime/src/main/assets"; 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 = 33; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' +static const int DEFAULT_TARGET_SDK_VERSION = 34; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' #ifndef ANDROID_ENABLED void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { @@ -291,7 +294,7 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { // Check for devices updates String adb = get_adb_path(); - if (FileAccess::exists(adb)) { + if (ea->has_runnable_preset.is_set() && FileAccess::exists(adb)) { String devices; List<String> args; args.push_back("devices"); @@ -376,14 +379,15 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { } else if (p.begins_with("ro.build.version.sdk=")) { d.api_level = p.get_slice("=", 1).to_int(); } else if (p.begins_with("ro.product.cpu.abi=")) { - d.description += "CPU: " + p.get_slice("=", 1).strip_edges() + "\n"; + d.architecture = p.get_slice("=", 1).strip_edges(); + d.description += "CPU: " + d.architecture + "\n"; } else if (p.begins_with("ro.product.manufacturer=")) { d.description += "Manufacturer: " + p.get_slice("=", 1).strip_edges() + "\n"; } else if (p.begins_with("ro.board.platform=")) { d.description += "Chipset: " + p.get_slice("=", 1).strip_edges() + "\n"; } else if (p.begins_with("ro.opengles.version=")) { uint32_t opengl = p.get_slice("=", 1).to_int(); - d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl)&0xFF) + "\n"; + d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl) & 0xFF) + "\n"; } } @@ -412,7 +416,7 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { } } - if (EDITOR_GET("export/android/shutdown_adb_on_exit")) { + if (ea->has_runnable_preset.is_set() && EDITOR_GET("export/android/shutdown_adb_on_exit")) { String adb = get_adb_path(); if (!FileAccess::exists(adb)) { return; //adb not configured @@ -423,6 +427,25 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { OS::get_singleton()->execute(adb, args); } } + +void EditorExportPlatformAndroid::_update_preset_status() { + const int preset_count = EditorExport::get_singleton()->get_export_preset_count(); + bool has_runnable = false; + + for (int i = 0; i < preset_count; i++) { + const Ref<EditorExportPreset> &preset = EditorExport::get_singleton()->get_export_preset(i); + if (preset->get_platform() == this && preset->is_runnable()) { + has_runnable = true; + break; + } + } + + if (has_runnable) { + has_runnable_preset.set(); + } else { + has_runnable_preset.clear(); + } +} #endif String EditorExportPlatformAndroid::get_project_name(const String &p_name) const { @@ -460,7 +483,7 @@ String EditorExportPlatformAndroid::get_valid_basename() const { if (is_digit(c) && first) { continue; } - if (is_ascii_alphanumeric_char(c)) { + if (is_ascii_identifier_char(c)) { name += String::chr(c); first = false; } @@ -474,7 +497,8 @@ String EditorExportPlatformAndroid::get_valid_basename() const { } String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const { - return p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY; + String gradle_build_directory = ExportTemplateManager::get_android_build_directory(p_preset); + return gradle_build_directory.path_join(p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY); } bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, String *r_error) const { @@ -537,13 +561,6 @@ bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, return false; } - if (p_package.find("$genname") >= 0 && !is_project_name_valid()) { - if (r_error) { - *r_error = TTR("The project name does not meet the requirement for the package name format. Please explicitly specify the package name."); - } - return false; - } - return true; } @@ -781,11 +798,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared } if (abi_index != -1) { exported = true; - String base = "res://android/build/libs"; String type = export_data->debug ? "debug" : "release"; String abi = abis[abi_index].abi; String filename = p_so.path.get_file(); - String dst_path = base.path_join(type).path_join(abi).path_join(filename); + String dst_path = export_data->libs_directory.path_join(type).path_join(abi).path_join(filename); Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_so.path); print_verbose("Copying .so file from " + p_so.path + " to " + dst_path); Error err = store_file_at_path(dst_path, data); @@ -812,6 +828,16 @@ bool EditorExportPlatformAndroid::_uses_vulkan() { return uses_vulkan; } +void EditorExportPlatformAndroid::_notification(int p_what) { +#ifndef ANDROID_ENABLED + if (p_what == NOTIFICATION_POSTINITIALIZE) { + if (EditorExport::get_singleton()) { + EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status)); + } + } +#endif +} + void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { const char **aperms = android_perms; while (*aperms) { @@ -874,7 +900,7 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres manifest_text += _get_application_tag(Ref<EditorExportPlatform>(this), p_preset, _has_read_write_storage_permission(perms), p_debug); manifest_text += "</manifest>\n"; - String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); + String manifest_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join(vformat("src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"))); print_verbose("Storing manifest into " + manifest_path + ": " + "\n" + manifest_text); store_string_at_path(manifest_path, manifest_text); @@ -1118,7 +1144,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } for (int i = 0; i < feature_names.size(); i++) { - String feature_name = feature_names[i]; + const String &feature_name = feature_names[i]; bool feature_required = feature_required_list[i]; int feature_version = feature_versions[i]; bool has_version_attribute = feature_version != -1; @@ -1542,18 +1568,32 @@ void EditorExportPlatformAndroid::_process_launcher_icons(const String &p_file_n String EditorExportPlatformAndroid::load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) { bool scale_splash = GLOBAL_GET("application/boot_splash/fullsize"); bool apply_filter = GLOBAL_GET("application/boot_splash/use_filter"); + bool show_splash_image = GLOBAL_GET("application/boot_splash/show_image"); String project_splash_path = GLOBAL_GET("application/boot_splash/image"); - if (!project_splash_path.is_empty()) { - splash_image.instantiate(); - print_verbose("Loading splash image: " + project_splash_path); - const Error err = ImageLoader::load_image(project_splash_path, splash_image); - if (err) { - if (OS::get_singleton()->is_stdout_verbose()) { - print_error("- unable to load splash image from " + project_splash_path + " (" + itos(err) + ")"); + // Setup the splash bg color. + bool bg_color_valid = false; + 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; + } + + if (show_splash_image) { + if (!project_splash_path.is_empty()) { + splash_image.instantiate(); + print_verbose("Loading splash image: " + project_splash_path); + const Error err = ImageLoader::load_image(project_splash_path, splash_image); + if (err) { + if (OS::get_singleton()->is_stdout_verbose()) { + print_error("- unable to load splash image from " + project_splash_path + " (" + itos(err) + ")"); + } + splash_image.unref(); } - splash_image.unref(); } + } else { + splash_image.instantiate(); + splash_image->initialize_data(1, 1, false, Image::FORMAT_RGBA8); + splash_image->set_pixel(0, 0, bg_color); } if (splash_image.is_null()) { @@ -1577,13 +1617,6 @@ String EditorExportPlatformAndroid::load_splash_refs(Ref<Image> &splash_image, R splash_image->resize(width, height); } - // 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; - } - print_verbose("Creating splash background color image."); splash_bg_color_image.instantiate(); splash_bg_color_image->initialize_data(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format()); @@ -1605,7 +1638,11 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> & print_verbose("Loading regular icon from " + path); if (path.is_empty() || ImageLoader::load_image(path, icon) != OK) { print_verbose("- falling back to project icon: " + project_icon_path); - ImageLoader::load_image(project_icon_path, icon); + if (!project_icon_path.is_empty()) { + ImageLoader::load_image(project_icon_path, icon); + } else { + ERR_PRINT("No project icon specified. Please specify one in the Project Settings under Application -> Config -> Icon"); + } } // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). @@ -1624,15 +1661,6 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> & } } -void EditorExportPlatformAndroid::store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) { - store_image(launcher_icon.export_path, data); -} - -void EditorExportPlatformAndroid::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 EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, const String &processed_splash_config_xml, const Ref<Image> &splash_image, @@ -1640,26 +1668,30 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor const Ref<Image> &main_image, const Ref<Image> &foreground, const Ref<Image> &background) { + String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + // Store the splash configuration if (!processed_splash_config_xml.is_empty()) { print_verbose("Storing processed splash configuration: " + String("\n") + processed_splash_config_xml); - store_string_at_path(SPLASH_CONFIG_PATH, processed_splash_config_xml); + store_string_at_path(gradle_build_dir.path_join(SPLASH_CONFIG_PATH), processed_splash_config_xml); } // Store the splash image if (splash_image.is_valid() && !splash_image->is_empty()) { - print_verbose("Storing splash image in " + String(SPLASH_IMAGE_EXPORT_PATH)); + String splash_export_path = gradle_build_dir.path_join(SPLASH_IMAGE_EXPORT_PATH); + print_verbose("Storing splash image in " + splash_export_path); Vector<uint8_t> data; _load_image_data(splash_image, data); - store_image(SPLASH_IMAGE_EXPORT_PATH, data); + store_file_at_path(splash_export_path, data); } // Store the splash bg color image if (splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { - print_verbose("Storing splash background image in " + String(SPLASH_BG_COLOR_PATH)); + String splash_bg_color_path = gradle_build_dir.path_join(SPLASH_BG_COLOR_PATH); + print_verbose("Storing splash background image in " + splash_bg_color_path); Vector<uint8_t> data; _load_image_data(splash_bg_color_image, data); - store_image(SPLASH_BG_COLOR_PATH, data); + store_file_at_path(splash_bg_color_path, data); } // Prepare images to be resized for the icons. If some image ends up being uninitialized, @@ -1670,7 +1702,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor print_verbose("Processing launcher icon for dimension " + itos(launcher_icons[i].dimensions) + " into " + launcher_icons[i].export_path); 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); + store_file_at_path(gradle_build_dir.path_join(launcher_icons[i].export_path), data); } if (foreground.is_valid() && !foreground->is_empty()) { @@ -1678,7 +1710,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor 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); + store_file_at_path(gradle_build_dir.path_join(launcher_adaptive_icon_foregrounds[i].export_path), data); } if (background.is_valid() && !background->is_empty()) { @@ -1686,7 +1718,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor 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); + store_file_at_path(gradle_build_dir.path_join(launcher_adaptive_icon_backgrounds[i].export_path), data); } } } @@ -1710,7 +1742,6 @@ void EditorExportPlatformAndroid::get_preset_features(const Ref<EditorExportPres Vector<ABI> abis = get_enabled_abis(p_preset); for (int i = 0; i < abis.size(); ++i) { r_features->push_back(abis[i].arch); - r_features->push_back(abis[i].abi); } } @@ -1741,6 +1772,11 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) { return TTR("OpenXR requires \"Use Gradle Build\" to be enabled"); } + } else if (p_name == "gradle_build/compress_native_libraries") { + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (bool(p_preset->get("gradle_build/compress_native_libraries")) && !gradle_build_enabled) { + return TTR("\"Compress Native Libraries\" is only valid when \"Use Gradle Build\" is enabled."); + } } 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) { @@ -1795,7 +1831,10 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio 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, false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, true, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/gradle_build_directory", PROPERTY_HINT_PLACEHOLDER_TEXT, "res://android"), "", false, false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/android_source_template", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/compress_native_libraries"), 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. @@ -1824,10 +1863,10 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio 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/debug_password", PROPERTY_HINT_PASSWORD, "", 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::STRING, "keystore/release_password", PROPERTY_HINT_PASSWORD, "", 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", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); @@ -1873,6 +1912,35 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio } } +bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_preset == nullptr) { + return true; + } + + bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); + if (p_option == "graphics/opengl_debug" || + p_option == "command_line/extra_args" || + p_option == "permissions/custom_permissions" || + p_option == "gradle_build/compress_native_libraries" || + p_option == "package/retain_data_on_uninstall" || + p_option == "package/exclude_from_recents" || + p_option == "package/show_in_app_library" || + p_option == "package/show_as_launcher_app" || + p_option == "apk_expansion/enable" || + p_option == "apk_expansion/SALT" || + p_option == "apk_expansion/public_key") { + return advanced_options_enabled; + } + if (p_option == "gradle_build/gradle_build_directory" || p_option == "gradle_build/android_source_template") { + return advanced_options_enabled && bool(p_preset->get("gradle_build/use_gradle_build")); + } + if (p_option == "custom_template/debug" || p_option == "custom_template/release") { + // The APK templates are ignored if Gradle build is enabled. + return advanced_options_enabled && !bool(p_preset->get("gradle_build/use_gradle_build")); + } + return true; +} + String EditorExportPlatformAndroid::get_name() const { return "Android"; } @@ -1933,6 +2001,12 @@ String EditorExportPlatformAndroid::get_option_tooltip(int p_index) const { return s; } +String EditorExportPlatformAndroid::get_device_architecture(int p_index) const { + ERR_FAIL_INDEX_V(p_index, devices.size(), ""); + MutexLock lock(device_lock); + return devices[p_index].architecture; +} + Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); @@ -2112,8 +2186,17 @@ Ref<Texture2D> EditorExportPlatformAndroid::get_run_icon() const { return run_icon; } +String EditorExportPlatformAndroid::get_java_path() { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + return java_sdk_path.path_join("bin/java" + exe_ext); +} + String EditorExportPlatformAndroid::get_adb_path() { - String exe_ext = ""; + String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { exe_ext = ".exe"; } @@ -2125,13 +2208,13 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ if (p_target_sdk == -1) { p_target_sdk = DEFAULT_TARGET_SDK_VERSION; } - String exe_ext = ""; + String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { exe_ext = ".bat"; } String apksigner_command_name = "apksigner" + exe_ext; String sdk_path = EDITOR_GET("export/android/android_sdk_path"); - String apksigner_path = ""; + String apksigner_path; Error errn; String build_tools_dir = sdk_path.path_join("build-tools"); @@ -2230,6 +2313,54 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ return apksigner_path; } +static bool has_valid_keystore_credentials(String &r_error_str, const String &p_keystore, const String &p_username, const String &p_password, const String &p_type) { + String output; + List<String> args; + args.push_back("-list"); + args.push_back("-keystore"); + args.push_back(p_keystore); + args.push_back("-storepass"); + args.push_back(p_password); + args.push_back("-alias"); + args.push_back(p_username); + Error error = OS::get_singleton()->execute("keytool", args, &output, nullptr, true); + String keytool_error = "keytool error:"; + bool valid = output.substr(0, keytool_error.length()) != keytool_error; + + if (error != OK) { + r_error_str = TTR("Error: There was a problem validating the keystore username and password"); + return false; + } + if (!valid) { + r_error_str = TTR(p_type + " Username and/or Password is invalid for the given " + p_type + " Keystore"); + return false; + } + r_error_str = ""; + return true; +} + +bool EditorExportPlatformAndroid::has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error) { + 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); + 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); + + bool valid = true; + if (!dk.is_empty() && !dk_user.is_empty() && !dk_password.is_empty()) { + String err = ""; + valid = has_valid_keystore_credentials(err, dk, dk_user, dk_password, "Debug"); + r_error += err; + } + if (!rk.is_empty() && !rk_user.is_empty() && !rk_password.is_empty()) { + String err = ""; + valid = has_valid_keystore_credentials(err, rk, rk_user, rk_password, "Release"); + r_error += err; + } + return valid; +} + 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; @@ -2238,6 +2369,19 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito #ifdef MODULE_MONO_ENABLED // Android export is still a work in progress, keep a message as a warning. err += TTR("Exporting to Android when using C#/.NET is experimental.") + "\n"; + + bool unsupported_arch = false; + Vector<ABI> enabled_abis = get_enabled_abis(p_preset); + for (ABI abi : enabled_abis) { + if (abi.arch != "arm64" && abi.arch != "x86_64") { + err += vformat(TTR("Android architecture %s not supported in C# projects."), abi.arch) + "\n"; + unsupported_arch = true; + } + } + if (unsupported_arch) { + r_error = err; + return false; + } #endif // Look for export templates (first official, and if defined custom templates). @@ -2272,9 +2416,22 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito err += template_err; } } else { - bool installed_android_build_template = FileAccess::exists("res://android/build/build.gradle"); + // Validate the custom gradle android source template. + bool android_source_template_valid = false; + const String android_source_template = p_preset->get("gradle_build/android_source_template"); + if (!android_source_template.is_empty()) { + android_source_template_valid = FileAccess::exists(android_source_template); + if (!android_source_template_valid) { + err += TTR("Custom Android source template not found.") + "\n"; + } + } + + // Validate the installed build template. + bool installed_android_build_template = FileAccess::exists(ExportTemplateManager::get_android_build_directory(p_preset).path_join("build.gradle")); if (!installed_android_build_template) { - r_missing_templates = !exists_export_template("android_source.zip", &err); + if (!android_source_template_valid) { + r_missing_templates = !exists_export_template("android_source.zip", &err); + } err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n"; } else { r_missing_templates = false; @@ -2317,6 +2474,32 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito err += TTR("Release keystore incorrectly configured in the export preset.") + "\n"; } + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + if (java_sdk_path.is_empty()) { + err += TTR("A valid Java SDK path is required in Editor Settings.") + "\n"; + valid = false; + } else { + // Validate the given path by checking that `java` is present under the `bin` directory. + Error errn; + // Check for the bin directory. + Ref<DirAccess> da = DirAccess::open(java_sdk_path.path_join("bin"), &errn); + if (errn != OK) { + err += TTR("Invalid Java SDK path in Editor Settings."); + err += TTR("Missing 'bin' directory!"); + err += "\n"; + valid = false; + } else { + // Check for the `java` command. + String java_path = get_java_path(); + if (!FileAccess::exists(java_path)) { + err += TTR("Unable to find 'java' command using the Java SDK path."); + err += TTR("Please check the Java SDK directory specified in Editor Settings."); + err += "\n"; + valid = false; + } + } + } + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); if (sdk_path.is_empty()) { err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n"; @@ -2393,6 +2576,19 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit valid = false; } + if (p_preset->get("gradle_build/use_gradle_build")) { + String build_version_path = ExportTemplateManager::get_android_build_directory(p_preset).get_base_dir().path_join(".build_version"); + Ref<FileAccess> f = FileAccess::open(build_version_path, FileAccess::READ); + if (f.is_valid()) { + String current_version = ExportTemplateManager::get_android_template_identifier(p_preset); + String installed_version = f->get_line().strip_edges(); + if (current_version != installed_version) { + err += vformat(TTR(MISMATCHED_VERSIONS_MESSAGE), installed_version, current_version); + err += "\n"; + } + } + } + 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. @@ -2427,6 +2623,13 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit err += "\n"; } + String package_name = p_preset->get("package/unique_name"); + if (package_name.find("$genname") >= 0 && !is_project_name_valid()) { + // Warning only, so don't override `valid`. + err += vformat(TTR("The project name does not meet the requirement for the package name format and will be updated to \"%s\". Please explicitly specify the package name if needed."), get_valid_basename()); + err += "\n"; + } + r_error = err; return valid; } @@ -2448,7 +2651,7 @@ String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorE Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { String fullpath = get_apk_expansion_fullpath(p_preset, p_path); - Error err = save_pack(p_preset, p_debug, fullpath); + Error err = save_pack(false, p_preset, p_debug, fullpath); return err; } @@ -2628,30 +2831,37 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre return OK; } -void EditorExportPlatformAndroid::_clear_assets_directory() { +void EditorExportPlatformAndroid::_clear_assets_directory(const Ref<EditorExportPreset> &p_preset) { Ref<DirAccess> da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES); + String gradle_build_directory = ExportTemplateManager::get_android_build_directory(p_preset); // Clear the APK assets directory - if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) { + String apk_assets_directory = gradle_build_directory.path_join(APK_ASSETS_DIRECTORY); + if (da_res->dir_exists(apk_assets_directory)) { print_verbose("Clearing APK assets directory..."); - Ref<DirAccess> da_assets = DirAccess::open(APK_ASSETS_DIRECTORY); + Ref<DirAccess> da_assets = DirAccess::open(apk_assets_directory); + ERR_FAIL_COND(da_assets.is_null()); + da_assets->erase_contents_recursive(); - da_res->remove(APK_ASSETS_DIRECTORY); + da_res->remove(apk_assets_directory); } // Clear the AAB assets directory - if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) { + String aab_assets_directory = gradle_build_directory.path_join(AAB_ASSETS_DIRECTORY); + if (da_res->dir_exists(aab_assets_directory)) { print_verbose("Clearing AAB assets directory..."); - Ref<DirAccess> da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY); + Ref<DirAccess> da_assets = DirAccess::open(aab_assets_directory); + ERR_FAIL_COND(da_assets.is_null()); + da_assets->erase_contents_recursive(); - da_res->remove(AAB_ASSETS_DIRECTORY); + da_res->remove(aab_assets_directory); } } -void EditorExportPlatformAndroid::_remove_copied_libs() { +void EditorExportPlatformAndroid::_remove_copied_libs(String p_gdextension_libs_path) { print_verbose("Removing previously installed libraries..."); Error error; - String libs_json = FileAccess::get_file_as_string(GDEXTENSION_LIBS_PATH, &error); + String libs_json = FileAccess::get_file_as_string(p_gdextension_libs_path, &error); if (error || libs_json.is_empty()) { print_verbose("No previously installed libraries found"); return; @@ -2667,7 +2877,7 @@ void EditorExportPlatformAndroid::_remove_copied_libs() { print_verbose("Removing previously installed library " + libs[i]); da->remove(libs[i]); } - da->remove(GDEXTENSION_LIBS_PATH); + da->remove(p_gdextension_libs_path); } String EditorExportPlatformAndroid::join_list(const List<String> &p_parts, const String &p_separator) { @@ -2726,6 +2936,8 @@ String EditorExportPlatformAndroid::_resolve_export_plugin_android_library_path( bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExportPreset> &p_preset) { bool first_build = last_gradle_build_time == 0; bool have_plugins_changed = false; + String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + bool has_build_dir_changed = last_gradle_build_dir != gradle_build_dir; String plugin_names = _get_plugins_names(p_preset); @@ -2745,9 +2957,10 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor } last_gradle_build_time = OS::get_singleton()->get_unix_time(); + last_gradle_build_dir = gradle_build_dir; last_plugin_names = plugin_names; - return have_plugins_changed || first_build; + return have_plugins_changed || has_build_dir_changed || first_build; } Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { @@ -2759,12 +2972,19 @@ Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + const String base_dir = p_path.get_base_dir(); + if (!DirAccess::exists(base_dir)) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Target folder does not exist or is inaccessible: \"%s\""), base_dir)); + return ERR_FILE_BAD_PATH; + } + String src_apk; Error err; EditorProgress ep("export", TTR("Exporting for Android"), 105, true); bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build")); + String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : ""; bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); bool apk_expansion = p_preset->get("apk_expansion/enable"); Vector<ABI> enabled_abis = get_enabled_abis(p_preset); @@ -2813,34 +3033,51 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unsupported export format!")); return ERR_UNCONFIGURED; } + String err_string; + if (!has_valid_username_and_password(p_preset, err_string)) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR(err_string)); + return ERR_UNCONFIGURED; + } if (use_gradle_build) { print_verbose("Starting gradle build..."); //test that installed build version is alright { print_verbose("Checking build version..."); - Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::READ); + String gradle_base_directory = gradle_build_directory.get_base_dir(); + Ref<FileAccess> f = FileAccess::open(gradle_base_directory.path_join(".build_version"), FileAccess::READ); if (f.is_null()) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Trying to build from a gradle 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(); - print_verbose("- build version: " + version); - if (version != VERSION_FULL_CONFIG) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Android build version mismatch: Template installed: %s, Godot version: %s. Please reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); + String current_version = ExportTemplateManager::get_android_template_identifier(p_preset); + String installed_version = f->get_line().strip_edges(); + print_verbose("- build version: " + installed_version); + if (installed_version != current_version) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR(MISMATCHED_VERSIONS_MESSAGE), installed_version, current_version)); return ERR_UNCONFIGURED; } } const String assets_directory = get_assets_directory(p_preset, export_format); + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + if (java_sdk_path.is_empty()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Java SDK path must be configured in Editor Settings at 'export/android/java_sdk_path'.")); + return ERR_UNCONFIGURED; + } + print_verbose("Java sdk path: " + java_sdk_path); + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); - ERR_FAIL_COND_V_MSG(sdk_path.is_empty(), ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'."); + if (sdk_path.is_empty()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'.")); + return ERR_UNCONFIGURED; + } print_verbose("Android sdk path: " + sdk_path); // TODO: should we use "package/name" or "application/config/name"? String project_name = get_project_name(p_preset->get("package/name")); - err = _create_project_name_strings_files(p_preset, project_name); //project name localization. + err = _create_project_name_strings_files(p_preset, project_name, gradle_build_directory); //project name localization. if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to overwrite res://android/build/res/*.xml files with project name.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to overwrite res/*.xml files with project name.")); } // Copies the project icon files into the appropriate Gradle project directory. _copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background); @@ -2848,24 +3085,26 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP _write_tmp_manifest(p_preset, p_give_internet, p_debug); //stores all the project files inside the Gradle project directory. Also includes all ABIs - _clear_assets_directory(); - _remove_copied_libs(); + _clear_assets_directory(p_preset); + String gdextension_libs_path = gradle_build_directory.path_join(GDEXTENSION_LIBS_PATH); + _remove_copied_libs(gdextension_libs_path); if (!apk_expansion) { print_verbose("Exporting project files..."); CustomExportData user_data; user_data.assets_directory = assets_directory; + user_data.libs_directory = gradle_build_directory.path_join("libs"); user_data.debug = p_debug; if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); + err = export_project_files(true, 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); + err = export_project_files(true, 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; } if (user_data.libs.size() > 0) { - Ref<FileAccess> fa = FileAccess::open(GDEXTENSION_LIBS_PATH, FileAccess::WRITE); + Ref<FileAccess> fa = FileAccess::open(gdextension_libs_path, FileAccess::WRITE); fa->store_string(JSON::stringify(user_data.libs, "\t")); } } else { @@ -2879,8 +3118,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP print_verbose("Storing command line flags..."); store_file_at_path(assets_directory + "/_cl_", command_line_flags); + print_verbose("Updating JAVA_HOME environment to " + java_sdk_path); + OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path); + print_verbose("Updating ANDROID_HOME environment to " + sdk_path); - OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required + OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); String build_command; #ifdef WINDOWS_ENABLED @@ -2889,7 +3131,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP build_command = "gradlew"; #endif - String build_path = ProjectSettings::get_singleton()->get_resource_path().path_join("android/build"); + String build_path = ProjectSettings::get_singleton()->globalize_path(gradle_build_directory); build_command = build_path.path_join(build_command); String package_name = get_package_name(p_preset->get("package/unique_name")); @@ -2906,6 +3148,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String enabled_abi_string = join_abis(enabled_abis, "|", false); String sign_flag = should_sign ? "true" : "false"; String zipalign_flag = "true"; + String compress_native_libraries_flag = bool(p_preset->get("gradle_build/compress_native_libraries")) ? "true" : "false"; Vector<String> android_libraries; Vector<String> android_dependencies; @@ -2943,6 +3186,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String combined_android_dependencies_maven_repos = String("|").join(android_dependencies_maven_repos); List<String> cmdline; + cmdline.push_back("validateJavaVersion"); if (clean_build_required) { cmdline.push_back("clean"); } @@ -2969,6 +3213,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP cmdline.push_back("-Pplugins_maven_repos=" + combined_android_dependencies_maven_repos); // argument to specify the list of maven repos for android dependencies provided by plugins. cmdline.push_back("-Pperform_zipalign=" + zipalign_flag); // argument to specify whether the build should be zipaligned. cmdline.push_back("-Pperform_signing=" + sign_flag); // argument to specify whether the build should be signed. + cmdline.push_back("-Pcompress_native_libraries=" + compress_native_libraries_flag); // argument to specify whether the build should compress native libraries. cmdline.push_back("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG)); // NOTE: The release keystore is not included in the verbose logging @@ -3018,10 +3263,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } } - int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline); + String build_project_output; + int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline, true, false, &build_project_output); if (result != 0) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Building of Android project failed, check output for the error. Alternatively visit docs.godotengine.org for Android build documentation.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Building of Android project failed, check output for the error:") + "\n\n" + build_project_output); return ERR_CANT_CREATE; + } else { + print_verbose(build_project_output); } List<String> copy_args; @@ -3048,10 +3296,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP copy_args.push_back("-Pexport_filename=" + export_filename); print_verbose("Copying Android binary using gradle command: " + String("\n") + build_command + " " + join_list(copy_args, String(" "))); - int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args); + String copy_binary_output; + int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args, true, false, ©_binary_output); if (copy_result != 0) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to copy and rename export file, check gradle project directory for outputs.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to copy and rename export file:") + "\n\n" + copy_binary_output); return ERR_CANT_CREATE; + } else { + print_verbose(copy_binary_output); } print_verbose("Successfully completed Android gradle build."); @@ -3077,10 +3328,6 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } } - if (!DirAccess::exists(p_path.get_base_dir())) { - return ERR_FILE_BAD_PATH; - } - Ref<FileAccess> io_fa; zlib_filefunc_def io = zipio_create_io(&io_fa); @@ -3235,7 +3482,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(p_preset, p_debug, ignore_apk_file, &ed, save_apk_so); + err = export_project_files(true, p_preset, p_debug, ignore_apk_file, &ed, save_apk_so); } else { if (apk_expansion) { err = save_apk_expansion_file(p_preset, p_debug, p_path); @@ -3247,7 +3494,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(p_preset, p_debug, save_apk_file, &ed, save_apk_so); + err = export_project_files(true, p_preset, p_debug, save_apk_file, &ed, save_apk_so); } } @@ -3273,10 +3520,6 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP zipClose(unaligned_apk, nullptr); unzClose(pkg); - if (err != OK) { - CLEANUP_AND_RETURN(err); - } - // Let's zip-align (must be done before signing) static const int ZIP_ALIGNMENT = 4; @@ -3363,6 +3606,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP // file will invalidate the signature. err = sign_apk(p_preset, p_debug, p_path, ep); if (err != OK) { + // Message is supplied by the subroutine method. CLEANUP_AND_RETURN(err); } } @@ -3396,6 +3640,7 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() { android_plugins_changed.set(); #endif // DISABLE_DEPRECATED #ifndef ANDROID_ENABLED + _update_preset_status(); check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif } |