Merge commit '1843c454bd' as 'engine'

This commit is contained in:
Sara Gerretsen 2026-04-28 14:58:19 +02:00
commit e7cc4cd72f
13965 changed files with 7502032 additions and 0 deletions

View file

@ -0,0 +1,10 @@
plugins {
id 'com.android.asset-pack'
}
assetPack {
packName = "assetPackInstallTime" // Directory name for the asset pack
dynamicDelivery {
deliveryType = "install-time" // Delivery mode
}
}

View file

@ -0,0 +1,337 @@
// Gradle build config for Godot Engine's Android port.
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
apply from: 'config.gradle'
allprojects {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
maven { url "https://central.sonatype.com/repository/maven-snapshots/"}
// Godot user plugins custom maven repos
String[] mavenRepos = getGodotPluginsMavenRepos()
if (mavenRepos != null && mavenRepos.size() > 0) {
for (String repoUrl : mavenRepos) {
maven {
url repoUrl
}
}
}
}
}
configurations {
// Initializes a placeholder for the monoImplementation dependency configuration.
monoImplementation {}
}
dependencies {
// Android instrumented test dependencies
androidTestImplementation "androidx.test.ext:junit:$versions.junitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCoreVersion"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$versions.kotlinTestVersion"
androidTestImplementation "androidx.test:runner:$versions.testRunnerVersion"
androidTestUtil "androidx.test:orchestrator:$versions.testOrchestratorVersion"
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
implementation "androidx.documentfile:documentfile:$versions.documentfileVersion"
if (rootProject.findProject(":lib")) {
implementation project(":lib")
} else if (rootProject.findProject(":godot:lib")) {
implementation project(":godot:lib")
} else {
// Godot gradle build mode. In this scenario this project is the only one around and the Godot
// library is available through the pre-generated godot-lib.*.aar android archive files.
debugImplementation fileTree(dir: 'libs/debug', include: ['**/*.jar', '*.aar'])
releaseImplementation fileTree(dir: 'libs/release', include: ['**/*.jar', '*.aar'])
}
// Godot user plugins remote dependencies
String[] remoteDeps = getGodotPluginsRemoteBinaries()
if (remoteDeps != null && remoteDeps.size() > 0) {
def platformPattern = /^\s*(platform|enforcedPlatform)\s*\(\s*['"]*(\S+)['"]*\s*\)$/
for (String dep : remoteDeps) {
def matcher = dep =~ platformPattern
if (matcher) {
switch (matcher[0][1]) {
case "platform":
implementation platform(matcher[0][2])
break
case "enforcedPlatform":
implementation enforcedPlatform(matcher[0][2])
break
default:
throw new GradleException("Invalid remote platform dependency: $dep")
break
}
} else {
implementation dep
}
}
}
// Godot user plugins local dependencies
String[] pluginsBinaries = getGodotPluginsLocalBinaries()
if (pluginsBinaries != null && pluginsBinaries.size() > 0) {
implementation files(pluginsBinaries)
}
// Automatically pick up local dependencies in res://addons
String addonsDirectory = getAddonsDirectory()
if (addonsDirectory != null && !addonsDirectory.isBlank()) {
implementation fileTree(dir: "$addonsDirectory", include: ['*.jar', '*.aar'])
}
// .NET dependencies
String jar = '../../../../modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar'
if (file(jar).exists()) {
monoImplementation files(jar)
}
}
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
ndkVersion versions.ndkVersion
compileOptions {
sourceCompatibility versions.javaVersion
targetCompatibility versions.javaVersion
}
kotlinOptions {
jvmTarget = versions.javaVersion
}
assetPacks = [":assetPackInstallTime"]
namespace = 'com.godot.game'
defaultConfig {
// The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
aaptOptions {
ignoreAssetsPattern "!.svn:!.git:!.gitignore:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}
ndk {
debugSymbolLevel 'NONE'
String[] export_abi_list = getExportEnabledABIs()
abiFilters export_abi_list
}
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
versionCode getExportVersionCode()
versionName getExportVersionName()
minSdkVersion getExportMinSdkVersion()
targetSdkVersion getExportTargetSdkVersion()
missingDimensionStrategy 'products', 'template'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// The following argument makes the Android Test Orchestrator run its
// "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
}
lintOptions {
abortOnError false
disable 'MissingTranslation', 'UnusedResources'
}
ndkVersion versions.ndkVersion
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
// Debug symbols are kept for development within Android Studio.
if (shouldNotStrip()) {
jniLibs {
keepDebugSymbols += '**/*.so'
}
}
jniLibs {
// Setting this to true causes AGP to package compressed native libraries when building the app
// For more background, see:
// - https://developer.android.com/build/releases/past-releases/agp-3-6-0-release-notes#extractNativeLibs
// - https://stackoverflow.com/a/44704840
useLegacyPackaging shouldUseLegacyPackaging()
}
// Always select Godot's version of libc++_shared.so in case deps have their own
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
}
signingConfigs {
debug {
if (hasCustomDebugKeystore()) {
storeFile new File(getDebugKeystoreFile())
storePassword getDebugKeystorePassword()
keyAlias getDebugKeyAlias()
keyPassword getDebugKeystorePassword()
}
}
release {
File keystoreFile = new File(getReleaseKeystoreFile())
if (keystoreFile.isFile()) {
storeFile keystoreFile
storePassword getReleaseKeystorePassword()
keyAlias getReleaseKeyAlias()
keyPassword getReleaseKeystorePassword()
}
}
}
buildFeatures {
buildConfig = true
}
buildTypes {
debug {
// Signing and zip-aligning are skipped for prebuilt builds, but
// performed for Godot gradle builds.
zipAlignEnabled shouldZipAlign()
if (shouldSign()) {
signingConfig signingConfigs.debug
} else {
signingConfig null
}
}
release {
// Signing and zip-aligning are skipped for prebuilt builds, but
// performed for Godot gradle builds.
zipAlignEnabled shouldZipAlign()
if (shouldSign()) {
signingConfig signingConfigs.release
} else {
signingConfig null
}
}
}
flavorDimensions 'edition'
productFlavors {
// Product flavor for the standard (no .net support) builds.
standard {
getIsDefault().set(true)
}
// Product flavor for the Mono (.net) builds.
mono {}
// Product flavor used for running instrumented tests.
instrumented {
applicationIdSuffix ".instrumented"
versionNameSuffix "-instrumented"
}
}
sourceSets {
main.res.srcDirs += ['res']
debug.jniLibs.srcDirs = ['libs/debug', 'libs/debug/vulkan_validation_layers']
release.jniLibs.srcDirs = ['libs/release']
}
applicationVariants.all { variant ->
variant.outputs.all { output ->
String filenameSuffix = variant.flavorName == "mono" ? variant.name : variant.buildType.name
output.outputFileName = "android_${filenameSuffix}.apk"
}
}
}
task copyAndRenameBinary(type: Copy) {
// The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
// and directories. Otherwise this check may cause permissions access failures on Windows
// machines.
doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
String exportPath = getExportPath()
String exportFilename = getExportFilename()
String exportEdition = getExportEdition()
String exportBuildType = getExportBuildType()
String exportBuildTypeCapitalized = exportBuildType.capitalize()
String exportFormat = getExportFormat()
boolean isAab = exportFormat == "aab"
boolean isMono = exportEdition == "mono"
String filenameSuffix = isAab ? "${exportEdition}-${exportBuildType}" : exportBuildType
if (isMono) {
filenameSuffix = isAab ? "${exportEdition}-${exportBuildType}" : "${exportEdition}${exportBuildTypeCapitalized}"
}
String sourceFilename = isAab ? "${project.name}-${filenameSuffix}.aab" : "android_${filenameSuffix}.apk"
String sourceFilepath = isAab ? "$buildDir/outputs/bundle/${exportEdition}${exportBuildTypeCapitalized}/$sourceFilename" : "$buildDir/outputs/apk/$exportEdition/$exportBuildType/$sourceFilename"
from sourceFilepath
into exportPath
rename sourceFilename, exportFilename
}
/**
* Used to validate the version of the Java SDK used for the Godot gradle builds.
*/
task validateJavaVersion {
if (!JavaVersion.current().isCompatibleWith(versions.javaVersion)) {
throw new GradleException("Invalid Java version ${JavaVersion.current()}. Version ${versions.javaVersion} is the minimum supported Java version for Godot gradle builds.")
}
}
/*
* Older versions of our vendor plugin include a loader that we no longer need.
* This code ensures those are removed.
*/
tasks.withType( com.android.build.gradle.internal.tasks.MergeNativeLibsTask) {
doFirst {
externalLibNativeLibs.each { jniDir ->
if (jniDir.getCanonicalPath().contains("godot-openxr-") || jniDir.getCanonicalPath().contains("godotopenxr")) {
// Delete the 'libopenxr_loader.so' files from the vendors plugin so we only use the version from the
// openxr loader dependency.
File armFile = new File(jniDir, "arm64-v8a/libopenxr_loader.so")
if (armFile.exists()) {
armFile.delete()
}
File x86File = new File(jniDir, "x86_64/libopenxr_loader.so")
if (x86File.exists()) {
x86File.delete()
}
}
}
}
}
/*
When they're scheduled to run, the copy*AARToAppModule tasks generate dependencies for the 'app'
module, so we're ensuring the ':app:preBuild' task is set to run after those tasks.
*/
if (rootProject.tasks.findByPath("copyDebugAARToAppModule") != null) {
preBuild.mustRunAfter(rootProject.tasks.named("copyDebugAARToAppModule"))
}
if (rootProject.tasks.findByPath("copyReleaseAARToAppModule") != null) {
preBuild.mustRunAfter(rootProject.tasks.named("copyReleaseAARToAppModule"))
}

View file

@ -0,0 +1,411 @@
ext.versions = [
androidGradlePlugin: '8.6.1',
compileSdk : 36,
// Also update:
// - 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
// - 'platform/android/detect.py#get_min_target_api()'
minSdk : 24,
// Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
targetSdk : 36,
buildTools : '36.1.0',
kotlinVersion : '2.1.21',
fragmentVersion : '1.8.6',
nexusPublishVersion: '1.3.0',
javaVersion : JavaVersion.VERSION_17,
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
ndkVersion : '29.0.14206865',
splashscreenVersion: '1.0.1',
// 'openxrLoaderVersion' should be set to XR_CURRENT_API_VERSION, see 'thirdparty/openxr'
openxrLoaderVersion: '1.1.53',
openxrVendorsVersion: '4.3.0-stable',
junitVersion : '1.3.0',
espressoCoreVersion: '3.7.0',
kotlinTestVersion : '1.3.11',
testRunnerVersion : '1.7.0',
testOrchestratorVersion: '1.6.1',
documentfileVersion: '1.1.0',
]
ext.getExportPackageName = { ->
// Retrieve the app id from the project property set by the Godot build command.
String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : ""
// Check if the app id is valid, otherwise use the default.
if (appId == null || appId.isEmpty()) {
appId = "com.godot.game"
}
return appId
}
ext.getExportVersionCode = { ->
String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : ""
if (versionCode == null || versionCode.isEmpty()) {
versionCode = "1"
}
try {
return Integer.parseInt(versionCode)
} catch (NumberFormatException ignored) {
return 1
}
}
ext.getExportVersionName = { ->
String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : ""
if (versionName == null || versionName.isEmpty()) {
versionName = "1.0"
}
return versionName
}
ext.getExportMinSdkVersion = { ->
String minSdkVersion = project.hasProperty("export_version_min_sdk") ? project.property("export_version_min_sdk") : ""
if (minSdkVersion == null || minSdkVersion.isEmpty()) {
minSdkVersion = "$versions.minSdk"
}
try {
return Integer.parseInt(minSdkVersion)
} catch (NumberFormatException ignored) {
return versions.minSdk
}
}
ext.getExportTargetSdkVersion = { ->
String targetSdkVersion = project.hasProperty("export_version_target_sdk") ? project.property("export_version_target_sdk") : ""
if (targetSdkVersion == null || targetSdkVersion.isEmpty()) {
targetSdkVersion = "$versions.targetSdk"
}
try {
return Integer.parseInt(targetSdkVersion)
} catch (NumberFormatException ignored) {
return versions.targetSdk
}
}
ext.getGodotLibraryVersionCode = { ->
String versionName = ""
int versionCode = 1
(versionName, versionCode) = getGodotLibraryVersion()
return versionCode
}
ext.getGodotLibraryVersionName = { ->
String versionName = ""
int versionCode = 1
(versionName, versionCode) = getGodotLibraryVersion()
return versionName
}
ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
// Attempt to read the version from the `version.py` file.
String libraryVersionName = ""
int libraryVersionCode = 0
File versionFile = new File("../../../version.py")
if (versionFile.isFile()) {
def map = [:]
List<String> lines = versionFile.readLines()
for (String line in lines) {
String[] keyValue = line.split("=")
String key = keyValue[0].trim()
String value = keyValue[1].trim().replaceAll("\"", "")
if (requiredKeys.contains(key)) {
if (!value.isEmpty()) {
map[key] = value
}
requiredKeys.remove(key)
}
}
if (requiredKeys.empty) {
libraryVersionName = map.values().join(".")
try {
if (map.containsKey("status")) {
int statusCode = 0
String statusValue = map["status"]
if (statusValue == null) {
statusCode = 0
} else if (statusValue.startsWith("dev")) {
statusCode = 1
} else if (statusValue.startsWith("alpha")) {
statusCode = 2
} else if (statusValue.startsWith("beta")) {
statusCode = 3
} else if (statusValue.startsWith("rc")) {
statusCode = 4
} else if (statusValue.startsWith("stable")) {
statusCode = 5
} else {
statusCode = 0
}
libraryVersionCode = statusCode
}
if (map.containsKey("patch")) {
libraryVersionCode += Integer.parseInt(map["patch"]) * 10
}
if (map.containsKey("minor")) {
libraryVersionCode += (Integer.parseInt(map["minor"]) * 1000)
}
if (map.containsKey("major")) {
libraryVersionCode += (Integer.parseInt(map["major"]) * 100000)
}
} catch (NumberFormatException ignore) {
libraryVersionCode = 1
}
}
}
if (libraryVersionName.isEmpty()) {
// Fallback value in case we're unable to read the file.
libraryVersionName = "custom_build"
}
if (libraryVersionCode == 0) {
libraryVersionCode = 1
}
return [libraryVersionName, libraryVersionCode]
}
ext.getGodotLibraryVersion = { ->
List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"]
return generateGodotLibraryVersion(requiredKeys)
}
ext.getGodotPublishVersion = { ->
List<String> requiredKeys = ["major", "minor", "patch", "status"]
String versionName = ""
int versionCode = 1
(versionName, versionCode) = generateGodotLibraryVersion(requiredKeys)
if (!versionName.endsWith("stable")) {
versionName += "-SNAPSHOT"
}
return versionName
}
final String VALUE_SEPARATOR_REGEX = "\\|"
// get the list of ABIs the project should be exported to
ext.getExportEnabledABIs = { ->
String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : ""
if (enabledABIs == null || enabledABIs.isEmpty()) {
enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
}
Set<String> exportAbiFilter = []
for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) {
if (!abi_name.trim().isEmpty()){
exportAbiFilter.add(abi_name)
}
}
return exportAbiFilter
}
ext.getExportPath = {
String exportPath = project.hasProperty("export_path") ? project.property("export_path") : ""
if (exportPath == null || exportPath.isEmpty()) {
exportPath = "."
}
return exportPath
}
ext.getExportFilename = {
String exportFilename = project.hasProperty("export_filename") ? project.property("export_filename") : ""
if (exportFilename == null || exportFilename.isEmpty()) {
exportFilename = "godot_android"
}
return exportFilename
}
ext.getExportEdition = {
String exportEdition = project.hasProperty("export_edition") ? project.property("export_edition") : ""
if (exportEdition == null || exportEdition.isEmpty()) {
exportEdition = "standard"
}
return exportEdition
}
ext.getExportBuildType = {
String exportBuildType = project.hasProperty("export_build_type") ? project.property("export_build_type") : ""
if (exportBuildType == null || exportBuildType.isEmpty()) {
exportBuildType = "debug"
}
return exportBuildType
}
ext.getExportFormat = {
String exportFormat = project.hasProperty("export_format") ? project.property("export_format") : ""
if (exportFormat == null || exportFormat.isEmpty()) {
exportFormat = "apk"
}
return exportFormat
}
/**
* Parse the project properties for the 'plugins_maven_repos' property and return the list
* of maven repos.
*/
ext.getGodotPluginsMavenRepos = { ->
Set<String> mavenRepos = []
// Retrieve the list of maven repos.
if (project.hasProperty("plugins_maven_repos")) {
String mavenReposProperty = project.property("plugins_maven_repos")
if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) {
for (String mavenRepoUrl : mavenReposProperty.split(VALUE_SEPARATOR_REGEX)) {
mavenRepos += mavenRepoUrl.trim()
}
}
}
return mavenRepos
}
/**
* Parse the project properties for the 'plugins_remote_binaries' property and return
* it for inclusion in the build dependencies.
*/
ext.getGodotPluginsRemoteBinaries = { ->
Set<String> remoteDeps = []
// Retrieve the list of remote plugins binaries.
if (project.hasProperty("plugins_remote_binaries")) {
String remoteDepsList = project.property("plugins_remote_binaries")
if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) {
for (String dep: remoteDepsList.split(VALUE_SEPARATOR_REGEX)) {
remoteDeps += dep.trim()
}
}
}
return remoteDeps
}
/**
* Parse the project properties for the 'plugins_local_binaries' property and return
* their binaries for inclusion in the build dependencies.
*/
ext.getGodotPluginsLocalBinaries = { ->
Set<String> binDeps = []
// Retrieve the list of local plugins binaries.
if (project.hasProperty("plugins_local_binaries")) {
String pluginsList = project.property("plugins_local_binaries")
if (pluginsList != null && !pluginsList.trim().isEmpty()) {
for (String plugin : pluginsList.split(VALUE_SEPARATOR_REGEX)) {
binDeps += plugin.trim()
}
}
}
return binDeps
}
ext.getDebugKeystoreFile = { ->
String keystoreFile = project.hasProperty("debug_keystore_file") ? project.property("debug_keystore_file") : ""
if (keystoreFile == null || keystoreFile.isEmpty()) {
keystoreFile = "."
}
return keystoreFile
}
ext.hasCustomDebugKeystore = { ->
File keystoreFile = new File(getDebugKeystoreFile())
return keystoreFile.isFile()
}
ext.getDebugKeystorePassword = { ->
String keystorePassword = project.hasProperty("debug_keystore_password") ? project.property("debug_keystore_password") : ""
if (keystorePassword == null || keystorePassword.isEmpty()) {
keystorePassword = "android"
}
return keystorePassword
}
ext.getDebugKeyAlias = { ->
String keyAlias = project.hasProperty("debug_keystore_alias") ? project.property("debug_keystore_alias") : ""
if (keyAlias == null || keyAlias.isEmpty()) {
keyAlias = "androiddebugkey"
}
return keyAlias
}
ext.getReleaseKeystoreFile = { ->
String keystoreFile = project.hasProperty("release_keystore_file") ? project.property("release_keystore_file") : ""
if (keystoreFile == null || keystoreFile.isEmpty()) {
keystoreFile = "."
}
return keystoreFile
}
ext.getReleaseKeystorePassword = { ->
String keystorePassword = project.hasProperty("release_keystore_password") ? project.property("release_keystore_password") : ""
return keystorePassword
}
ext.getReleaseKeyAlias = { ->
String keyAlias = project.hasProperty("release_keystore_alias") ? project.property("release_keystore_alias") : ""
return keyAlias
}
ext.isAndroidStudio = { ->
return project.hasProperty('android.injected.invoked.from.ide')
}
ext.shouldZipAlign = { ->
String zipAlignFlag = project.hasProperty("perform_zipalign") ? project.property("perform_zipalign") : ""
if (zipAlignFlag == null || zipAlignFlag.isEmpty()) {
if (isAndroidStudio()) {
zipAlignFlag = "true"
} else {
zipAlignFlag = "false"
}
}
return Boolean.parseBoolean(zipAlignFlag)
}
ext.shouldSign = { ->
String signFlag = project.hasProperty("perform_signing") ? project.property("perform_signing") : ""
if (signFlag == null || signFlag.isEmpty()) {
if (isAndroidStudio()) {
signFlag = "true"
} else {
signFlag = "false"
}
}
return Boolean.parseBoolean(signFlag)
}
ext.shouldNotStrip = { ->
return isAndroidStudio() || project.hasProperty("doNotStrip")
}
/**
* Whether to use the legacy convention of compressing all .so files in the APK.
*
* For more background, see:
* - https://developer.android.com/build/releases/past-releases/agp-3-6-0-release-notes#extractNativeLibs
* - https://stackoverflow.com/a/44704840
*/
ext.shouldUseLegacyPackaging = { ->
String legacyPackagingFlag = project.hasProperty("compress_native_libraries") ? project.property("compress_native_libraries") : ""
if (legacyPackagingFlag != null && !legacyPackagingFlag.isEmpty()) {
return Boolean.parseBoolean(legacyPackagingFlag)
}
if (getExportMinSdkVersion() <= 29) {
// Use legacy packaging for compatibility with device running api <= 29.
// See https://github.com/godotengine/godot/issues/108842 for reference.
return true
}
// Default behavior for minSdk > 29.
return false
}
ext.getAddonsDirectory = { ->
String addonsDirectory = project.hasProperty("addons_directory") ? project.property("addons_directory") : ""
return addonsDirectory
}

