diff options
Diffstat (limited to 'platform/android/export/export_plugin.cpp')
-rw-r--r-- | platform/android/export/export_plugin.cpp | 475 |
1 files changed, 352 insertions, 123 deletions
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 6030239695..5e42d8a967 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -30,9 +30,9 @@ #include "export_plugin.h" -#include "../logo_svg.gen.h" -#include "../run_icon_svg.gen.h" #include "gradle_export_util.h" +#include "logo_svg.gen.h" +#include "run_icon_svg.gen.h" #include "core/config/project_settings.h" #include "core/io/dir_access.h" @@ -45,11 +45,13 @@ #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/import/resource_importer_texture_settings.h" +#include "editor/themes/editor_scale.h" #include "main/splash.gen.h" +#include "scene/resources/image_texture.h" -#include "modules/modules_enabled.gen.h" // For svg. +#include "modules/modules_enabled.gen.h" // For mono and svg. #ifdef MODULE_SVG_ENABLED #include "modules/svg/image_loader_svg.h" #endif @@ -260,30 +262,32 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { EditorExportPlatformAndroid *ea = static_cast<EditorExportPlatformAndroid *>(ud); while (!ea->quit_request.is_set()) { - // Check for plugins updates +#ifndef DISABLE_DEPRECATED + // Check for android plugins updates { // Nothing to do if we already know the plugins have changed. - if (!ea->plugins_changed.is_set()) { + if (!ea->android_plugins_changed.is_set()) { Vector<PluginConfigAndroid> loaded_plugins = get_plugins(); - MutexLock lock(ea->plugins_lock); + MutexLock lock(ea->android_plugins_lock); - if (ea->plugins.size() != loaded_plugins.size()) { - ea->plugins_changed.set(); + if (ea->android_plugins.size() != loaded_plugins.size()) { + ea->android_plugins_changed.set(); } else { - for (int i = 0; i < ea->plugins.size(); i++) { - if (ea->plugins[i].name != loaded_plugins[i].name) { - ea->plugins_changed.set(); + for (int i = 0; i < ea->android_plugins.size(); i++) { + if (ea->android_plugins[i].name != loaded_plugins[i].name) { + ea->android_plugins_changed.set(); break; } } } - if (ea->plugins_changed.is_set()) { - ea->plugins = loaded_plugins; + if (ea->android_plugins_changed.is_set()) { + ea->android_plugins = loaded_plugins; } } } +#endif // DISABLE_DEPRECATED // Check for devices updates String adb = get_adb_path(); @@ -456,7 +460,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; } @@ -533,13 +537,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; } @@ -627,6 +624,7 @@ Vector<EditorExportPlatformAndroid::ABI> EditorExportPlatformAndroid::get_abis() return abis; } +#ifndef DISABLE_DEPRECATED /// List the gdap files in the directory specified by the p_path parameter. Vector<String> EditorExportPlatformAndroid::list_gdap_files(const String &p_path) { Vector<String> dir_files; @@ -693,6 +691,7 @@ Vector<PluginConfigAndroid> EditorExportPlatformAndroid::get_enabled_plugins(con return enabled_plugins; } +#endif // DISABLE_DEPRECATED Error EditorExportPlatformAndroid::store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method) { zip_fileinfo zipfi = get_zip_fileinfo(); @@ -827,16 +826,6 @@ void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> r_permissions.push_back("android.permission.INTERNET"); } } - - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - if (xr_mode_index == XR_MODE_OPENXR) { - int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required - if (hand_tracking_index > XR_HAND_TRACKING_NONE) { - if (r_permissions.find("com.oculus.permission.HAND_TRACKING") == -1) { - r_permissions.push_back("com.oculus.permission.HAND_TRACKING"); - } - } - } } void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { @@ -860,8 +849,23 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres } } - manifest_text += _get_xr_features_tag(p_preset, _uses_vulkan()); - manifest_text += _get_application_tag(p_preset, _has_read_write_storage_permission(perms)); + if (_uses_vulkan()) { + manifest_text += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"false\" android:version=\"1\" />\n"; + manifest_text += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.version\" android:required=\"true\" android:version=\"0x400003\" />\n"; + } + + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) { + const String contents = export_plugins[i]->get_android_manifest_element_contents(Ref<EditorExportPlatform>(this), p_debug); + if (!contents.is_empty()) { + manifest_text += contents; + manifest_text += "\n"; + } + } + } + + 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")); @@ -894,7 +898,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p uint32_t string_table_ends = 0; Vector<uint8_t> stable_extra; - String version_name = p_preset->get("version/name"); + String version_name = p_preset->get_version("version/name"); int version_code = p_preset->get("version/code"); String package_name = p_preset->get("package/unique_name"); @@ -1107,7 +1111,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; @@ -1531,18 +1535,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()) { @@ -1566,13 +1584,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()); @@ -1594,7 +1605,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). @@ -1699,7 +1714,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); } } @@ -1720,7 +1734,7 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport } } else if (p_name == "gradle_build/use_gradle_build") { bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); - String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(Ref<EditorExportPreset>(p_preset))); + String enabled_plugins_names = _get_plugins_names(Ref<EditorExportPreset>(p_preset)); if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) { return TTR("\"Use Gradle Build\" must be enabled to use the plugins."); } @@ -1730,22 +1744,6 @@ 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 == "xr_features/hand_tracking") { - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int hand_tracking = p_preset->get("xr_features/hand_tracking"); - if (xr_mode_index != XR_MODE_OPENXR) { - if (hand_tracking > XR_HAND_TRACKING_NONE) { - return TTR("\"Hand Tracking\" is only valid when \"XR Mode\" is \"OpenXR\"."); - } - } - } else if (p_name == "xr_features/passthrough") { - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int passthrough_mode = p_preset->get("xr_features/passthrough"); - if (xr_mode_index != XR_MODE_OPENXR) { - if (passthrough_mode > XR_PASSTHROUGH_NONE) { - return TTR("\"Passthrough\" is only valid when \"XR Mode\" is \"OpenXR\"."); - } - } } else if (p_name == "gradle_build/export_format") { bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB && !gradle_build_enabled) { @@ -1807,12 +1805,14 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", VULKAN_MIN_SDK_VERSION)), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true)); +#ifndef DISABLE_DEPRECATED Vector<PluginConfigAndroid> plugins_configs = get_plugins(); for (int i = 0; i < plugins_configs.size(); i++) { print_verbose("Found Android plugin " + plugins_configs[i].name); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), plugins_configs[i].name)), false)); } - plugins_changed.clear(); + android_plugins_changed.clear(); +#endif // DISABLE_DEPRECATED // Android supports multiple architectures in an app bundle, so // we expose each option as a checkbox in the export dialog. @@ -1827,20 +1827,23 @@ 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"), "1.0")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "com.example.$genname", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "package/app_category", PROPERTY_HINT_ENUM, "Accessibility,Audio,Game,Image,Maps,News,Productivity,Social,Video"), APP_CATEGORY_GAME)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/retain_data_on_uninstall"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/exclude_from_recents"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_in_android_tv"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_in_app_library"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_as_launcher_app"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), "")); @@ -1849,9 +1852,6 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/opengl_debug"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,OpenXR"), XR_MODE_REGULAR, false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_HAND_TRACKING_NONE, false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking_frequency", PROPERTY_HINT_ENUM, "Low,High"), XR_HAND_TRACKING_FREQUENCY_LOW)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/passthrough", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_PASSTHROUGH_NONE, false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true)); @@ -1889,12 +1889,14 @@ Ref<Texture2D> EditorExportPlatformAndroid::get_logo() const { } bool EditorExportPlatformAndroid::should_update_export_options() { - bool export_options_changed = plugins_changed.is_set(); - if (export_options_changed) { +#ifndef DISABLE_DEPRECATED + if (android_plugins_changed.is_set()) { // don't clear unless we're reporting true, to avoid race - plugins_changed.clear(); + android_plugins_changed.clear(); + return true; } - return export_options_changed; +#endif // DISABLE_DEPRECATED + return false; } bool EditorExportPlatformAndroid::poll_export() { @@ -1955,8 +1957,9 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, return ERR_SKIP; } + const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug"); const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); - const bool use_reverse = devices[p_device].api_level >= 21; + const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug; if (use_reverse) { p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; @@ -1983,7 +1986,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, String output; bool remove_prev = EDITOR_GET("export/android/one_click_deploy_clear_previous_install"); - String version_name = p_preset->get("version/name"); + String version_name = p_preset->get_version("version/name"); String package_name = p_preset->get("package/unique_name"); if (remove_prev) { @@ -2069,7 +2072,10 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, print_line("Reverse result2: " + itos(rv)); } } else { - static const char *const msg = "--- Device API < 21; debugging over Wi-Fi ---"; + static const char *const api_version_msg = "--- Device API < 21; debugging over Wi-Fi ---"; + static const char *const manual_override_msg = "--- Wi-Fi remote debug enabled in project settings; debugging over Wi-Fi ---"; + + const char *const msg = use_wifi_for_remote_debug ? manual_override_msg : api_version_msg; EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR); print_line(String(msg).to_upper()); } @@ -2109,8 +2115,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"; } @@ -2122,13 +2137,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"); @@ -2227,11 +2242,77 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ return apksigner_path; } -bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { +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; const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); +#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). if (!gradle_build_enabled) { @@ -2286,7 +2367,8 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito err += TTR("Either Debug Keystore, Debug User AND Debug Password settings must be configured OR none of them.") + "\n"; } - if (!FileAccess::exists(dk)) { + // Use OR to make the export UI able to show this error. + if ((p_debug || !dk.is_empty()) && !FileAccess::exists(dk)) { dk = EDITOR_GET("export/android/debug_keystore"); if (!FileAccess::exists(dk)) { valid = false; @@ -2303,11 +2385,37 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito err += TTR("Either Release Keystore, Release User AND Release Password settings must be configured OR none of them.") + "\n"; } - if (!rk.is_empty() && !FileAccess::exists(rk)) { + if (!p_debug && !rk.is_empty() && !FileAccess::exists(rk)) { valid = false; err += TTR("Release keystore incorrectly configured in the export preset.") + "\n"; } + 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"; @@ -2323,7 +2431,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito valid = false; } - // Validate that adb is available + // Validate that adb is available. String adb_path = get_adb_path(); if (!FileAccess::exists(adb_path)) { err += TTR("Unable to find Android SDK platform-tools' adb command."); @@ -2345,7 +2453,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito if (!target_sdk_version.is_valid_int()) { target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); } - // Validate that apksigner is available + // Validate that apksigner is available. String apksigner_path = get_apksigner_path(target_sdk_version.to_int()); if (!FileAccess::exists(apksigner_path)) { err += TTR("Unable to find Android SDK build-tools' apksigner command."); @@ -2380,10 +2488,8 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit } } - String etc_error = test_etc2(); - if (!etc_error.is_empty()) { + if (!ResourceImporterTextureSettings::should_import_etc2_astc()) { valid = false; - err += etc_error; } String min_sdk_str = p_preset->get("gradle_build/min_sdk"); @@ -2420,6 +2526,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; } @@ -2628,6 +2741,8 @@ void EditorExportPlatformAndroid::_clear_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); + ERR_FAIL_COND(da_assets.is_null()); + da_assets->erase_contents_recursive(); da_res->remove(APK_ASSETS_DIRECTORY); } @@ -2636,6 +2751,8 @@ void EditorExportPlatformAndroid::_clear_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); + ERR_FAIL_COND(da_assets.is_null()); + da_assets->erase_contents_recursive(); da_res->remove(AAB_ASSETS_DIRECTORY); } @@ -2685,6 +2802,64 @@ String EditorExportPlatformAndroid::join_abis(const Vector<EditorExportPlatformA return ret; } +String EditorExportPlatformAndroid::_get_plugins_names(const Ref<EditorExportPreset> &p_preset) const { + Vector<String> names; + +#ifndef DISABLE_DEPRECATED + PluginConfigAndroid::get_plugins_names(get_enabled_plugins(p_preset), names); +#endif // DISABLE_DEPRECATED + + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) { + names.push_back(export_plugins[i]->get_name()); + } + } + + String plugins_names = String("|").join(names); + return plugins_names; +} + +String EditorExportPlatformAndroid::_resolve_export_plugin_android_library_path(const String &p_android_library_path) const { + String absolute_path; + if (!p_android_library_path.is_empty()) { + if (p_android_library_path.is_absolute_path()) { + absolute_path = ProjectSettings::get_singleton()->globalize_path(p_android_library_path); + } else { + const String export_plugin_absolute_path = String("res://addons/").path_join(p_android_library_path); + absolute_path = ProjectSettings::get_singleton()->globalize_path(export_plugin_absolute_path); + } + } + return absolute_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 plugin_names = _get_plugins_names(p_preset); + + if (!first_build) { + have_plugins_changed = plugin_names != last_plugin_names; +#ifndef DISABLE_DEPRECATED + if (!have_plugins_changed) { + Vector<PluginConfigAndroid> enabled_plugins = get_enabled_plugins(p_preset); + for (int i = 0; i < enabled_plugins.size(); i++) { + if (enabled_plugins.get(i).last_updated > last_gradle_build_time) { + have_plugins_changed = true; + break; + } + } + } +#endif // DISABLE_DEPRECATED + } + + last_gradle_build_time = OS::get_singleton()->get_unix_time(); + last_plugin_names = plugin_names; + + return have_plugins_changed || first_build; +} + Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { int export_format = int(p_preset->get("gradle_build/export_format")); bool should_sign = p_preset->get("package/signed"); @@ -2694,6 +2869,12 @@ 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; @@ -2748,6 +2929,11 @@ 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..."); @@ -2767,8 +2953,18 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } } 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"? @@ -2814,8 +3010,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 @@ -2829,7 +3028,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP 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 version_name = p_preset->get_version("version/name"); String min_sdk_version = p_preset->get("gradle_build/min_sdk"); if (!min_sdk_version.is_valid_int()) { min_sdk_version = itos(VULKAN_MIN_SDK_VERSION); @@ -2842,13 +3041,43 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String sign_flag = should_sign ? "true" : "false"; String zipalign_flag = "true"; + Vector<String> android_libraries; + Vector<String> android_dependencies; + Vector<String> android_dependencies_maven_repos; + +#ifndef DISABLE_DEPRECATED Vector<PluginConfigAndroid> enabled_plugins = get_enabled_plugins(p_preset); - String local_plugins_binaries = PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_LOCAL, enabled_plugins); - String remote_plugins_binaries = PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_REMOTE, enabled_plugins); - String custom_maven_repos = PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins); - bool clean_build_required = is_clean_build_required(enabled_plugins); + PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_LOCAL, enabled_plugins, android_libraries); + PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_REMOTE, enabled_plugins, android_dependencies); + PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins, android_dependencies_maven_repos); +#endif // DISABLE_DEPRECATED + + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) { + PackedStringArray export_plugin_android_libraries = export_plugins[i]->get_android_libraries(Ref<EditorExportPlatform>(this), p_debug); + for (int k = 0; k < export_plugin_android_libraries.size(); k++) { + const String resolved_android_library_path = _resolve_export_plugin_android_library_path(export_plugin_android_libraries[k]); + if (!resolved_android_library_path.is_empty()) { + android_libraries.push_back(resolved_android_library_path); + } + } + + PackedStringArray export_plugin_android_dependencies = export_plugins[i]->get_android_dependencies(Ref<EditorExportPlatform>(this), p_debug); + android_dependencies.append_array(export_plugin_android_dependencies); + + PackedStringArray export_plugin_android_dependencies_maven_repos = export_plugins[i]->get_android_dependencies_maven_repos(Ref<EditorExportPlatform>(this), p_debug); + android_dependencies_maven_repos.append_array(export_plugin_android_dependencies_maven_repos); + } + } + + bool clean_build_required = _is_clean_build_required(p_preset); + String combined_android_libraries = String("|").join(android_libraries); + String combined_android_dependencies = String("|").join(android_dependencies); + 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"); } @@ -2870,9 +3099,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP cmdline.push_back("-Pexport_version_min_sdk=" + min_sdk_version); // argument to specify the min sdk. cmdline.push_back("-Pexport_version_target_sdk=" + target_sdk_version); // argument to specify the target sdk. 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. + cmdline.push_back("-Pplugins_local_binaries=" + combined_android_libraries); // argument to specify the list of android libraries provided by plugins. + cmdline.push_back("-Pplugins_remote_binaries=" + combined_android_dependencies); // argument to specify the list of android dependencies provided by plugins. + 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("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG)); @@ -2924,10 +3153,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; @@ -2954,10 +3186,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."); @@ -2983,10 +3218,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); @@ -3018,7 +3249,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String cmdline = p_preset->get("command_line/extra_args"); - String version_name = p_preset->get("version/name"); + String version_name = p_preset->get_version("version/name"); String package_name = p_preset->get("package/unique_name"); String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); @@ -3179,10 +3410,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; @@ -3269,6 +3496,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); } } @@ -3290,16 +3518,17 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() { Ref<Image> img = memnew(Image); const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false); + ImageLoaderSVG::create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false); logo = ImageTexture::create_from_image(img); - img_loader.create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false); + ImageLoaderSVG::create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false); run_icon = ImageTexture::create_from_image(img); #endif devices_changed.set(); - plugins_changed.set(); +#ifndef DISABLE_DEPRECATED + android_plugins_changed.set(); +#endif // DISABLE_DEPRECATED #ifndef ANDROID_ENABLED check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif |