diff options
Diffstat (limited to 'platform/android/export/export_plugin.cpp')
-rw-r--r-- | platform/android/export/export_plugin.cpp | 580 |
1 files changed, 364 insertions, 216 deletions
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 317f111202..fdd2fed836 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* export_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* export_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "export_plugin.h" @@ -43,10 +43,16 @@ #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 "main/splash.gen.h" -#include "platform/android/logo.gen.h" -#include "platform/android/run_icon.gen.h" +#include "platform/android/logo_svg.gen.h" +#include "platform/android/run_icon_svg.gen.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif #include <string.h> @@ -205,7 +211,7 @@ static const char *LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi-v 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 *GDNATIVE_LIBS_PATH = "res://android/build/libs/gdnativelibs.json"; +static const char *GDEXTENSION_LIBS_PATH = "res://android/build/libs/gdextensionlibs.json"; static const int icon_densities_count = 6; static const char *launcher_icon_option = PNAME("launcher_icons/main_192x192"); @@ -245,7 +251,8 @@ 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 int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' +static const int DEFAULT_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' +static const int VULKAN_MIN_SDK_VERSION = 24; static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' #ifndef ANDROID_ENABLED @@ -401,7 +408,7 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { } } - if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) { + if (EDITOR_GET("export/android/shutdown_adb_on_exit")) { String adb = get_adb_path(); if (!FileAccess::exists(adb)) { return; //adb not configured @@ -419,7 +426,7 @@ String EditorExportPlatformAndroid::get_project_name(const String &p_name) const if (!p_name.is_empty()) { aname = p_name; } else { - aname = ProjectSettings::get_singleton()->get("application/config/name"); + aname = GLOBAL_GET("application/config/name"); } if (aname.is_empty()) { @@ -431,7 +438,15 @@ String EditorExportPlatformAndroid::get_project_name(const String &p_name) const String EditorExportPlatformAndroid::get_package_name(const String &p_package) const { String pname = p_package; - String basename = ProjectSettings::get_singleton()->get("application/config/name"); + String name = get_valid_basename(); + pname = pname.replace("$genname", name); + return pname; +} + +// Returns the project name without invalid characters +// or the "noname" string if all characters are invalid. +String EditorExportPlatformAndroid::get_valid_basename() const { + String basename = GLOBAL_GET("application/config/name"); basename = basename.to_lower(); String name; @@ -446,13 +461,12 @@ String EditorExportPlatformAndroid::get_package_name(const String &p_package) co first = false; } } + if (name.is_empty()) { name = "noname"; } - pname = pname.replace("$genname", name); - - return pname; + return name; } String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const { @@ -460,7 +474,7 @@ String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportP } bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, String *r_error) const { - String pname = p_package; + String pname = get_package_name(p_package); if (pname.length() == 0) { if (r_error) { @@ -519,6 +533,24 @@ 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; +} + +bool EditorExportPlatformAndroid::is_project_name_valid() const { + // Get the original project name and convert to lowercase. + String basename = GLOBAL_GET("application/config/name"); + basename = basename.to_lower(); + // Check if there are invalid characters. + if (basename != get_valid_basename()) { + return false; + } return true; } @@ -585,12 +617,13 @@ zip_fileinfo EditorExportPlatformAndroid::get_zip_fileinfo() { return zipfi; } -Vector<String> EditorExportPlatformAndroid::get_abis() { - Vector<String> abis; - abis.push_back("armeabi-v7a"); - abis.push_back("arm64-v8a"); - abis.push_back("x86"); - abis.push_back("x86_64"); +Vector<EditorExportPlatformAndroid::ABI> EditorExportPlatformAndroid::get_abis() { + // Should have the same order and size as get_archs. + Vector<ABI> abis; + abis.push_back(ABI("armeabi-v7a", "arm32")); + abis.push_back(ABI("arm64-v8a", "arm64")); + abis.push_back(ABI("x86", "x86_32")); + abis.push_back(ABI("x86_64", "x86_64")); return abis; } @@ -687,24 +720,28 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj return FAILED; } APKExportData *ed = static_cast<APKExportData *>(p_userdata); - Vector<String> abis = get_abis(); + Vector<ABI> abis = get_abis(); bool exported = false; for (int i = 0; i < p_so.tags.size(); ++i) { // shared objects can be fat (compatible with multiple ABIs) - int abi_index = abis.find(p_so.tags[i]); + int abi_index = -1; + for (int j = 0; j < abis.size(); ++j) { + if (abis[j].abi == p_so.tags[i] || abis[j].arch == p_so.tags[i]) { + abi_index = j; + break; + } + } if (abi_index != -1) { exported = true; - String abi = abis[abi_index]; + String abi = abis[abi_index].abi; String dst_path = String("lib").path_join(abi).path_join(p_so.path.get_file()); - Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(p_so.path); Error store_err = store_in_apk(ed, dst_path, array); ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'."); } } if (!exported) { - String abis_string = String(" ").join(abis); - String err = "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + abis_string; - ERR_PRINT(err); + ERR_PRINT("Cannot determine architecture for library \"" + p_so.path + "\". One of the supported architectures must be used as a tag: " + join_abis(abis, " ", true)); return FAILED; } return OK; @@ -725,19 +762,25 @@ Error EditorExportPlatformAndroid::ignore_apk_file(void *p_userdata, const Strin Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const SharedObject &p_so) { ERR_FAIL_COND_V_MSG(!p_so.path.get_file().begins_with("lib"), FAILED, "Android .so file names must start with \"lib\", but got: " + p_so.path); - Vector<String> abis = get_abis(); + Vector<ABI> abis = get_abis(); CustomExportData *export_data = static_cast<CustomExportData *>(p_userdata); bool exported = false; for (int i = 0; i < p_so.tags.size(); ++i) { - int abi_index = abis.find(p_so.tags[i]); + int abi_index = -1; + for (int j = 0; j < abis.size(); ++j) { + if (abis[j].abi == p_so.tags[i] || abis[j].arch == p_so.tags[i]) { + abi_index = j; + break; + } + } if (abi_index != -1) { exported = true; String base = "res://android/build/libs"; String type = export_data->debug ? "debug" : "release"; - String abi = abis[abi_index]; + 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); - Vector<uint8_t> data = FileAccess::get_file_as_array(p_so.path); + 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); ERR_FAIL_COND_V_MSG(err, err, "Failed to copy .so file from " + p_so.path + " to " + dst_path); @@ -745,7 +788,7 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared } } ERR_FAIL_COND_V_MSG(!exported, FAILED, - "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + String(" ").join(abis)); + "Cannot determine architecture for library \"" + p_so.path + "\". One of the supported architectures must be used as a tag:" + join_abis(abis, " ", true)); return OK; } @@ -757,6 +800,12 @@ bool EditorExportPlatformAndroid::_has_manage_external_storage_permission(const return p_permissions.find("android.permission.MANAGE_EXTERNAL_STORAGE") != -1; } +bool EditorExportPlatformAndroid::_uses_vulkan() { + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + bool uses_vulkan = (current_renderer == "forward_plus" || current_renderer == "mobile") && GLOBAL_GET("rendering/rendering_device/driver.android") == "vulkan"; + return uses_vulkan; +} + void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { const char **aperms = android_perms; while (*aperms) { @@ -791,7 +840,7 @@ void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> } void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { - print_verbose("Building temporary manifest.."); + print_verbose("Building temporary manifest..."); String manifest_text = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" @@ -811,7 +860,7 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres } } - manifest_text += _get_xr_features_tag(p_preset); + manifest_text += _get_xr_features_tag(p_preset, _uses_vulkan()); manifest_text += _get_application_tag(p_preset, _has_read_write_storage_permission(perms)); manifest_text += "</manifest>\n"; String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); @@ -857,12 +906,8 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p bool screen_support_large = p_preset->get("screen/support_large"); bool screen_support_xlarge = p_preset->get("screen/support_xlarge"); - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); - int hand_tracking_frequency_index = p_preset->get("xr_features/hand_tracking_frequency"); - bool backup_allowed = p_preset->get("user_data_backup/allow"); - bool classify_as_game = p_preset->get("package/classify_as_game"); + int app_category = p_preset->get("package/app_category"); bool retain_data_on_uninstall = p_preset->get("package/retain_data_on_uninstall"); bool exclude_from_recents = p_preset->get("package/exclude_from_recents"); bool is_resizeable = bool(GLOBAL_GET("display/window/size/resizable")); @@ -961,8 +1006,12 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]); } + if (tname == "application" && attrname == "appCategory") { + encode_uint32(_get_app_category_value(app_category), &p_manifest.write[iofs + 16]); + } + if (tname == "application" && attrname == "isGame") { - encode_uint32(classify_as_game, &p_manifest.write[iofs + 16]); + encode_uint32(app_category == APP_CATEGORY_GAME, &p_manifest.write[iofs + 16]); } if (tname == "application" && attrname == "hasFragileUserData") { @@ -981,6 +1030,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p encode_uint32(is_resizeable, &p_manifest.write[iofs + 16]); } + if (tname == "provider" && attrname == "authorities") { + string_table.write[attr_value] = get_package_name(package_name) + String(".fileprovider"); + } + if (tname == "supports-screens") { if (attrname == "smallScreens") { encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); @@ -996,25 +1049,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } } - // Hand tracking related configurations - if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) { - if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") { - string_table.write[attr_value] = "com.oculus.handtracking.frequency"; - } - - if (tname == "meta-data" && attrname == "value" && value == "xr_hand_tracking_metadata_value") { - string_table.write[attr_value] = (hand_tracking_frequency_index == XR_HAND_TRACKING_FREQUENCY_LOW ? "LOW" : "HIGH"); - } - - if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_version_name") { - string_table.write[attr_value] = "com.oculus.handtracking.version"; - } - - if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_version_value") { - string_table.write[attr_value] = "V2.0"; - } - } - iofs += 20; } @@ -1029,26 +1063,11 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p Vector<bool> feature_required_list; Vector<int> feature_versions; - if (xr_mode_index == XR_MODE_OPENXR) { - // Set degrees of freedom - feature_names.push_back("android.hardware.vr.headtracking"); + if (_uses_vulkan()) { + // Require vulkan hardware level 1 support + feature_names.push_back("android.hardware.vulkan.level"); feature_required_list.push_back(true); feature_versions.push_back(1); - - // Check for hand tracking - if (hand_tracking_index > XR_HAND_TRACKING_NONE) { - feature_names.push_back("oculus.software.handtracking"); - feature_required_list.push_back(hand_tracking_index == XR_HAND_TRACKING_REQUIRED); - feature_versions.push_back(-1); // no version attribute should be added. - } - - // Check for passthrough - int passthrough_mode = p_preset->get("xr_features/passthrough"); - if (passthrough_mode > XR_PASSTHROUGH_NONE) { - feature_names.push_back("com.oculus.feature.PASSTHROUGH"); - feature_required_list.push_back(passthrough_mode == XR_PASSTHROUGH_REQUIRED); - feature_versions.push_back(-1); - } } if (feature_names.size() > 0) { @@ -1395,7 +1414,7 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> & Vector<String> string_table; String package_name = p_preset->get("package/name"); - Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); + Dictionary appnames = GLOBAL_GET("application/config/name_localized"); for (uint32_t i = 0; i < string_count; i++) { uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]); @@ -1505,9 +1524,9 @@ 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 = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize"); - bool apply_filter = ProjectSettings::get_singleton()->get("application/boot_splash/use_filter"); - String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + bool scale_splash = GLOBAL_GET("application/boot_splash/fullsize"); + bool apply_filter = GLOBAL_GET("application/boot_splash/use_filter"); + String project_splash_path = GLOBAL_GET("application/boot_splash/image"); if (!project_splash_path.is_empty()) { splash_image.instantiate(); @@ -1528,7 +1547,7 @@ String EditorExportPlatformAndroid::load_splash_refs(Ref<Image> &splash_image, R } if (scale_splash) { - Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); int width, height; if (screen_size.width > screen_size.height) { // scale horizontally @@ -1559,7 +1578,7 @@ String EditorExportPlatformAndroid::load_splash_refs(Ref<Image> &splash_image, R } void EditorExportPlatformAndroid::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"); + String project_icon_path = GLOBAL_GET("application/config/icon"); icon.instantiate(); foreground.instantiate(); @@ -1656,11 +1675,11 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor } } -Vector<String> EditorExportPlatformAndroid::get_enabled_abis(const Ref<EditorExportPreset> &p_preset) { - Vector<String> abis = get_abis(); - Vector<String> enabled_abis; +Vector<EditorExportPlatformAndroid::ABI> EditorExportPlatformAndroid::get_enabled_abis(const Ref<EditorExportPreset> &p_preset) { + Vector<ABI> abis = get_abis(); + Vector<ABI> enabled_abis; for (int i = 0; i < abis.size(); ++i) { - bool is_enabled = p_preset->get("architectures/" + abis[i]); + bool is_enabled = p_preset->get("architectures/" + abis[i].abi); if (is_enabled) { enabled_abis.push_back(abis[i]); } @@ -1670,10 +1689,12 @@ Vector<String> EditorExportPlatformAndroid::get_enabled_abis(const Ref<EditorExp void EditorExportPlatformAndroid::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const { r_features->push_back("etc2"); + r_features->push_back("astc"); - Vector<String> abis = get_enabled_abis(p_preset); + Vector<ABI> abis = get_enabled_abis(p_preset); for (int i = 0; i < abis.size(); ++i) { - r_features->push_back(abis[i]); + r_features->push_back(abis[i].arch); + r_features->push_back(abis[i].abi); } } @@ -1681,12 +1702,12 @@ 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, "custom_build/use_custom_build"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK)); // Using String instead of int to default to an empty string (no override) with placeholder for instructions (see GH-62465). // This implies doing validation that the string is a proper int. - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_MIN_SDK_VERSION)), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_MIN_SDK_VERSION)), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "")); Vector<PluginConfigAndroid> plugins_configs = get_plugins(); for (int i = 0; i < plugins_configs.size(); i++) { @@ -1697,9 +1718,9 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio // Android supports multiple architectures in an app bundle, so // we expose each option as a checkbox in the export dialog. - const Vector<String> abis = get_abis(); + const Vector<ABI> abis = get_abis(); for (int i = 0; i < abis.size(); ++i) { - const String abi = abis[i]; + const String abi = abis[i].abi; // All Android devices supporting Vulkan run 64-bit Android, // so there is usually no point in exporting for 32-bit Android. const bool is_default = abi == "arm64-v8a"; @@ -1719,7 +1740,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/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::BOOL, "package/classify_as_game"), 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)); @@ -1920,7 +1941,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, print_verbose(output); if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { - int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); + int dbg_port = EDITOR_GET("network/debug/remote_port"); args.clear(); args.push_back("-s"); args.push_back(devices[p_device].id); @@ -1935,7 +1956,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, } if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { - int fs_port = EditorSettings::get_singleton()->get("filesystem/file_server/port"); + int fs_port = EDITOR_GET("filesystem/file_server/port"); args.clear(); args.push_back("-s"); @@ -1965,7 +1986,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, args.push_back("shell"); args.push_back("am"); args.push_back("start"); - if ((bool)EditorSettings::get_singleton()->get("export/android/force_system_user") && devices[p_device].api_level >= 17) { // Multi-user introduced in Android 17 + if ((bool)EDITOR_GET("export/android/force_system_user") && devices[p_device].api_level >= 17) { // Multi-user introduced in Android 17 args.push_back("--user"); args.push_back("0"); } @@ -1995,17 +2016,20 @@ String EditorExportPlatformAndroid::get_adb_path() { if (OS::get_singleton()->get_name() == "Windows") { exe_ext = ".exe"; } - String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path"); + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); return sdk_path.path_join("platform-tools/adb" + exe_ext); } -String EditorExportPlatformAndroid::get_apksigner_path() { +String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_check_executes) { + if (p_target_sdk == -1) { + p_target_sdk = DEFAULT_TARGET_SDK_VERSION; + } String exe_ext = ""; if (OS::get_singleton()->get_name() == "Windows") { exe_ext = ".bat"; } String apksigner_command_name = "apksigner" + exe_ext; - String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path"); + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); String apksigner_path = ""; Error errn; @@ -2017,23 +2041,89 @@ String EditorExportPlatformAndroid::get_apksigner_path() { } // There are additional versions directories we need to go through. - da->list_dir_begin(); - String sub_dir = da->get_next(); - while (!sub_dir.is_empty()) { - if (!sub_dir.begins_with(".") && da->current_is_dir()) { - // Check if the tool is here. - String tool_path = build_tools_dir.path_join(sub_dir).path_join(apksigner_command_name); - if (FileAccess::exists(tool_path)) { - apksigner_path = tool_path; - break; + Vector<String> dir_list = da->get_directories(); + + // We need to use the version of build_tools that matches the Target SDK + // If somehow we can't find that, we see if a version between 28 and the default target SDK exists. + // We need to avoid versions <= 27 because they fail on Java versions >9 + // If we can't find that, we just use the first valid version. + Vector<String> ideal_versions; + Vector<String> other_versions; + Vector<String> versions; + bool found_target_sdk = false; + // We only allow for versions <= 27 if specifically set + int min_version = p_target_sdk <= 27 ? p_target_sdk : 28; + for (String sub_dir : dir_list) { + if (!sub_dir.begins_with(".")) { + Vector<String> ver_numbers = sub_dir.split("."); + // Dir not a version number, will use as last resort + if (!ver_numbers.size() || !ver_numbers[0].is_valid_int()) { + other_versions.push_back(sub_dir); + continue; + } + int ver_number = ver_numbers[0].to_int(); + if (ver_number == p_target_sdk) { + found_target_sdk = true; + //ensure this is in front of the ones we check + versions.push_back(sub_dir); + } else { + if (ver_number >= min_version && ver_number <= DEFAULT_TARGET_SDK_VERSION) { + ideal_versions.push_back(sub_dir); + } else { + other_versions.push_back(sub_dir); + } } } - sub_dir = da->get_next(); } - da->list_dir_end(); + // we will check ideal versions first, then other versions. + versions.append_array(ideal_versions); + versions.append_array(other_versions); - if (apksigner_path.is_empty()) { + if (!versions.size()) { print_error("Unable to find the 'apksigner' tool."); + return apksigner_path; + } + + int i; + bool failed = false; + String version_to_use; + + List<String> args; + args.push_back("--version"); + String output; + int retval; + Error err; + for (i = 0; i < versions.size(); i++) { + // Check if the tool is here. + apksigner_path = build_tools_dir.path_join(versions[i]).path_join(apksigner_command_name); + if (FileAccess::exists(apksigner_path)) { + version_to_use = versions[i]; + // If we aren't exporting, just break here. + if (!p_check_executes) { + break; + } + // we only check to see if it executes on export because it is slow to load + err = OS::get_singleton()->execute(apksigner_path, args, &output, &retval, false); + if (err || retval) { + failed = true; + } else { + break; + } + } + } + if (i == versions.size()) { + if (failed) { + print_error("All located 'apksigner' tools in " + build_tools_dir + " failed to execute"); + return "<FAILED>"; + } else { + print_error("Unable to find the 'apksigner' tool."); + return ""; + } + } + if (!found_target_sdk) { + print_line("Could not find version of build tools that matches Target SDK, using " + version_to_use); + } else if (failed && found_target_sdk) { + print_line("Version of build tools that matches Target SDK failed to execute, using " + version_to_use); } return apksigner_path; @@ -2042,11 +2132,11 @@ String EditorExportPlatformAndroid::get_apksigner_path() { bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; bool valid = false; - const bool custom_build_enabled = p_preset->get("custom_build/use_custom_build"); + const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); // Look for export templates (first official, and if defined custom templates). - if (!custom_build_enabled) { + if (!gradle_build_enabled) { String template_err; bool dvalid = false; bool rvalid = false; @@ -2099,7 +2189,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito } if (!FileAccess::exists(dk)) { - dk = EditorSettings::get_singleton()->get("export/android/debug_keystore"); + dk = EDITOR_GET("export/android/debug_keystore"); if (!FileAccess::exists(dk)) { valid = false; err += TTR("Debug keystore not configured in the Editor Settings nor in the preset.") + "\n"; @@ -2120,7 +2210,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito err += TTR("Release keystore incorrectly configured in the export preset.") + "\n"; } - String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path"); + 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"; valid = false; @@ -2153,8 +2243,12 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito valid = false; } + String target_sdk_version = p_preset->get("gradle_build/target_sdk"); + if (!target_sdk_version.is_valid_int()) { + target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); + } // Validate that apksigner is available - String apksigner_path = get_apksigner_path(); + 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."); err += TTR("Please check in the Android SDK directory specified in Editor Settings."); @@ -2173,7 +2267,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { String err; bool valid = true; - const bool custom_build_enabled = p_preset->get("custom_build/use_custom_build"); + const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); // Validate the project configuration. bool apk_expansion = p_preset->get("apk_expansion/enable"); @@ -2191,7 +2285,7 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit String pn = p_preset->get("package/unique_name"); String pn_err; - if (!is_package_name_valid(get_package_name(pn), &pn_err)) { + if (!is_package_name_valid(pn, &pn_err)) { valid = false; err += TTR("Invalid package name:") + " " + pn_err + "\n"; } @@ -2202,11 +2296,11 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit err += etc_error; } - // Ensure that `Use Custom Build` is enabled if a plugin is selected. + // Ensure that `Use Gradle Build` is enabled if a plugin is selected. String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(p_preset)); - if (!enabled_plugins_names.is_empty() && !custom_build_enabled) { + if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) { valid = false; - err += TTR("\"Use Custom Build\" must be enabled to use the plugins."); + err += TTR("\"Use Gradle Build\" must be enabled to use the plugins."); err += "\n"; } @@ -2214,6 +2308,12 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit int xr_mode_index = p_preset->get("xr_features/xr_mode"); int hand_tracking = p_preset->get("xr_features/hand_tracking"); int passthrough_mode = p_preset->get("xr_features/passthrough"); + if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) { + valid = false; + err += TTR("OpenXR requires \"Use Gradle Build\" to be enabled"); + err += "\n"; + } + if (xr_mode_index != XR_MODE_OPENXR) { if (hand_tracking > XR_HAND_TRACKING_NONE) { valid = false; @@ -2228,20 +2328,20 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit } } - if (int(p_preset->get("custom_build/export_format")) == EXPORT_FORMAT_AAB && - !custom_build_enabled) { + if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB && + !gradle_build_enabled) { valid = false; - err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled."); + err += TTR("\"Export AAB\" is only valid when \"Use Gradle Build\" is enabled."); err += "\n"; } // Check the min sdk version. - String min_sdk_str = p_preset->get("custom_build/min_sdk"); + String min_sdk_str = p_preset->get("gradle_build/min_sdk"); int min_sdk_int = DEFAULT_MIN_SDK_VERSION; if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do. - if (!custom_build_enabled) { + if (!gradle_build_enabled) { valid = false; - err += TTR("\"Min SDK\" can only be overridden when \"Use Custom Build\" is enabled."); + err += TTR("\"Min SDK\" can only be overridden when \"Use Gradle Build\" is enabled."); err += "\n"; } if (!min_sdk_str.is_valid_int()) { @@ -2259,12 +2359,12 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit } // Check the target sdk version. - String target_sdk_str = p_preset->get("custom_build/target_sdk"); + String target_sdk_str = p_preset->get("gradle_build/target_sdk"); int target_sdk_int = DEFAULT_TARGET_SDK_VERSION; if (!target_sdk_str.is_empty()) { // Empty means no override, nothing to do. - if (!custom_build_enabled) { + if (!gradle_build_enabled) { valid = false; - err += TTR("\"Target SDK\" can only be overridden when \"Use Custom Build\" is enabled."); + err += TTR("\"Target SDK\" can only be overridden when \"Use Gradle Build\" is enabled."); err += "\n"; } if (!target_sdk_str.is_valid_int()) { @@ -2287,6 +2387,18 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit err += "\n"; } + String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); + if (current_renderer == "forward_plus") { + // Warning only, so don't override `valid`. + err += vformat(TTR("The \"%s\" renderer is designed for Desktop devices, and is not suitable for Android devices."), current_renderer); + err += "\n"; + } + if (_uses_vulkan() && min_sdk_int < VULKAN_MIN_SDK_VERSION) { + // Warning only, so don't override `valid`. + err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer); + err += "\n"; + } + r_error = err; return valid; } @@ -2372,14 +2484,21 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP } Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) { - int export_format = int(p_preset->get("custom_build/export_format")); + int export_format = int(p_preset->get("gradle_build/export_format")); String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK"; String release_keystore = p_preset->get("keystore/release"); String release_username = p_preset->get("keystore/release_user"); String release_password = p_preset->get("keystore/release_password"); - - String apksigner = get_apksigner_path(); + String target_sdk_version = p_preset->get("gradle_build/target_sdk"); + if (!target_sdk_version.is_valid_int()) { + target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); + } + String apksigner = get_apksigner_path(target_sdk_version.to_int(), true); print_verbose("Starting signing of the " + export_label + " binary using " + apksigner); + if (apksigner == "<FAILED>") { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("All 'apksigner' tools located in Android SDK 'build-tools' directory failed to execute. Please check that you have the correct version installed for your target sdk version. The resulting %s is unsigned."), export_label)); + return OK; + } if (!FileAccess::exists(apksigner)) { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' could not be found. Please check that the command is available in the Android SDK build-tools directory. The resulting %s is unsigned."), export_label)); return OK; @@ -2394,9 +2513,9 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre user = p_preset->get("keystore/debug_user"); if (keystore.is_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"); + keystore = EDITOR_GET("export/android/debug_keystore"); + password = EDITOR_GET("export/android/debug_keystore_pass"); + user = EDITOR_GET("export/android/debug_keystore_user"); } if (ep.step(vformat(TTR("Signing debug %s..."), export_label), 104)) { @@ -2429,20 +2548,27 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre args.push_back("--ks-key-alias"); args.push_back(user); args.push_back(export_path); - if (p_debug) { - // We only print verbose logs for debug builds to avoid leaking release keystore credentials. + if (OS::get_singleton()->is_stdout_verbose() && p_debug) { + // We only print verbose logs with credentials for debug builds to avoid leaking release keystore credentials. print_verbose("Signing debug binary using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); + } else { + List<String> redacted_args = List<String>(args); + redacted_args.find(keystore)->set("<REDACTED>"); + redacted_args.find("pass:" + password)->set("pass:<REDACTED>"); + redacted_args.find(user)->set("<REDACTED>"); + print_line("Signing binary using: " + String("\n") + apksigner + " " + join_list(redacted_args, String(" "))); } int retval; - output.clear(); Error err = OS::get_singleton()->execute(apksigner, args, &output, &retval, true); if (err != OK) { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start apksigner executable.")); return err; } - print_verbose(output); + // By design, apksigner does not output credentials in its output unless --verbose is used + print_line(output); if (retval) { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' returned with error #%d"), retval)); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("output: \n%s"), output)); return ERR_CANT_CREATE; } @@ -2467,6 +2593,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre print_verbose(output); if (retval) { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' verification of %s failed."), export_label)); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("output: \n%s"), output)); return ERR_CANT_CREATE; } @@ -2479,7 +2606,7 @@ void EditorExportPlatformAndroid::_clear_assets_directory() { // Clear the APK assets directory if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) { - print_verbose("Clearing APK assets directory.."); + print_verbose("Clearing APK assets directory..."); Ref<DirAccess> da_assets = DirAccess::open(APK_ASSETS_DIRECTORY); da_assets->erase_contents_recursive(); da_res->remove(APK_ASSETS_DIRECTORY); @@ -2487,7 +2614,7 @@ void EditorExportPlatformAndroid::_clear_assets_directory() { // Clear the AAB assets directory if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) { - print_verbose("Clearing AAB assets directory.."); + print_verbose("Clearing AAB assets directory..."); Ref<DirAccess> da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY); da_assets->erase_contents_recursive(); da_res->remove(AAB_ASSETS_DIRECTORY); @@ -2497,7 +2624,7 @@ void EditorExportPlatformAndroid::_clear_assets_directory() { void EditorExportPlatformAndroid::_remove_copied_libs() { print_verbose("Removing previously installed libraries..."); Error error; - String libs_json = FileAccess::get_file_as_string(GDNATIVE_LIBS_PATH, &error); + String libs_json = FileAccess::get_file_as_string(GDEXTENSION_LIBS_PATH, &error); if (error || libs_json.is_empty()) { print_verbose("No previously installed libraries found"); return; @@ -2513,22 +2640,33 @@ void EditorExportPlatformAndroid::_remove_copied_libs() { print_verbose("Removing previously installed library " + libs[i]); da->remove(libs[i]); } - da->remove(GDNATIVE_LIBS_PATH); + da->remove(GDEXTENSION_LIBS_PATH); } -String EditorExportPlatformAndroid::join_list(List<String> parts, const String &separator) const { +String EditorExportPlatformAndroid::join_list(const List<String> &p_parts, const String &p_separator) { String ret; - for (int i = 0; i < parts.size(); ++i) { + for (int i = 0; i < p_parts.size(); ++i) { if (i > 0) { - ret += separator; + ret += p_separator; } - ret += parts[i]; + ret += p_parts[i]; + } + return ret; +} + +String EditorExportPlatformAndroid::join_abis(const Vector<EditorExportPlatformAndroid::ABI> &p_parts, const String &p_separator, bool p_use_arch) { + String ret; + for (int i = 0; i < p_parts.size(); ++i) { + if (i > 0) { + ret += p_separator; + } + ret += (p_use_arch) ? p_parts[i].arch : p_parts[i].abi; } return ret; } 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("custom_build/export_format")); + int export_format = int(p_preset->get("gradle_build/export_format")); bool should_sign = p_preset->get("package/signed"); return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags); } @@ -2541,19 +2679,19 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP EditorProgress ep("export", TTR("Exporting for Android"), 105, true); - bool use_custom_build = bool(p_preset->get("custom_build/use_custom_build")); + bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build")); bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); bool apk_expansion = p_preset->get("apk_expansion/enable"); - Vector<String> enabled_abis = get_enabled_abis(p_preset); + Vector<ABI> enabled_abis = get_enabled_abis(p_preset); print_verbose("Exporting for Android..."); print_verbose("- debug build: " + bool_to_string(p_debug)); print_verbose("- export path: " + p_path); print_verbose("- export format: " + itos(export_format)); print_verbose("- sign build: " + bool_to_string(should_sign)); - print_verbose("- custom build enabled: " + bool_to_string(use_custom_build)); + print_verbose("- gradle build enabled: " + bool_to_string(use_gradle_build)); print_verbose("- apk expansion enabled: " + bool_to_string(apk_expansion)); - print_verbose("- enabled abis: " + String(",").join(enabled_abis)); + print_verbose("- enabled abis: " + join_abis(enabled_abis, ",", false)); print_verbose("- export filter: " + itos(p_preset->get_export_filter())); print_verbose("- include filter: " + p_preset->get_include_filter()); print_verbose("- exclude filter: " + p_preset->get_exclude_filter()); @@ -2591,14 +2729,14 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP return ERR_UNCONFIGURED; } - if (use_custom_build) { - print_verbose("Starting custom build.."); + if (use_gradle_build) { + print_verbose("Starting gradle build..."); //test that installed build version is alright { - print_verbose("Checking build version.."); + print_verbose("Checking build version..."); Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::READ); if (f.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); + 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(); @@ -2628,7 +2766,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP _clear_assets_directory(); _remove_copied_libs(); if (!apk_expansion) { - print_verbose("Exporting project files.."); + print_verbose("Exporting project files..."); CustomExportData user_data; user_data.assets_directory = assets_directory; user_data.debug = p_debug; @@ -2638,18 +2776,18 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP return err; } if (user_data.libs.size() > 0) { - Ref<FileAccess> fa = FileAccess::open(GDNATIVE_LIBS_PATH, FileAccess::WRITE); + Ref<FileAccess> fa = FileAccess::open(GDEXTENSION_LIBS_PATH, FileAccess::WRITE); fa->store_string(JSON::stringify(user_data.libs, "\t")); } } else { - print_verbose("Saving apk expansion file.."); + print_verbose("Saving apk expansion file..."); err = save_apk_expansion_file(p_preset, p_debug, p_path); if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not write expansion package file!")); return err; } } - print_verbose("Storing command line flags.."); + print_verbose("Storing command line flags..."); store_file_at_path(assets_directory + "/_cl_", command_line_flags); print_verbose("Updating ANDROID_HOME environment to " + sdk_path); @@ -2668,15 +2806,15 @@ 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 min_sdk_version = p_preset->get("custom_build/min_sdk"); + String min_sdk_version = p_preset->get("gradle_build/min_sdk"); if (!min_sdk_version.is_valid_int()) { min_sdk_version = itos(DEFAULT_MIN_SDK_VERSION); } - String target_sdk_version = p_preset->get("custom_build/target_sdk"); + String target_sdk_version = p_preset->get("gradle_build/target_sdk"); if (!target_sdk_version.is_valid_int()) { target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); } - String enabled_abi_string = String("|").join(enabled_abis); + String enabled_abi_string = join_abis(enabled_abis, "|", false); String sign_flag = should_sign ? "true" : "false"; String zipalign_flag = "true"; @@ -2728,9 +2866,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String debug_user = p_preset->get("keystore/debug_user"); if (debug_keystore.is_empty()) { - debug_keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); - debug_password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); - debug_user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); + debug_keystore = EDITOR_GET("export/android/debug_keystore"); + debug_password = EDITOR_GET("export/android/debug_keystore_pass"); + debug_user = EDITOR_GET("export/android/debug_keystore_user"); } if (debug_keystore.is_relative_path()) { debug_keystore = OS::get_singleton()->get_resource_dir().path_join(debug_keystore).simplify_path(); @@ -2798,11 +2936,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP return ERR_CANT_CREATE; } - print_verbose("Successfully completed Android custom build."); + print_verbose("Successfully completed Android gradle build."); return OK; } // This is the start of the Legacy build system - print_verbose("Starting legacy build system.."); + print_verbose("Starting legacy build system..."); if (p_debug) { src_apk = p_preset->get("custom_template/debug"); } else { @@ -2861,7 +2999,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); - Vector<String> invalid_abis(enabled_abis); + Vector<ABI> invalid_abis(enabled_abis); while (ret == UNZ_OK) { //get filename unz_file_info info; @@ -2901,20 +3039,22 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP _load_image_data(splash_bg_color_image, data); } - for (int i = 0; i < icon_densities_count; ++i) { - if (main_image.is_valid() && !main_image->is_empty()) { - if (file == launcher_icons[i].export_path) { - _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data); + if (file.ends_with(".png") && file.contains("mipmap")) { + for (int i = 0; i < icon_densities_count; ++i) { + if (main_image.is_valid() && !main_image->is_empty()) { + if (file == launcher_icons[i].export_path) { + _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data); + } } - } - if (foreground.is_valid() && !foreground->is_empty()) { - if (file == launcher_adaptive_icon_foregrounds[i].export_path) { - _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); + if (foreground.is_valid() && !foreground->is_empty()) { + if (file == launcher_adaptive_icon_foregrounds[i].export_path) { + _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); + } } - } - if (background.is_valid() && !background->is_empty()) { - if (file == launcher_adaptive_icon_backgrounds[i].export_path) { - _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); + if (background.is_valid() && !background->is_empty()) { + if (file == launcher_adaptive_icon_backgrounds[i].export_path) { + _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); + } } } } @@ -2922,7 +3062,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP if (file.ends_with(".so")) { bool enabled = false; for (int i = 0; i < enabled_abis.size(); ++i) { - if (file.begins_with("lib/" + enabled_abis[i] + "/")) { + if (file.begins_with("lib/" + enabled_abis[i].abi + "/")) { invalid_abis.erase(enabled_abis[i]); enabled = true; break; @@ -2964,8 +3104,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } if (!invalid_abis.is_empty()) { - String unsupported_arch = String(", ").join(invalid_abis); - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Missing libraries in the export template for the selected architectures: %s. Please build a template with all required libraries, or uncheck the missing architectures in the export preset."), unsupported_arch)); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Missing libraries in the export template for the selected architectures: %s. Please build a template with all required libraries, or uncheck the missing architectures in the export preset."), join_abis(invalid_abis, ", ", false))); CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND); } @@ -3122,8 +3261,17 @@ void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref< } EditorExportPlatformAndroid::EditorExportPlatformAndroid() { - logo = ImageTexture::create_from_image(memnew(Image(_android_logo))); - run_icon = ImageTexture::create_from_image(memnew(Image(_android_run_icon))); +#ifdef MODULE_SVG_ENABLED + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false); + logo = ImageTexture::create_from_image(img); + + img_loader.create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); +#endif devices_changed.set(); plugins_changed.set(); |