View file

@ -0,0 +1,28 @@
# Godot gradle build settings.
# These properties apply when running a gradle build from the Godot editor.
# NOTE: This should be kept in sync with 'godot/platform/android/java/gradle.properties' except
# where otherwise specified.
# For more details on how to configure your build environment visit
# https://www.gradle.org/docs/current/userguide/build_environment.html
android.enableJetifier=true
android.useAndroidX=true
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx4536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.warning.mode=all
# Enable resource optimizations for release build.
# NOTE: This is turned off for template release build in order to support the build legacy process.
android.enableResourceOptimizations=true
# Fix gradle build errors when the build path contains non-ASCII characters
android.overridePathCheck=true

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ar</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-bg</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ca</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-cs</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-da</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-de</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-el</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-en</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-es_ES</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-es</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-fa</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-fi</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-fr</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hi</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hr</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hu</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-in</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-it</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-iw</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ja</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ko</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-lt</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-lv</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-nb</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-nl</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-pl</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-pt</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ro</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ru</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sk</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sl</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sr</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sv</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-th</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-tl</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-tr</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-uk</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-vi</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh_HK</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh_TW</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name</string>
</resources>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- GodotAppMainTheme is auto-generated during export. Manual changes will be overwritten.
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
<style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
<item name="android:windowSwipeToDismiss">false</item>
<item name="android:windowIsTranslucent">false</item>
</style>
<!-- GodotAppSplashTheme is auto-generated during export. Manual changes will be overwritten.
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
<style name="GodotAppSplashTheme" parent="Theme.SplashScreen">
<item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
<item name="android:windowSplashScreenBrandingImage">@drawable/splash_branding_image</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
<item name="android:windowIsTranslucent">false</item>
</style>
</resources>

