diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index ec3b8076867..31de83fe708 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -632,6 +632,22 @@ If [code]true[/code], shaders will be compiled and embedded in the application. This option is only supported when using the Forward+ or Mobile renderers. [b]Note:[/b] When exporting as a dedicated server, the shader baker is always disabled since no rendering is performed. + + The background color used for the system splash screen window. + If not set, it will fallback to [member EditorExportPlatformAndroid.launcher_icons/adaptive_background_432x432]. + [b]Note:[/b] This is only applied if [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] is enabled. + + + System splash screen branding image file. If left empty, no branding image will be used. See [url=https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions]splash-screen dimensions[/url]. + [b]Note:[/b] Can be used to set an image to be shown at the bottom of the splash screen. + + + If [code]true[/code], Godot's boot splash will not be shown, and the system boot splash will remain visible for a longer time, until the mainloop starts. + + + System splash screen icon file. If left empty, it will fall back to [member EditorExportPlatformAndroid.launcher_icons/adaptive_foreground_432x432]. See [url=https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions]splash-screen dimensions[/url]. + [b]Note:[/b] You can provide an [url=https://developer.android.com/reference/android/graphics/drawable/AnimatedVectorDrawable]AnimatedVectorDrawable (AVD)[/url] XML. However, the XML file will only be used if [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] is enabled. If not, it will fall back to [member EditorExportPlatformAndroid.launcher_icons/adaptive_background_432x432]. + If [code]true[/code], allows the application to participate in the backup and restore infrastructure. diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 29cf1f08a23..1aa4a082b35 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -244,6 +244,14 @@ static const String ICON_XML_TEMPLATE = static const String ICON_XML_PATH = "res/mipmap-anydpi-v26/icon.xml"; static const String THEMED_ICON_XML_PATH = "res/mipmap-anydpi-v26/themed_icon.xml"; +static const String ANDROID_SPLASH_ICON_PATH = "res/drawable/splash_icon.webp"; +static const String ANDROID_SPLASH_BRANDING_IMAGE_PATH = "res/drawable/splash_branding_image.webp"; + +static const char *DISABLE_GODOT_SPLASH_OPTION = PNAME("splash_screen/disable_godot_boot_splash"); +static const char *ANDROID_SPLASH_ICON_OPTION = PNAME("splash_screen/icon"); +static const char *ANDROID_SPLASH_BACKGROUND_COLOR_OPTION = PNAME("splash_screen/background_color"); +static const char *ANDROID_SPLASH_BRANDING_IMAGE_OPTION = PNAME("splash_screen/branding_image"); + static const int ICON_DENSITIES_COUNT = 6; static const char *LAUNCHER_ICON_OPTION = PNAME("launcher_icons/main_192x192"); static const char *LAUNCHER_ADAPTIVE_ICON_FOREGROUND_OPTION = PNAME("launcher_icons/adaptive_foreground_432x432"); @@ -1084,7 +1092,8 @@ bool EditorExportPlatformAndroid::_is_transparency_allowed(const Ref &p_preset) { - const String themes_xml_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join("res/values/themes.xml"); + String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + const String themes_xml_path = gradle_build_dir.path_join("res/values/themes.xml"); if (!FileAccess::exists(themes_xml_path)) { print_error("res/values/themes.xml does not exist."); @@ -1104,8 +1113,24 @@ void EditorExportPlatformAndroid::_fix_themes_xml(const Ref } Dictionary splash_theme_attributes; - splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background"; - splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@mipmap/icon_foreground"; + + Color color = p_preset->get(ANDROID_SPLASH_BACKGROUND_COLOR_OPTION); + if (color == Color()) { + splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background"; + } else { + splash_theme_attributes["android:windowSplashScreenBackground"] = "#" + color.to_html(false); + } + splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@drawable/splash_icon"; + String splash_icon_path = p_preset->get(ANDROID_SPLASH_ICON_OPTION); + if (!splash_icon_path.is_empty() && splash_icon_path.get_extension() == "xml") { + Vector data = FileAccess::get_file_as_bytes(splash_icon_path); + store_file_at_path(gradle_build_dir.path_join("res/drawable/splash_icon_vector.xml"), data); + splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@drawable/splash_icon_vector"; + } + String splash_branding_image_path = p_preset->get(ANDROID_SPLASH_BRANDING_IMAGE_OPTION); + if (!splash_branding_image_path.is_empty()) { + splash_theme_attributes["android:windowSplashScreenBrandingImage"] = "@drawable/splash_branding_image"; + } splash_theme_attributes["postSplashScreenTheme"] = "@style/GodotAppMainTheme"; splash_theme_attributes["android:windowIsTranslucent"] = bool_to_string(transparency_allowed); @@ -1897,7 +1922,7 @@ void EditorExportPlatformAndroid::_process_launcher_icons(const String &p_file_n memcpy(p_data.ptrw(), buffer.ptr(), p_data.size()); } -void EditorExportPlatformAndroid::load_icon_refs(const Ref &p_preset, Ref &icon, Ref &foreground, Ref &background, Ref &monochrome) { +void EditorExportPlatformAndroid::load_icon_refs(const Ref &p_preset, Ref &icon, Ref &foreground, Ref &background, Ref &monochrome, Ref &splash_icon, Ref &splash_branding_image) { String project_icon_path = get_project_setting(p_preset, "application/config/icon"); Error err = OK; @@ -1941,15 +1966,56 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref & print_verbose("Loading adaptive monochrome icon from " + path); monochrome = _load_icon_or_splash_image(path, &err); } + + // Splash icon: user selection -> adaptive foreground icon (user selection -> regular icon). + path = static_cast(p_preset->get(ANDROID_SPLASH_ICON_OPTION)).strip_edges(); + if (path.get_extension() != "xml") { + // XML file is handled in _fix_themes_xml(). + print_verbose("Loading splash screen icon from " + path); + bool loaded_ok = false; + if (!path.is_empty()) { + splash_icon = _load_icon_or_splash_image(path, &err); + loaded_ok = (err == OK && splash_icon.is_valid() && !splash_icon->is_empty()); + } + if (!loaded_ok) { + print_verbose("- falling back to using the adaptive foreground icon"); + splash_icon = foreground; + } + } + + // Splash branding image: user selection -> No image. + // - Gradle build: If the path is empty, the splash theme attribute is not added in _fix_themes_xml(). + // - Legacy build: If the path is empty, a transparent image is used. + path = static_cast(p_preset->get(ANDROID_SPLASH_BRANDING_IMAGE_OPTION)).strip_edges(); + if (!path.is_empty()) { + print_verbose("Loading splash screen branding image from " + path); + splash_branding_image = _load_icon_or_splash_image(path, &err); + } } void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref &p_preset, const Ref &p_main_image, const Ref &p_foreground, const Ref &p_background, - const Ref &p_monochrome) { + const Ref &p_monochrome, + const Ref &p_splash_icon, + const Ref &p_splash_branding_image) { String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + // Copy splash screen icon to the drawable directory. + // This is only for png/webp/svg file; XML file is handled in _fix_themes_xml(). + if (p_splash_icon.is_valid() && !p_splash_icon->is_empty()) { + print_verbose("Copying splash screen icon into " + ANDROID_SPLASH_ICON_PATH); + Vector buffer = p_splash_icon->save_webp_to_buffer(); + store_file_at_path(gradle_build_dir.path_join(ANDROID_SPLASH_ICON_PATH), buffer); + } + + if (p_splash_branding_image.is_valid() && !p_splash_branding_image->is_empty()) { + print_verbose("Copying splash screen branding image into " + ANDROID_SPLASH_BRANDING_IMAGE_PATH); + Vector buffer = p_splash_branding_image->save_webp_to_buffer(); + store_file_at_path(gradle_build_dir.path_join(ANDROID_SPLASH_BRANDING_IMAGE_PATH), buffer); + } + String monochrome_tag = ""; // Prepare images to be resized for the icons. If some image ends up being uninitialized, @@ -2211,6 +2277,11 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "screen/background_color", PROPERTY_HINT_COLOR_NO_ALPHA), Color())); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, DISABLE_GODOT_SPLASH_OPTION), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, ANDROID_SPLASH_ICON_OPTION, PROPERTY_HINT_FILE, "*.png,*.webp,*.svg,*.xml"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, ANDROID_SPLASH_BRANDING_IMAGE_OPTION, PROPERTY_HINT_FILE, "*.png,*.webp,*.svg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, ANDROID_SPLASH_BACKGROUND_COLOR_OPTION, PROPERTY_HINT_COLOR_NO_ALPHA), Color())); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data_backup/allow"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args", PROPERTY_HINT_NONE, "monospace"), "")); @@ -2248,7 +2319,11 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor p_option == "gesture/swipe_to_dismiss" || p_option == "apk_expansion/enable" || p_option == "apk_expansion/SALT" || - p_option == "apk_expansion/public_key") { + p_option == "apk_expansion/public_key" || + p_option == DISABLE_GODOT_SPLASH_OPTION || + p_option == ANDROID_SPLASH_ICON_OPTION || + p_option == ANDROID_SPLASH_BACKGROUND_COLOR_OPTION || + p_option == ANDROID_SPLASH_BRANDING_IMAGE_OPTION) { return advanced_options_enabled; } if (p_option == "gradle_build/gradle_build_directory" || p_option == "gradle_build/android_source_template") { @@ -3244,6 +3319,11 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Refget(DISABLE_GODOT_SPLASH_OPTION); + if (disable_godot_splash) { + command_line_strings.push_back("--disable_godot_splash"); + } + bool debug_opengl = p_preset->get("graphics/opengl_debug"); if (debug_opengl) { command_line_strings.push_back("--debug_opengl"); @@ -3692,8 +3772,10 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref foreground; Ref background; Ref monochrome; + Ref splash_icon; + Ref splash_branding_image; - load_icon_refs(p_preset, main_image, foreground, background, monochrome); + load_icon_refs(p_preset, main_image, foreground, background, monochrome, splash_icon, splash_branding_image); Vector command_line_flags; // Write command line flags into the command_line_flags variable. @@ -3766,7 +3848,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refis_empty()) { + Vector buffer = splash_icon->save_webp_to_buffer(); + data.resize(buffer.size()); + memcpy(data.ptrw(), buffer.ptr(), data.size()); + } + } + + if (file == ANDROID_SPLASH_BRANDING_IMAGE_PATH) { + if (splash_branding_image.is_valid() && !splash_branding_image->is_empty()) { + Vector buffer = splash_branding_image->save_webp_to_buffer(); + data.resize(buffer.size()); + memcpy(data.ptrw(), buffer.ptr(), data.size()); + } + } + if (file == THEMED_ICON_XML_PATH) { // Store themed_icon.xml data. themed_icon_xml_data = data; diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 2f08e89fc80..2b40e251c2e 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -187,13 +187,15 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { void _process_launcher_icons(const String &p_file_name, const Ref &p_source_image, int dimension, Vector &p_data); - void load_icon_refs(const Ref &p_preset, Ref &icon, Ref &foreground, Ref &background, Ref &monochrome); + void load_icon_refs(const Ref &p_preset, Ref &icon, Ref &foreground, Ref &background, Ref &monochrome, Ref &splash_icon, Ref &splash_branding_image); void _copy_icons_to_gradle_project(const Ref &p_preset, const Ref &p_main_image, const Ref &p_foreground, const Ref &p_background, - const Ref &p_monochrome); + const Ref &p_monochrome, + const Ref &p_splash_icon, + const Ref &p_splash_branding_image); static void _create_editor_debug_keystore_if_needed(); diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml index af2dd509cf6..6125707c442 100644 --- a/platform/android/java/app/res/values/themes.xml +++ b/platform/android/java/app/res/values/themes.xml @@ -11,7 +11,8 @@ To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. --> diff --git a/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java b/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java index 8573f713269..4406bf0b5b4 100644 --- a/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java @@ -67,9 +67,14 @@ public class GodotApp extends GodotActivity { @Override public void onCreate(Bundle savedInstanceState) { - SplashScreen.installSplashScreen(this); + SplashScreen splashScreen = SplashScreen.installSplashScreen(this); EdgeToEdge.enable(this); super.onCreate(savedInstanceState); + + Godot godot = getGodot(); + if (godot != null && godot.getDisableGodotSplash()) { + splashScreen.setKeepOnScreenCondition(() -> godot.getRunStatus() != Godot.RunStatus.STARTED); + } } @Override diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt index e6a3ea342f9..86261b02159 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt @@ -204,6 +204,8 @@ class Godot private constructor(val context: Context) { internal var darkMode = false private var backgroundColor: Int = Color.BLACK private var orientation = Configuration.ORIENTATION_UNDEFINED + var disableGodotSplash = false + private set internal var containerLayout: FrameLayout? = null var renderView: GodotRenderView? = null @@ -276,6 +278,8 @@ class Godot private constructor(val context: Context) { newArgs.add(commandLine[i]) } else if (commandLine[i] == "--background_color") { setWindowColor(commandLine[i + 1]) + } else if (commandLine[i] == "--disable_godot_splash") { + disableGodotSplash = true } else if (commandLine[i] == "--use_apk_expansion") { useApkExpansion = true } else if (hasExtra && commandLine[i] == "--apk_expansion_md5") { diff --git a/platform/android/java/lib/src/main/res/drawable/splash_branding_image.webp b/platform/android/java/lib/src/main/res/drawable/splash_branding_image.webp new file mode 100644 index 00000000000..bfd4f7cb0a6 Binary files /dev/null and b/platform/android/java/lib/src/main/res/drawable/splash_branding_image.webp differ diff --git a/platform/android/java/lib/src/main/res/drawable/splash_icon.webp b/platform/android/java/lib/src/main/res/drawable/splash_icon.webp new file mode 100644 index 00000000000..1403678b074 Binary files /dev/null and b/platform/android/java/lib/src/main/res/drawable/splash_icon.webp differ