diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index b9840154e08..2c99a3c5344 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -61,6 +61,11 @@ Application export format (*.apk or *.aab). + + If [code]true[/code], configures the exported project for Play Instant Build. + Use this option when targeting Play Instant to allow users to launch the app without installation. See [url=https://developer.android.com/topic/google-play-instant/overview]Google Play Instant[/url]. + [b]Note:[/b] Instant play games also need to be optimized for size. See [url=https://docs.godotengine.org/en/stable/contributing/development/compiling/optimizing_for_size.html]Optimizing a build for size[/url]. + Path to the Gradle build directory. If left empty, then [code]res://android[/code] will be used. diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 9fc4cab9cfc..090b31e1290 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -280,6 +280,7 @@ static const int EXPORT_FORMAT_AAB = 1; static const char *APK_ASSETS_DIRECTORY = "assets"; static const char *AAB_ASSETS_DIRECTORY = "assetPackInstallTime/src/main/assets"; +static const char *INSTANT_APP_ASSETS_DIRECTORY = "assets"; // instant build doesn't support installTime assetspacks, so using the same directory as APK static const int DEFAULT_MIN_SDK_VERSION = 24; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' static const int DEFAULT_TARGET_SDK_VERSION = 35; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' @@ -524,6 +525,12 @@ String EditorExportPlatformAndroid::get_valid_basename(const Ref &p_preset, int p_export_format) const { String gradle_build_directory = ExportTemplateManager::get_android_build_directory(p_preset); + + bool google_play_instant_build = p_preset->get("gradle_build/google_play_instant"); + if (google_play_instant_build) { + return gradle_build_directory.path_join(INSTANT_APP_ASSETS_DIRECTORY); // Always use base APK asset format + } + return gradle_build_directory.path_join(p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY); } @@ -994,10 +1001,19 @@ void EditorExportPlatformAndroid::_get_manifest_info(const Ref &p_preset, bool p_give_internet, bool p_debug) { print_verbose("Building temporary manifest..."); + + bool google_play_instant_build = (bool)p_preset->get("gradle_build/google_play_instant"); + String manifest_text = "\n" "\n"; + " xmlns:tools=\"http://schemas.android.com/tools\""; + + if (google_play_instant_build) { + manifest_text += " android:targetSandboxVersion=\"2\" \n xmlns:dist=\"http://schemas.android.com/apk/distribution\""; + } + + manifest_text += ">\n"; manifest_text += _get_screen_sizes_tag(p_preset); manifest_text += _get_gles_tag(); @@ -1031,6 +1047,7 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref(this), p_preset, _has_read_write_storage_permission(perms), p_debug, manifest_metadata); + manifest_text += "\n"; String manifest_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join(vformat("src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"))); @@ -2008,6 +2025,12 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB && !gradle_build_enabled) { return TTR("\"Export AAB\" is only valid when \"Use Gradle Build\" is enabled."); } + } else if (p_name == "gradle_build/google_play_instant") { + bool instant_enabled = p_preset->get("gradle_build/google_play_instant"); + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (instant_enabled && !gradle_build_enabled) { + return TTR("\"Instant Build\" is only valid when \"Use Gradle Build\" is enabled."); + } } else if (p_name == "gradle_build/min_sdk") { String min_sdk_str = p_preset->get("gradle_build/min_sdk"); bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); @@ -2087,6 +2110,7 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/gradle_build_directory", PROPERTY_HINT_PLACEHOLDER_TEXT, "res://android"), "", false, false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/android_source_template", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK, false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/google_play_instant"), false, true, true)); // Using String instead of int to default to an empty string (no override) with placeholder for instructions (see GH-62465). // This implies doing validation that the string is a proper int. r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_MIN_SDK_VERSION)), "", false, true)); @@ -3564,6 +3588,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget("gradle_build/google_play_instant")); Vector android_libraries; Vector android_dependencies; @@ -3638,6 +3663,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget("gradle_build/google_play_instant"); + String manifest_application_text = vformat( " &p_export_platform, } manifest_application_text += " tools:ignore=\"GoogleAppIndexingWarning\">\n\n"; + if (google_play_instant_build) { + manifest_application_text += " \n"; + } + for (int i = 0; i < p_metadata.size(); i++) { manifest_application_text += vformat(" \n", p_metadata[i].name, p_metadata[i].value); } diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index bb5c16b2fa8..f479c43a649 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -37,6 +37,10 @@ dependencies { implementation "androidx.fragment:fragment:$versions.fragmentVersion" implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" + if (isInstantApp()) { + implementation "com.google.android.gms:play-services-instantapps:$versions.instantAppsVersion" + } + if (rootProject.findProject(":lib")) { implementation project(":lib") } else if (rootProject.findProject(":godot:lib")) { @@ -90,7 +94,7 @@ android { jvmTarget = versions.javaVersion } - assetPacks = [":assetPackInstallTime"] + assetPacks = isInstantApp() ? [] : [":assetPackInstallTime"] namespace = 'com.godot.game' diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index bcb362e1db7..acee899f2a1 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -15,7 +15,8 @@ ext.versions = [ // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. ndkVersion : '28.1.13356709', splashscreenVersion: '1.0.1', - openxrVendorsVersion: '4.0.0-stable' + openxrVendorsVersion: '4.0.0-stable', + instantAppsVersion: '17.0.0' ] @@ -379,3 +380,8 @@ ext.getAddonsDirectory = { -> String addonsDirectory = project.hasProperty("addons_directory") ? project.property("addons_directory") : "" return addonsDirectory } + +ext.isInstantApp = { -> + String instantApp = project.hasProperty("play_instant_app") ? project.property("play_instant_app") : "" + return Boolean.parseBoolean(instantApp) +}