View file

@ -0,0 +1,18 @@
// This is the root directory of the Godot Android gradle build.
pluginManagement {
apply from: 'config.gradle'
plugins {
id 'com.android.application' version versions.androidGradlePlugin
id 'org.jetbrains.kotlin.android' version versions.kotlinVersion
}
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
maven { url "https://central.sonatype.com/repository/maven-snapshots/"}
}
}
include ':assetPackInstallTime'

View file

@ -0,0 +1,227 @@
/**************************************************************************/
/* GodotAppTest.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package com.godot.game
import android.content.ComponentName
import android.content.Intent
import android.util.Log
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.godot.game.test.GodotAppInstrumentedTestPlugin
import org.godotengine.godot.Godot
import org.godotengine.godot.GodotActivity.Companion.EXTRA_COMMAND_LINE_PARAMS
import org.godotengine.godot.plugin.GodotPluginRegistry
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
/**
* This instrumented test will launch the `instrumented` version of GodotApp and run a set of tests against it.
*/
@RunWith(AndroidJUnit4::class)
class GodotAppTest {
companion object {
private val TAG = GodotAppTest::class.java.simpleName
private const val GODOT_APP_LAUNCHER_CLASS_NAME = "com.godot.game.GodotAppLauncher"
private const val GODOT_APP_CLASS_NAME = "com.godot.game.GodotApp"
private val TEST_COMMAND_LINE_PARAMS = arrayOf("This is a test")
}
private fun getTestPlugin(): GodotAppInstrumentedTestPlugin? {
return GodotPluginRegistry.getPluginRegistry()
.getPlugin("GodotAppInstrumentedTestPlugin") as GodotAppInstrumentedTestPlugin?
}
/**
* Runs the JavaClassWrapper tests via the GodotAppInstrumentedTestPlugin.
*/
@Test
fun runJavaClassWrapperTests() {
ActivityScenario.launch(GodotApp::class.java).use { scenario ->
scenario.onActivity { activity ->
val testPlugin = getTestPlugin()
assertNotNull(testPlugin)
Log.d(TAG, "Waiting for the Godot main loop to start...")
testPlugin.waitForGodotMainLoopStarted()
Log.d(TAG, "Running JavaClassWrapper tests...")
val result = testPlugin.runJavaClassWrapperTests()
assertNotNull(result)
result.exceptionOrNull()?.let { throw it }
assertTrue(result.isSuccess)
Log.d(TAG, "Passed ${result.getOrNull()} tests")
}
}
}
/**
* Runs file access related tests.
*/
@Test
fun runFileAccessTests() {
ActivityScenario.launch(GodotApp::class.java).use { scenario ->
scenario.onActivity { activity ->
val testPlugin = getTestPlugin()
assertNotNull(testPlugin)
Log.d(TAG, "Waiting for the Godot main loop to start...")
testPlugin.waitForGodotMainLoopStarted()
Log.d(TAG, "Running FileAccess tests...")
val result = testPlugin.runFileAccessTests()
assertNotNull(result)
result.exceptionOrNull()?.let { throw it }
assertTrue(result.isSuccess)
}
}
}
/**
* Test implicit launch of the Godot app, and validates this resolves to the `GodotAppLauncher` activity alias.
*/
@Test
fun testImplicitGodotAppLauncherLaunch() {
val implicitLaunchIntent = Intent().apply {
setPackage(BuildConfig.APPLICATION_ID)
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
putExtra(EXTRA_COMMAND_LINE_PARAMS, TEST_COMMAND_LINE_PARAMS)
}
ActivityScenario.launch<GodotApp>(implicitLaunchIntent).use { scenario ->
scenario.onActivity { activity ->
assertEquals(activity.intent.component?.className, GODOT_APP_LAUNCHER_CLASS_NAME)
val commandLineParams = activity.intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
assertNull(commandLineParams)
}
}
}
/**
* Test explicit launch of the Godot app via its activity-alias launcher, and validates it resolves properly.
*/
@Test
fun testExplicitGodotAppLauncherLaunch() {
val explicitIntent = Intent().apply {
component = ComponentName(BuildConfig.APPLICATION_ID, GODOT_APP_LAUNCHER_CLASS_NAME)
putExtra(EXTRA_COMMAND_LINE_PARAMS, TEST_COMMAND_LINE_PARAMS)
}
ActivityScenario.launch<GodotApp>(explicitIntent).use { scenario ->
scenario.onActivity { activity ->
assertEquals(activity.intent.component?.className, GODOT_APP_LAUNCHER_CLASS_NAME)
val commandLineParams = activity.intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
assertNull(commandLineParams)
}
}
}
/**
* Test explicit launch of the `GodotApp` activity.
*/
@Test
fun testExplicitGodotAppLaunch() {
val explicitIntent = Intent().apply {
component = ComponentName(BuildConfig.APPLICATION_ID, GODOT_APP_CLASS_NAME)
putExtra(EXTRA_COMMAND_LINE_PARAMS, TEST_COMMAND_LINE_PARAMS)
}
ActivityScenario.launch<GodotApp>(explicitIntent).use { scenario ->
scenario.onActivity { activity ->
assertEquals(activity.intent.component?.className, GODOT_APP_CLASS_NAME)
val commandLineParams = activity.intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
assertNotNull(commandLineParams)
assertTrue(commandLineParams.contentEquals(TEST_COMMAND_LINE_PARAMS))
}
}
}
/**
* Validate that the back press does not quit the game when 'quit_on_go_back' is disabled.
*/
@Test
fun testGameNotQuittingOnBackPress() {
ActivityScenario.launch(GodotApp::class.java).use { scenario ->
val testPlugin = getTestPlugin()
assertNotNull(testPlugin)
Log.d(TAG, "Waiting for the Godot main loop to start...")
testPlugin.waitForGodotMainLoopStarted()
// Disable 'quit_on_go_back'.
testPlugin.updateQuitOnGoBack(false)
// Trigger the back press event.
Espresso.pressBackUnconditionally()
Log.d(TAG, "Waiting for the engine to terminate...")
testPlugin.waitForEngineTermination(5_000L)
val godot = Godot.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)
assertTrue { godot.runStatus != Godot.RunStatus.TERMINATING }
}
}
/**
* Validate that the back press event quits the game when 'quit_on_go_back' is enabled.
*/
@Test
fun testGameQuittingOnBackPress() {
ActivityScenario.launch(GodotApp::class.java).use { scenario ->
val testPlugin = getTestPlugin()
assertNotNull(testPlugin)
Log.d(TAG, "Waiting for the Godot main loop to start...")
testPlugin.waitForGodotMainLoopStarted()
// Enable 'quit_on_go_back'.
testPlugin.updateQuitOnGoBack(true)
// Trigger the back press event.
Espresso.pressBackUnconditionally()
Log.d(TAG, "Waiting for the engine to terminate...")
testPlugin.waitForEngineTermination(5_000L)
val godot = Godot.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)
assertTrue { godot.runStatus == Godot.RunStatus.TERMINATING }
}
}
}

