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