View file

@ -0,0 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data
android:name="org.godotengine.plugin.v2.GodotAppInstrumentedTestPlugin"
android:value="com.godot.game.test.GodotAppInstrumentedTestPlugin"/>
</application>
</manifest>

View file

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

View file

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
/android/
/.godot/editor

View file

@ -0,0 +1,25 @@
list=[{
"base": &"BaseTest",
"class": &"FileAccessTests",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://test/file_access/file_access_tests.gd"
}, {
"base": &"BaseTest",
"class": &"JavaClassWrapperTests",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://test/javaclasswrapper/java_class_wrapper_tests.gd"
}, {
"base": &"RefCounted",
"class": &"BaseTest",
"icon": "",
"is_abstract": true,
"is_tool": false,
"language": &"GDScript",
"path": "res://test/base_test.gd"
}]

View file

@ -0,0 +1,2 @@
source_md5="4cdc64b13a9af63279c486903c9b54cc"
dest_md5="ddbdfc47e6405ad8d8e9e6a88a32824e"

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 996 B

View file

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://srnrli5m8won"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,67 @@
extends Node2D
var _plugin_name = "GodotAppInstrumentedTestPlugin"
var _android_plugin
func _ready():
if Engine.has_singleton(_plugin_name):
_android_plugin = Engine.get_singleton(_plugin_name)
_android_plugin.connect("launch_tests", _launch_tests)
_android_plugin.connect("update_quit_on_go_back", _update_quit_on_go_back)
else:
printerr("Couldn't find plugin " + _plugin_name)
get_tree().quit()
func _launch_tests(test_label: String) -> void:
var test_instance: BaseTest = null
match test_label:
"javaclasswrapper_tests":
test_instance = JavaClassWrapperTests.new()
"file_access_tests":
test_instance = FileAccessTests.new()
if test_instance:
test_instance.__reset_tests()
test_instance.run_tests()
var incomplete_tests = test_instance._test_started - test_instance._test_completed
_android_plugin.onTestsCompleted(test_label, test_instance._test_completed, test_instance._test_assert_failures + incomplete_tests)
else:
_android_plugin.onTestsFailed(test_label, "Unable to launch tests")
func _update_quit_on_go_back(quit_on_go_back: bool) -> void:
get_tree().quit_on_go_back = quit_on_go_back
func _on_plugin_toast_button_pressed() -> void:
if _android_plugin:
_android_plugin.helloWorld()
func _on_vibration_button_pressed() -> void:
var android_runtime = Engine.get_singleton("AndroidRuntime")
if android_runtime:
print("Checking if the device supports vibration")
var vibrator_service = android_runtime.getApplicationContext().getSystemService("vibrator")
if vibrator_service:
if vibrator_service.hasVibrator():
print("Vibration is supported on device! Vibrating now...")
var VibrationEffect = JavaClassWrapper.wrap("android.os.VibrationEffect")
var effect = VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE)
vibrator_service.vibrate(effect)
else:
printerr("Vibration is not supported on device")
else:
printerr("Unable to retrieve the vibrator service")
else:
printerr("Couldn't find AndroidRuntime singleton")
func _on_gd_script_toast_button_pressed() -> void:
var android_runtime = Engine.get_singleton("AndroidRuntime")
if android_runtime:
var activity = android_runtime.getActivity()
var toastCallable = func ():
var ToastClass = JavaClassWrapper.wrap("android.widget.Toast")
ToastClass.makeText(activity, "Toast from GDScript", ToastClass.LENGTH_LONG).show()
activity.runOnUiThread(android_runtime.createRunnableFromGodotCallable(toastCallable))

View file

@ -0,0 +1 @@
uid://bv6y7in6otgcm

View file

@ -0,0 +1,34 @@
[gd_scene format=3 uid="uid://cg3hylang5fxn"]
[ext_resource type="Script" uid="uid://bv6y7in6otgcm" path="res://main.gd" id="1_j0gfq"]
[node name="Main" type="Node2D" unique_id=852911723]
script = ExtResource("1_j0gfq")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1839352715]
offset_left = 68.0
offset_top = 102.0
offset_right = 506.0
offset_bottom = 408.0
theme_override_constants/separation = 25
[node name="PluginToastButton" type="Button" parent="VBoxContainer" unique_id=1670434164]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "Plugin Toast
"
[node name="VibrationButton" type="Button" parent="VBoxContainer" unique_id=648980813]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "Vibration"
[node name="GDScriptToastButton" type="Button" parent="VBoxContainer" unique_id=95554078]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "GDScript Toast
"
[connection signal="pressed" from="VBoxContainer/PluginToastButton" to="." method="_on_plugin_toast_button_pressed"]
[connection signal="pressed" from="VBoxContainer/VibrationButton" to="." method="_on_vibration_button_pressed"]
[connection signal="pressed" from="VBoxContainer/GDScriptToastButton" to="." method="_on_gd_script_toast_button_pressed"]

View file

@ -0,0 +1,30 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[animation]
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application]
config/name="Godot App Instrumentation Tests"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.6", "GL Compatibility")
config/icon="res://icon.svg"
[debug]
settings/stdout/verbose_stdout=true
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
textures/vram_compression/import_etc2_astc=true

View file

@ -0,0 +1,52 @@
@abstract class_name BaseTest
var _test_started := 0
var _test_completed := 0
var _test_assert_passes := 0
var _test_assert_failures := 0
@abstract func run_tests()
func __exec_test(test_func: Callable):
_test_started += 1
var ret = test_func.call()
if ret == true:
_test_completed += 1
func __reset_tests():
_test_started = 0
_test_completed = 0
_test_assert_passes = 0
_test_assert_failures = 0
func __get_stack_frame():
for s in get_stack():
if not s.function.begins_with('__') and s.function != "assert_equal":
return s
return null
func __assert_pass():
_test_assert_passes += 1
pass
func __assert_fail():
_test_assert_failures += 1
var s = __get_stack_frame()
if s != null:
print_rich ("[color=red] == FAILURE: In function %s() from '%s' on line %s[/color]" % [s.function, s.source, s.line])
else:
print_rich ("[color=red] == FAILURE (run with --debug to get more information!) ==[/color]")
func assert_equal(actual, expected):
if actual == expected:
__assert_pass()
else:
__assert_fail()
print (" |-> Expected '%s' but got '%s'" % [expected, actual])
func assert_true(value):
if value:
__assert_pass()
else:
__assert_fail()
print (" |-> Expected '%s' to be truthy" % value)

View file

@ -0,0 +1 @@
uid://mofa8j0d801f

View file

@ -0,0 +1,84 @@
class_name FileAccessTests
extends BaseTest
const FILE_CONTENT = "This is a test for reading / writing to the "
func run_tests():
print("FileAccess tests starting...")
__exec_test(test_obb_dir_access)
__exec_test(test_internal_app_dir_access)
__exec_test(test_internal_cache_dir_access)
__exec_test(test_external_app_dir_access)
# Scoped storage: Testing access to Downloads and Documents directory.
var version = JavaClassWrapper.wrap("android.os.Build$VERSION")
if version.SDK_INT >= 30:
__exec_test(test_downloads_dir_access)
__exec_test(test_documents_dir_access)
func _test_dir_access(dir_path: String, data_file_content: String) -> bool:
print("Testing access to " + dir_path)
var data_file_path = dir_path.path_join("data.dat")
var data_file = FileAccess.open(data_file_path, FileAccess.WRITE)
assert_true(data_file != null)
assert_true(data_file.store_string(data_file_content))
data_file.close()
data_file = FileAccess.open(data_file_path, FileAccess.READ)
assert_true(data_file != null)
var file_content = data_file.get_as_text()
assert_equal(file_content, data_file_content)
data_file.close()
var deletion_result = DirAccess.remove_absolute(data_file_path)
assert_equal(deletion_result, OK)
return true
func test_obb_dir_access() -> bool:
var android_runtime = Engine.get_singleton("AndroidRuntime")
assert_true(android_runtime != null)
var app_context = android_runtime.getApplicationContext()
var obb_dir: String = app_context.getObbDir().getCanonicalPath()
_test_dir_access(obb_dir, FILE_CONTENT + "obb dir.")
return true
func test_internal_app_dir_access() -> bool:
var android_runtime = Engine.get_singleton("AndroidRuntime")
assert_true(android_runtime != null)
var app_context = android_runtime.getApplicationContext()
var internal_app_dir: String = app_context.getFilesDir().getCanonicalPath()
_test_dir_access(internal_app_dir, FILE_CONTENT + "internal app dir.")
return true
func test_internal_cache_dir_access() -> bool:
var android_runtime = Engine.get_singleton("AndroidRuntime")
assert_true(android_runtime != null)
var app_context = android_runtime.getApplicationContext()
var internal_cache_dir: String = app_context.getCacheDir().getCanonicalPath()
_test_dir_access(internal_cache_dir, FILE_CONTENT + "internal cache dir.")
return true
func test_external_app_dir_access() -> bool:
var android_runtime = Engine.get_singleton("AndroidRuntime")
assert_true(android_runtime != null)
var app_context = android_runtime.getApplicationContext()
var external_app_dir: String = app_context.getExternalFilesDir("").getCanonicalPath()
_test_dir_access(external_app_dir, FILE_CONTENT + "external app dir.")
return true
func test_downloads_dir_access() -> bool:
var EnvironmentClass = JavaClassWrapper.wrap("android.os.Environment")
var downloads_dir = EnvironmentClass.getExternalStoragePublicDirectory(EnvironmentClass.DIRECTORY_DOWNLOADS).getCanonicalPath()
_test_dir_access(downloads_dir, FILE_CONTENT + "downloads dir.")
return true
func test_documents_dir_access() -> bool:
var EnvironmentClass = JavaClassWrapper.wrap("android.os.Environment")
var documents_dir = EnvironmentClass.getExternalStoragePublicDirectory(EnvironmentClass.DIRECTORY_DOCUMENTS).getCanonicalPath()
_test_dir_access(documents_dir, FILE_CONTENT + "documents dir.")
return true

View file

@ -0,0 +1,206 @@
class_name JavaClassWrapperTests
extends BaseTest
func run_tests():
print("JavaClassWrapper tests starting..")
__exec_test(test_exceptions)
__exec_test(test_multiple_signatures)
__exec_test(test_array_arguments)
__exec_test(test_array_return)
__exec_test(test_dictionary)
__exec_test(test_object_overload)
__exec_test(test_variant_conversion_safe_from_stack_overflow)
__exec_test(test_big_integers)
__exec_test(test_callable)
__exec_test(test_interface_callable_proxy)
__exec_test(test_interface_object_proxy)
print("JavaClassWrapper tests finished.")
print("Tests started: " + str(_test_started))
print("Tests completed: " + str(_test_completed))
func test_exceptions() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
#print(TestClass.get_java_method_list())
assert_equal(JavaClassWrapper.get_exception(), null)
assert_equal(TestClass.testExc(27), 0)
assert_equal(str(JavaClassWrapper.get_exception()), '<JavaObject:java.lang.NullPointerException "java.lang.NullPointerException">')
assert_equal(JavaClassWrapper.get_exception(), null)
return true
func test_multiple_signatures() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
var ai := [1, 2]
assert_equal(TestClass.testMethod(1, ai), "IntArray: [1, 2]")
var astr := ["abc"]
assert_equal(TestClass.testMethod(2, astr), "IntArray: [0]")
var atstr: Array[String] = ["abc"]
assert_equal(TestClass.testMethod(3, atstr), "StringArray: [abc]")
var TestClass2: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass2')
var aobjl: Array[Object] = [
TestClass2.TestClass2(27),
TestClass2.TestClass2(135),
]
assert_equal(TestClass.testMethod(3, aobjl), "testObjects: 27 135")
return true
func test_array_arguments() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
assert_equal(TestClass.testArgBoolArray([true, false, true]), "[true, false, true]")
assert_equal(TestClass.testArgByteArray(PackedByteArray([1, 2, 3])), "[1, 2, 3]")
assert_equal(TestClass.testArgCharArray("abc".to_utf16_buffer()), "abc");
assert_equal(TestClass.testArgShortArray(PackedInt32Array([27, 28, 29])), "[27, 28, 29]")
assert_equal(TestClass.testArgShortArray([27, 28, 29]), "[27, 28, 29]")
assert_equal(TestClass.testArgIntArray(PackedInt32Array([7, 8, 9])), "[7, 8, 9]")
assert_equal(TestClass.testArgIntArray([7, 8, 9]), "[7, 8, 9]")
assert_equal(TestClass.testArgLongArray(PackedInt64Array([17, 18, 19])), "[17, 18, 19]")
assert_equal(TestClass.testArgLongArray([17, 18, 19]), "[17, 18, 19]")
assert_equal(TestClass.testArgFloatArray(PackedFloat32Array([17.1, 18.2, 19.3])), "[17.1, 18.2, 19.3]")
assert_equal(TestClass.testArgFloatArray([17.1, 18.2, 19.3]), "[17.1, 18.2, 19.3]")
assert_equal(TestClass.testArgDoubleArray(PackedFloat64Array([37.1, 38.2, 39.3])), "[37.1, 38.2, 39.3]")
assert_equal(TestClass.testArgDoubleArray([37.1, 38.2, 39.3]), "[37.1, 38.2, 39.3]")
return true
func test_array_return() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
#print(TestClass.get_java_method_list())
assert_equal(TestClass.testRetBoolArray(), [true, false, true])
assert_equal(TestClass.testRetWrappedBoolArray(), [true, false, true])
assert_equal(TestClass.testRetByteArray(), PackedByteArray([1, 2, 3]))
assert_equal(TestClass.testRetWrappedByteArray(), PackedByteArray([1, 2, 3]))
assert_equal(TestClass.testRetCharArray().get_string_from_utf16(), "abc")
assert_equal(TestClass.testRetWrappedCharArray().get_string_from_utf16(), "abc")
assert_equal(TestClass.testRetShortArray(), PackedInt32Array([11, 12, 13]))
assert_equal(TestClass.testRetWrappedShortArray(), PackedInt32Array([11, 12, 13]))
assert_equal(TestClass.testRetIntArray(), PackedInt32Array([21, 22, 23]))
assert_equal(TestClass.testRetWrappedIntArray(), PackedInt32Array([21, 22, 23]))
assert_equal(TestClass.testRetLongArray(), PackedInt64Array([41, 42, 43]))
assert_equal(TestClass.testRetWrappedLongArray(), PackedInt64Array([41, 42, 43]))
assert_equal(TestClass.testRetFloatArray(), PackedFloat32Array([31.1, 32.2, 33.3]))
assert_equal(TestClass.testRetWrappedFloatArray(), PackedFloat32Array([31.1, 32.2, 33.3]))
assert_equal(TestClass.testRetDoubleArray(), PackedFloat64Array([41.1, 42.2, 43.3]))
assert_equal(TestClass.testRetWrappedDoubleArray(), PackedFloat64Array([41.1, 42.2, 43.3]))
var obj_array = TestClass.testRetObjectArray()
assert_equal(str(obj_array[0]), '<JavaObject:com.godot.game.test.javaclasswrapper.TestClass2 "51">')
assert_equal(str(obj_array[1]), '<JavaObject:com.godot.game.test.javaclasswrapper.TestClass2 "52">')
assert_equal(TestClass.testRetStringArray(), PackedStringArray(["I", "am", "String"]))
assert_equal(TestClass.testRetCharSequenceArray(), PackedStringArray(["I", "am", "CharSequence"]))
return true
func test_dictionary() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
assert_equal(TestClass.testDictionary({a = 1, b = 2}), "{a=1, b=2}")
assert_equal(TestClass.testRetDictionary(), {a = 1, b = 2})
assert_equal(TestClass.testRetDictionaryArray(), [{a = 1, b = 2}])
assert_equal(TestClass.testDictionaryNested({a = 1, b = [2, 3], c = 4}), "{a: 1, b: [2, 3], c: 4}")
return true
func test_object_overload() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
var TestClass2: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass2')
var TestClass3: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass3')
var t2 = TestClass2.TestClass2(33)
var t3 = TestClass3.TestClass3("thirty three")
assert_equal(TestClass.testObjectOverload(t2), "TestClass2: 33")
assert_equal(TestClass.testObjectOverload(t3), "TestClass3: thirty three")
var arr_of_t2 = [t2, TestClass2.TestClass2(34)]
var arr_of_t3 = [t3, TestClass3.TestClass3("thirty four")]
assert_equal(TestClass.testObjectOverloadArray(arr_of_t2), "TestClass2: [33, 34]")
assert_equal(TestClass.testObjectOverloadArray(arr_of_t3), "TestClass3: [thirty three, thirty four]")
return true
func test_variant_conversion_safe_from_stack_overflow() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
var arr: Array = [42]
var dict: Dictionary = {"arr": arr}
arr.append(dict)
# The following line will crash with stack overflow if not handled property:
TestClass.testDictionary(dict)
return true
func test_big_integers() -> bool:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
assert_equal(TestClass.testArgLong(4242424242), "4242424242")
assert_equal(TestClass.testArgLong(-4242424242), "-4242424242")
assert_equal(TestClass.testDictionary({a = 4242424242, b = -4242424242}), "{a=4242424242, b=-4242424242}")
return true
func test_callable() -> bool:
var android_runtime = Engine.get_singleton("AndroidRuntime")
assert_true(android_runtime != null)
var cb1_data := {called = false}
var cb1 = func():
cb1_data['called'] = true
return null
android_runtime.createRunnableFromGodotCallable(cb1).run()
assert_equal(cb1_data['called'], true)
return true
func test_interface_callable_proxy() -> bool:
var cb1_data := {called = false, content = ""}
var cb1 = func (content: String) -> void:
cb1_data['called'] = true
cb1_data['content'] = content
var printer_proxy = JavaClassWrapper.create_sam_callback("android.util.Printer", cb1)
assert_true(printer_proxy != null)
printer_proxy.println("This is a callback test")
assert_equal(cb1_data['called'], true)
assert_equal(cb1_data['content'], "This is a callback test")
return true
class PrintProxy:
var test_data := {called = false, content = ""}
func println(content: String) -> void:
test_data['called'] = true
test_data['content'] = content
func test_interface_object_proxy() -> bool:
var print_object = PrintProxy.new()
var proxy = JavaClassWrapper.create_proxy(print_object, ["android.util.Printer"])
assert_true(proxy != null)
proxy.println("This is proxy test")
assert_equal(print_object.test_data['called'], true)
assert_equal(print_object.test_data['content'], "This is proxy test")
return true

View file

@ -0,0 +1,176 @@
/**************************************************************************/
/* GodotAppInstrumentedTestPlugin.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package com.godot.game.test
import android.util.Log
import android.widget.Toast
import org.godotengine.godot.Godot
import org.godotengine.godot.plugin.GodotPlugin
import org.godotengine.godot.plugin.UsedByGodot
import org.godotengine.godot.plugin.SignalInfo
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
/**
* [GodotPlugin] used to drive instrumented tests.
*/
class GodotAppInstrumentedTestPlugin(godot: Godot) : GodotPlugin(godot) {
companion object {
private val TAG = GodotAppInstrumentedTestPlugin::class.java.simpleName
private const val MAIN_LOOP_STARTED_LATCH_KEY = "main_loop_started_latch"
private const val ENGINE_TERMINATING_LATCH_KEY = "engine_terminating_latch"
private const val JAVACLASSWRAPPER_TESTS = "javaclasswrapper_tests"
private const val FILE_ACCESS_TESTS = "file_access_tests"
private val LAUNCH_TESTS_SIGNAL = SignalInfo("launch_tests", String::class.java)
private val UPDATE_QUIT_ON_GO_BACK_SIGNAL = SignalInfo("update_quit_on_go_back", java.lang.Boolean::class.java)
private val SIGNALS = setOf(
LAUNCH_TESTS_SIGNAL,
UPDATE_QUIT_ON_GO_BACK_SIGNAL
)
}
private val testResults = ConcurrentHashMap<String, Result<Any>>()
private val latches = ConcurrentHashMap<String, CountDownLatch>()
init {
// Add a countdown latch that is triggered when `onGodotMainLoopStarted` is fired.
// This will be used by tests to wait until the engine is ready.
latches[MAIN_LOOP_STARTED_LATCH_KEY] = CountDownLatch(1)
// Add a countdown latch that is triggered when the engine terminates.
latches[ENGINE_TERMINATING_LATCH_KEY] = CountDownLatch(1)
}
override fun getPluginName() = "GodotAppInstrumentedTestPlugin"
override fun getPluginSignals() = SIGNALS
override fun onGodotMainLoopStarted() {
super.onGodotMainLoopStarted()
latches.remove(MAIN_LOOP_STARTED_LATCH_KEY)?.countDown()
}
override fun onGodotTerminating() {
super.onGodotTerminating()
latches.remove(ENGINE_TERMINATING_LATCH_KEY)?.countDown()
}
/**
* Used by the instrumented test to wait until the Godot main loop is up and running.
*/
internal fun waitForGodotMainLoopStarted() {
// Wait on the CountDownLatch for `onGodotMainLoopStarted`
try {
latches[MAIN_LOOP_STARTED_LATCH_KEY]?.await()
} catch (e: InterruptedException) {
Log.e(TAG, "Unable to wait for Godot main loop started event.", e)
}
}
internal fun waitForEngineTermination(timeoutInMs: Long) {
// Wait on the CountDownLatch for `onGodotTerminating`.
try {
latches[ENGINE_TERMINATING_LATCH_KEY]?.await(timeoutInMs, TimeUnit.MILLISECONDS)
} catch (e: InterruptedException) {
Log.e(TAG, "Unable to wait for engine termination event.", e)
}
}
internal fun updateQuitOnGoBack(quitOnGoBack: Boolean) {
emitSignal(UPDATE_QUIT_ON_GO_BACK_SIGNAL, quitOnGoBack)
}
/**
* This launches the JavaClassWrapper tests, and wait until the tests are complete before returning.
*/
internal fun runJavaClassWrapperTests(): Result<Any>? {
return launchTests(JAVACLASSWRAPPER_TESTS)
}
/**
* Launches the FileAccess tests, and wait until the tests are complete before returning.
*/
internal fun runFileAccessTests(): Result<Any>? {
return launchTests(FILE_ACCESS_TESTS)
}
private fun launchTests(testLabel: String): Result<Any>? {
val latch = latches.getOrPut(testLabel) { CountDownLatch(1) }
emitSignal(LAUNCH_TESTS_SIGNAL, testLabel)
return try {
latch.await()
val result = testResults.remove(testLabel)
result
} catch (e: InterruptedException) {
Log.e(TAG, "Unable to wait for completion for $testLabel", e)
null
}
}
/**
* Callback invoked from gdscript when the tests are completed.
*/
@UsedByGodot
fun onTestsCompleted(testLabel: String, passes: Int, failures: Int) {
Log.d(TAG, "$testLabel tests completed")
val result = if (failures == 0) {
Result.success(passes)
} else {
Result.failure(AssertionError("$failures tests failed!"))
}
completeTest(testLabel, result)
}
@UsedByGodot
fun onTestsFailed(testLabel: String, failureMessage: String) {
Log.d(TAG, "$testLabel tests failed")
val result: Result<Any> = Result.failure(AssertionError(failureMessage))
completeTest(testLabel, result)
}
private fun completeTest(testKey: String, result: Result<Any>) {
testResults[testKey] = result
latches.remove(testKey)?.countDown()
}
@UsedByGodot
fun helloWorld() {
runOnHostThread {
Toast.makeText(activity, "Toast from Android plugin", Toast.LENGTH_LONG).show()
Log.v(pluginName, "Hello World")
}
}
}

View file

@ -0,0 +1,266 @@
/**************************************************************************/
/* TestClass.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package com.godot.game.test.javaclasswrapper
import org.godotengine.godot.Dictionary
import kotlin.collections.contentToString
import kotlin.collections.joinToString
class TestClass {
companion object {
@JvmStatic
fun stringify(value: Any?): String {
return when (value) {
null -> "null"
is Map<*, *> -> {
val entries = value.entries.joinToString(", ") { (k, v) -> "${stringify(k)}: ${stringify(v)}" }
"{$entries}"
}
is List<*> -> value.joinToString(prefix = "[", postfix = "]") { stringify(it) }
is Array<*> -> value.joinToString(prefix = "[", postfix = "]") { stringify(it) }
is IntArray -> value.joinToString(prefix = "[", postfix = "]")
is LongArray -> value.joinToString(prefix = "[", postfix = "]")
is FloatArray -> value.joinToString(prefix = "[", postfix = "]")
is DoubleArray -> value.joinToString(prefix = "[", postfix = "]")
is BooleanArray -> value.joinToString(prefix = "[", postfix = "]")
is CharArray -> value.joinToString(prefix = "[", postfix = "]")
else -> value.toString()
}
}
@JvmStatic
fun testDictionary(d: Dictionary): String {
return d.toString()
}
@JvmStatic
fun testDictionaryNested(d: Dictionary): String {
return stringify(d)
}
@JvmStatic
fun testRetDictionary(): Dictionary {
var d = Dictionary()
d.putAll(mapOf("a" to 1, "b" to 2))
return d
}
@JvmStatic
fun testRetDictionaryArray(): Array<Dictionary> {
var d = Dictionary()
d.putAll(mapOf("a" to 1, "b" to 2))
return arrayOf(d)
}
@JvmStatic
fun testMethod(int: Int, array: IntArray): String {
return "IntArray: " + array.contentToString()
}
@JvmStatic
fun testMethod(int: Int, vararg args: String): String {
return "StringArray: " + args.contentToString()
}
@JvmStatic
fun testMethod(int: Int, objects: Array<TestClass2>): String {
return "testObjects: " + objects.joinToString(separator = " ") { it.getValue().toString() }
}
@JvmStatic
fun testExc(i: Int): Int {
val s: String? = null
s!!.length
return i
}
@JvmStatic
fun testArgLong(a: Long): String {
return "${a}"
}
@JvmStatic
fun testArgBoolArray(a: BooleanArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgByteArray(a: ByteArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgCharArray(a: CharArray): String {
return a.joinToString("")
}
@JvmStatic
fun testArgShortArray(a: ShortArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgIntArray(a: IntArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgLongArray(a: LongArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgFloatArray(a: FloatArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgDoubleArray(a: DoubleArray): String {
return a.contentToString();
}
@JvmStatic
fun testRetBoolArray(): BooleanArray {
return booleanArrayOf(true, false, true)
}
@JvmStatic
fun testRetByteArray(): ByteArray {
return byteArrayOf(1, 2, 3)
}
@JvmStatic
fun testRetCharArray(): CharArray {
return "abc".toCharArray()
}
@JvmStatic
fun testRetShortArray(): ShortArray {
return shortArrayOf(11, 12, 13)
}
@JvmStatic
fun testRetIntArray(): IntArray {
return intArrayOf(21, 22, 23)
}
@JvmStatic
fun testRetLongArray(): LongArray {
return longArrayOf(41, 42, 43)
}
@JvmStatic
fun testRetFloatArray(): FloatArray {
return floatArrayOf(31.1f, 32.2f, 33.3f)
}
@JvmStatic
fun testRetDoubleArray(): DoubleArray {
return doubleArrayOf(41.1, 42.2, 43.3)
}
@JvmStatic
fun testRetWrappedBoolArray(): Array<Boolean> {
return arrayOf(true, false, true)
}
@JvmStatic
fun testRetWrappedByteArray(): Array<Byte> {
return arrayOf(1, 2, 3)
}
@JvmStatic
fun testRetWrappedCharArray(): Array<Char> {
return arrayOf('a', 'b', 'c')
}
@JvmStatic
fun testRetWrappedShortArray(): Array<Short> {
return arrayOf(11, 12, 13)
}
@JvmStatic
fun testRetWrappedIntArray(): Array<Int> {
return arrayOf(21, 22, 23)
}
@JvmStatic
fun testRetWrappedLongArray(): Array<Long> {
return arrayOf(41, 42, 43)
}
@JvmStatic
fun testRetWrappedFloatArray(): Array<Float> {
return arrayOf(31.1f, 32.2f, 33.3f)
}
@JvmStatic
fun testRetWrappedDoubleArray(): Array<Double> {
return arrayOf(41.1, 42.2, 43.3)
}
@JvmStatic
fun testRetObjectArray(): Array<TestClass2> {
return arrayOf(TestClass2(51), TestClass2(52));
}
@JvmStatic
fun testRetStringArray(): Array<String> {
return arrayOf("I", "am", "String")
}
@JvmStatic
fun testRetCharSequenceArray(): Array<CharSequence> {
return arrayOf("I", "am", "CharSequence")
}
@JvmStatic
fun testObjectOverload(a: TestClass2): String {
return "TestClass2: $a"
}
@JvmStatic
fun testObjectOverload(a: TestClass3): String {
return "TestClass3: $a"
}
@JvmStatic
fun testObjectOverloadArray(a: Array<TestClass2>): String {
return "TestClass2: " + a.contentToString()
}
@JvmStatic
fun testObjectOverloadArray(a: Array<TestClass3>): String {
return "TestClass3: " + a.contentToString()
}
}
}

View file

@ -0,0 +1,40 @@
/**************************************************************************/
/* TestClass2.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package com.godot.game.test.javaclasswrapper
class TestClass2(private val value: Int) {
fun getValue(): Int {
return value
}
override fun toString(): String {
return value.toString()
}
}

View file

@ -0,0 +1,40 @@
/**************************************************************************/
/* TestClass3.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package com.godot.game.test.javaclasswrapper
class TestClass3(private val value: String) {
fun getValue(): String {
return value
}
override fun toString(): String {
return value
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="godot_project_name_string">Godot App Instrumented Tests</string>
</resources>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto" >
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true" />
<uses-feature
android:glEsVersion="0x00030000"
android:required="true" />
<application
android:label="@string/godot_project_name_string"
android:allowBackup="false"
android:icon="@mipmap/icon"
android:appCategory="game"
android:isGame="true"
android:hasFragileUserData="false"
android:requestLegacyExternalStorage="false"
tools:ignore="GoogleAppIndexingWarning" >
<profileable
android:shell="true"
android:enabled="true"
tools:targetApi="29" />
<activity
android:name=".GodotApp"
android:theme="@style/GodotAppSplashTheme"
android:launchMode="singleInstancePerTask"
android:excludeFromRecents="false"
android:exported="false"
android:supportsPictureInPicture="true"
android:screenOrientation="landscape"
android:windowSoftInputMode="adjustResize"
android:configChanges="layoutDirection|locale|orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:resizeableActivity="false"
tools:ignore="UnusedAttribute" />
<activity-alias
android:name=".GodotAppLauncher"
android:targetActivity=".GodotApp"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>

View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -0,0 +1,105 @@
/**************************************************************************/
/* GodotApp.java */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package com.godot.game;
import org.godotengine.godot.Godot;
import org.godotengine.godot.GodotActivity;
import android.os.Bundle;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.core.splashscreen.SplashScreen;
/**
* Template activity for Godot Android builds.
* Feel free to extend and modify this class for your custom logic.
*/
public class GodotApp extends GodotActivity {
static {
// .NET libraries.
if (BuildConfig.FLAVOR.equals("mono")) {
try {
Log.v("GODOT", "Loading System.Security.Cryptography.Native.Android library");
System.loadLibrary("System.Security.Cryptography.Native.Android");
} catch (UnsatisfiedLinkError e) {
Log.e("GODOT", "Unable to load System.Security.Cryptography.Native.Android library");
}
}
}
private final Runnable updateWindowAppearance = () -> {
Godot godot = getGodot();
if (godot != null) {
godot.enableImmersiveMode(godot.isInImmersiveMode(), true);
godot.enableEdgeToEdge(godot.isInEdgeToEdgeMode(), true);
godot.setSystemBarsAppearance();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
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
public void onResume() {
super.onResume();
updateWindowAppearance.run();
}
@Override
public void onGodotMainLoopStarted() {
super.onGodotMainLoopStarted();
runOnUiThread(updateWindowAppearance);
}
@Override
public void onGodotForceQuit(Godot instance) {
if (!BuildConfig.FLAVOR.equals("instrumented")) {
// For instrumented builds, we disable force-quitting to allow the instrumented tests to complete
// successfully, otherwise they fail when the process crashes.
super.onGodotForceQuit(instance);
}
}
@Override
protected boolean isPiPEnabled() {
return true;
}
}