diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index e3dd738c947..78316247c74 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -474,6 +474,9 @@ void EditorNode::_notification(int p_what) { get_tree()->get_root()->set_as_audio_listener(false); get_tree()->get_root()->set_as_audio_listener_2d(false); get_tree()->set_auto_accept_quit(false); +#ifdef ANDROID_ENABLED + get_tree()->set_quit_on_go_back(false); +#endif get_tree()->connect("files_dropped", this, "_dropped_files"); get_tree()->connect("global_menu_action", this, "_global_menu_action"); diff --git a/platform/android/SCsub b/platform/android/SCsub index 1a3e2b458a9..f5258a5497c 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -50,10 +50,17 @@ else: if lib_arch_dir != "": if env["target"] == "release": lib_type_dir = "release" - else: # release_debug, debug + elif env["target"] == "release_debug": + lib_type_dir = "release_debug" + else: # debug lib_type_dir = "debug" - out_dir = "#platform/android/java/lib/libs/" + lib_type_dir + "/" + lib_arch_dir + if env["tools"]: + lib_tools_dir = "tools/" + else: + lib_tools_dir = "" + + out_dir = "#platform/android/java/lib/libs/" + lib_tools_dir + lib_type_dir + "/" + lib_arch_dir env_android.Command( out_dir + "/libgodot_android.so", "#bin/libgodot" + env["SHLIBSUFFIX"], Move("$TARGET", "$SOURCE") ) diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index d03f70807db..47026798d1b 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -78,6 +78,7 @@ dependencies { android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools + ndkVersion versions.ndkVersion compileOptions { sourceCompatibility versions.javaVersion @@ -105,6 +106,8 @@ android { versionName getExportVersionName() minSdkVersion getExportMinSdkVersion() targetSdkVersion getExportTargetSdkVersion() + + missingDimensionStrategy 'tools', 'toolsDisabled' //CHUNK_ANDROID_DEFAULTCONFIG_BEGIN //CHUNK_ANDROID_DEFAULTCONFIG_END } diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index c238d1b3615..8f0f2c6cb68 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -76,7 +76,7 @@ ext.getGodotEditorVersion = { -> String editorVersion = project.hasProperty("godot_editor_version") ? project.property("godot_editor_version") : "" if (editorVersion == null || editorVersion.isEmpty()) { // Try the library version first - editorVersion = getGodotLibraryVersion() + editorVersion = getGodotLibraryVersionName() if (editorVersion.isEmpty()) { // Fallback value. @@ -86,9 +86,24 @@ ext.getGodotEditorVersion = { -> return editorVersion } +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 requiredKeys -> // Attempt to read the version from the `version.py` file. - String libraryVersion = "" + String libraryVersionName = "" + int libraryVersionCode = 1 File versionFile = new File("../../../version.py") if (versionFile.isFile()) { @@ -109,15 +124,20 @@ ext.generateGodotLibraryVersion = { List requiredKeys -> } if (requiredKeys.empty) { - libraryVersion = map.values().join(".") + libraryVersionName = map.values().join(".") + try { + libraryVersionCode = Integer.parseInt(map["patch"]) + + (Integer.parseInt(map["minor"]) * 100) + + (Integer.parseInt(map["major"]) * 10000) + } catch (NumberFormatException ignore) {} } } - if (libraryVersion.isEmpty()) { + if (libraryVersionName.isEmpty()) { // Fallback value in case we're unable to read the file. - libraryVersion = "custom_build" + libraryVersionName = "custom_build" } - return libraryVersion + return [libraryVersionName, libraryVersionCode] } ext.getGodotLibraryVersion = { -> diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 83bc68c9929..5ee63e0fc6c 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -26,21 +26,22 @@ allprojects { ext { supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] - supportedTargets = ["release", "debug"] + supportedTargets = ["release", "release_debug", "debug"] + supportedFlavors = ["toolsEnabled", "toolsDisabled"] - // Used by gradle to specify which architecture to build for by default when running `./gradlew build`. - // This command is usually used by Android Studio. + // Used by gradle to specify which architecture to build for by default when running + // `./gradlew build` (this command is usually used by Android Studio). // If building manually on the command line, it's recommended to use the - // `./gradlew generateGodotTemplates` build command instead after running the `scons` command. - // The defaultAbi must be one of the {supportedAbis} values. - defaultAbi = "arm64v8" + // `./gradlew generateGodotTemplates` build command instead after running the `scons` command(s). + // The {selectedAbis} values must be from the {supportedAbis} values. + selectedAbis = ["arm64v8"] } def rootDir = "../../.." def binDir = "$rootDir/bin/" -def getSconsTaskName(String buildType) { - return "compileGodotNativeLibs" + buildType.capitalize() +def getSconsTaskName(String flavor, String buildType, String abi) { + return "compileGodotNativeLibs" + flavor.capitalize() + buildType.capitalize() + abi.capitalize() } /** @@ -70,7 +71,7 @@ task copyReleaseBinaryToBin(type: Copy) { * Depends on the library build task to ensure the AAR file is generated prior to copying. */ task copyDebugAARToAppModule(type: Copy) { - dependsOn ':lib:assembleDebug' + dependsOn ':lib:assembleToolsDisabledDebug' from('lib/build/outputs/aar') into('app/libs/debug') include('godot-lib.debug.aar') @@ -81,7 +82,7 @@ task copyDebugAARToAppModule(type: Copy) { * Depends on the library build task to ensure the AAR file is generated prior to copying. */ task copyDebugAARToBin(type: Copy) { - dependsOn ':lib:assembleDebug' + dependsOn ':lib:assembleToolsDisabledDebug' from('lib/build/outputs/aar') into(binDir) include('godot-lib.debug.aar') @@ -92,7 +93,7 @@ task copyDebugAARToBin(type: Copy) { * Depends on the library build task to ensure the AAR file is generated prior to copying. */ task copyReleaseAARToAppModule(type: Copy) { - dependsOn ':lib:assembleRelease' + dependsOn ':lib:assembleToolsDisabledRelease' from('lib/build/outputs/aar') into('app/libs/release') include('godot-lib.release.aar') @@ -103,7 +104,7 @@ task copyReleaseAARToAppModule(type: Copy) { * Depends on the library build task to ensure the AAR file is generated prior to copying. */ task copyReleaseAARToBin(type: Copy) { - dependsOn ':lib:assembleRelease' + dependsOn ':lib:assembleToolsDisabledRelease' from('lib/build/outputs/aar') into(binDir) include('godot-lib.release.aar') @@ -130,8 +131,12 @@ def templateExcludedBuildTask() { def excludedTasks = [] if (!isAndroidStudio()) { logger.lifecycle("Excluding Android studio build tasks") - for (String buildType : supportedTargets) { - excludedTasks += ":lib:" + getSconsTaskName(buildType) + for (String flavor : supportedFlavors) { + for (String buildType : supportedTargets) { + for (String abi : selectedAbis) { + excludedTasks += ":lib:" + getSconsTaskName(flavor, buildType, abi) + } + } } } return excludedTasks @@ -142,6 +147,10 @@ def templateBuildTasks() { // Only build the apks and aar files for which we have native shared libraries. for (String target : supportedTargets) { + if (target == "release_debug") { + // 'release_debug' is not supported for generating templates. + continue + } File targetLibs = new File("lib/libs/" + target) if (targetLibs != null && targetLibs.isDirectory() @@ -167,6 +176,56 @@ def isAndroidStudio() { return sysProps != null && sysProps['idea.platform.prefix'] != null } +task copyEditorDebugBinaryToBin(type: Copy) { + dependsOn ':editor:assembleDebug' + from('editor/build/outputs/apk/debug') + into(binDir) + include('android_editor_debug.apk') +} + +task copyEditorRelease_debugBinaryToBin(type: Copy) { + dependsOn ':editor:assembleRelease_debug' + from('editor/build/outputs/apk/release_debug') + into(binDir) + include('android_editor_release_debug.apk') +} + +task copyEditorReleaseBinaryToBin(type: Copy) { + dependsOn ':editor:assembleRelease' + from('editor/build/outputs/apk/release') + into(binDir) + include('android_editor_release.apk') +} + +/** + * Generate the Godot Editor Android apk. + * + * Note: The Godot 'tools' shared libraries must have been generated (via scons) prior to running + * this gradle task. The task will only build the apk(s) for which the shared libraries is + * available. + */ +task generateGodotEditor { + gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() + + def tasks = [] + + for (String target : supportedTargets) { + if (target == "release") { + // Skip release for now since we need to provide signing information. + continue + } + File targetLibs = new File("lib/libs/tools/" + target) + if (targetLibs != null + && targetLibs.isDirectory() + && targetLibs.listFiles() != null + && targetLibs.listFiles().length > 0) { + tasks += "copyEditor${target.capitalize()}BinaryToBin" + } + } + + dependsOn = tasks +} + /** * Master task used to coordinate the tasks defined above to generate the set of Godot templates. */ diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle new file mode 100644 index 00000000000..f5a658b7614 --- /dev/null +++ b/platform/android/java/editor/build.gradle @@ -0,0 +1,55 @@ +// Gradle build config for Godot Engine's Android port. +apply plugin: 'com.android.application' + +dependencies { + implementation libraries.kotlinStdLib + implementation libraries.androidxFragment + implementation project(":lib") +} + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + ndkVersion versions.ndkVersion + + defaultConfig { + // The 'applicationId' suffix allows to install Godot 3.x(v3) and 4.x(v4) on the same device + applicationId "org.godotengine.editor.v3" + versionCode getGodotLibraryVersionCode() + versionName getGodotLibraryVersionName() + minSdkVersion versions.minSdk + //noinspection ExpiredTargetSdkVersion - Restrict to version 29 until https://github.com/godotengine/godot/pull/51815 is submitted + targetSdkVersion 29 // versions.targetSdk + + missingDimensionStrategy 'tools', 'toolsEnabled' + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + + buildTypes { + debug { + applicationIdSuffix ".debug" + } + + release_debug { + initWith debug + applicationIdSuffix ".releaseDebug" + } + } + + packagingOptions { + // 'doNotStrip' is enabled for development within Android Studio + if (shouldNotStrip()) { + doNotStrip '**/*.so' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { output -> + output.outputFileName = "android_editor_${variant.name}.apk" + } + } +} diff --git a/platform/android/java/editor/src/debug/res/values/strings.xml b/platform/android/java/editor/src/debug/res/values/strings.xml new file mode 100644 index 00000000000..4871bcfc091 --- /dev/null +++ b/platform/android/java/editor/src/debug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Godot Editor (debug) + diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..0708ffa32f4 --- /dev/null +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java new file mode 100644 index 00000000000..b3a340cc64e --- /dev/null +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java @@ -0,0 +1,110 @@ +/*************************************************************************/ +/* GodotEditor.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.editor; + +import org.godotengine.godot.FullScreenGodotApp; +import org.godotengine.godot.utils.PermissionsUtil; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Debug; + +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Base class for the Godot Android Editor activities. + * + * This provides the basic templates for the activities making up this application. + * Each derived activity runs in its own process, which enable up to have several instances of + * the Godot engine up and running at the same time. + * + * It also plays the role of the primary editor window. + */ +public class GodotEditor extends FullScreenGodotApp { + private static final boolean WAIT_FOR_DEBUGGER = false; + private static final String COMMAND_LINE_PARAMS = "command_line_params"; + + private static final String EDITOR_ARG = "--editor"; + private static final String PROJECT_MANAGER_ARG = "--project-manager"; + + private final List commandLineParams = new ArrayList<>(); + + @Override + public void onCreate(Bundle savedInstanceState) { + PermissionsUtil.requestManifestPermissions(this); + + String[] params = getIntent().getStringArrayExtra(COMMAND_LINE_PARAMS); + updateCommandLineParams(params); + + if (BuildConfig.BUILD_TYPE.equals("debug") && WAIT_FOR_DEBUGGER) { + Debug.waitForDebugger(); + } + super.onCreate(savedInstanceState); + } + + private void updateCommandLineParams(@Nullable String[] args) { + // Update the list of command line params with the new args + commandLineParams.clear(); + if (args != null && args.length > 0) { + commandLineParams.addAll(Arrays.asList(args)); + } + } + + @Override + public List getCommandLine() { + return commandLineParams; + } + + @Override + public void onNewGodotInstanceRequested(String[] args) { + // Parse the arguments to figure out which activity to start. + Class targetClass = GodotGame.class; + for (String arg : args) { + if (EDITOR_ARG.equals(arg)) { + targetClass = GodotEditor.class; + break; + } + + if (PROJECT_MANAGER_ARG.equals(arg)) { + targetClass = GodotProjectManager.class; + break; + } + } + + // Launch a new activity + Intent newInstance = new Intent(this, targetClass).putExtra(COMMAND_LINE_PARAMS, args); + startActivity(newInstance); + } +} diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java new file mode 100644 index 00000000000..5a0be391cf4 --- /dev/null +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* GodotGame.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.editor; + +/** + * Drives the 'run project' window of the Godot Editor. + */ +public class GodotGame extends GodotEditor { +} diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.java b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.java new file mode 100644 index 00000000000..d30f66bb8c6 --- /dev/null +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.java @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* GodotProjectManager.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.editor; + +/** + * Launcher activity for the Godot Android Editor. + * + * It presents the user with the project manager interface. + * Upon selection of a project, this activity (via its parent logic) starts the + * {@link GodotEditor} activity. + */ +public class GodotProjectManager extends GodotEditor { +} diff --git a/platform/android/java/editor/src/main/res/values/strings.xml b/platform/android/java/editor/src/main/res/values/strings.xml new file mode 100644 index 00000000000..fed1308ef63 --- /dev/null +++ b/platform/android/java/editor/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Godot Editor + diff --git a/platform/android/java/editor/src/release_debug/res/values/strings.xml b/platform/android/java/editor/src/release_debug/res/values/strings.xml new file mode 100644 index 00000000000..c2629aac983 --- /dev/null +++ b/platform/android/java/editor/src/release_debug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Godot Editor (release-debug) + diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 120a40a31d1..ee28f44b4e4 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -18,14 +18,13 @@ def pathToRootDir = "../../../../" android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools - ndkVersion versions.ndkVersion defaultConfig { minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk - manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()] + manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersionName()] } namespace = "org.godotengine.godot" @@ -35,6 +34,18 @@ android { targetCompatibility versions.javaVersion } + buildTypes { + release_debug { + initWith debug + } + } + + flavorDimensions "tools" + productFlavors { + toolsEnabled {} + toolsDisabled {} + } + lintOptions { abortOnError false disable 'MissingTranslation', 'UnusedResources' @@ -58,24 +69,39 @@ android { aidl.srcDirs = ['aidl'] assets.srcDirs = ['assets'] } + debug.jniLibs.srcDirs = ['libs/debug'] + release_debug.jniLibs.srcDirs = ['libs/release_debug'] release.jniLibs.srcDirs = ['libs/release'] + + // Tools enabled jni library + toolsEnabledDebug.jniLibs.srcDirs = ['libs/tools/debug'] + toolsEnabledRelease_debug.jniLibs.srcDirs = ['libs/tools/release_debug'] + toolsEnabledRelease.jniLibs.srcDirs = ['libs/tools/release'] } libraryVariants.all { variant -> - variant.outputs.all { output -> - output.outputFileName = "godot-lib.${variant.name}.aar" + def flavorName = variant.getFlavorName() + def buildType = variant.buildType.name.capitalize() + + if (flavorName == null || flavorName == "") { + throw new GradleException("Invalid product flavor: $flavorName") } - def buildType = variant.buildType.name.capitalize() + boolean toolsFlag = flavorName == "toolsEnabled" def releaseTarget = buildType.toLowerCase() if (releaseTarget == null || releaseTarget == "") { throw new GradleException("Invalid build type: " + buildType) } - if (!supportedAbis.contains(defaultAbi)) { - throw new GradleException("Invalid default abi: " + defaultAbi) + // Update the name of the generated library + def outputSuffix = "${releaseTarget}.aar" + if (toolsFlag) { + outputSuffix = "tools.$outputSuffix" + } + variant.outputs.all { output -> + output.outputFileName = "godot-lib.${outputSuffix}" } // Find scons' executable path @@ -110,15 +136,21 @@ android { logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}") } - // Creating gradle task to generate the native libraries for the default abi. - def taskName = getSconsTaskName(buildType) - tasks.create(name: taskName, type: Exec) { - executable sconsExecutableFile.absolutePath - args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${defaultAbi}", "-j" + Runtime.runtime.availableProcessors() - } + for (String selectedAbi : selectedAbis) { + if (!supportedAbis.contains(selectedAbi)) { + throw new GradleException("Invalid selected abi: " + selectedAbi) + } - // Schedule the tasks so the generated libs are present before the aar file is packaged. - tasks["merge${buildType}JniLibFolders"].dependsOn taskName + // Creating gradle task to generate the native libraries for the selected abi. + def taskName = getSconsTaskName(flavorName, buildType, selectedAbi) + tasks.create(name: taskName, type: Exec) { + executable sconsExecutableFile.absolutePath + args "--directory=${pathToRootDir}", "platform=android", "tools=${toolsFlag}", "target=${releaseTarget}", "android_arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors() + } + + // Schedule the tasks so the generated libs are present before the aar file is packaged. + tasks["merge${flavorName.capitalize()}${buildType}JniLibFolders"].dependsOn taskName + } } // TODO: Enable when issues with AGP 7.1+ are resolved (https://github.com/GodotVR/godot_openxr/issues/187). diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 4c1ae6f19d3..cb4c7866cd6 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -47,7 +47,6 @@ import android.app.AlertDialog; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipboardManager; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -449,9 +448,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } public void restart() { - if (godotHost != null) { - godotHost.onGodotRestartRequested(this); - } + runOnUiThread(() -> { + if (godotHost != null) { + godotHost.onGodotRestartRequested(this); + } + }); } public void alert(final String message, final String title) { @@ -995,9 +996,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC private void forceQuit() { // TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each // native Godot components that is started in Godot#onVideoInit. - if (godotHost != null) { - godotHost.onGodotForceQuit(this); - } + runOnUiThread(() -> { + if (godotHost != null) { + godotHost.onGodotForceQuit(this); + } + }); } private boolean obbIsCorrupted(String f, String main_pack_md5) { @@ -1146,7 +1149,17 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal)); } + public void initInputDevices() { mView.initInputDevices(); } + + @Keep + private void createNewGodotInstance(String[] args) { + runOnUiThread(() -> { + if (godotHost != null) { + godotHost.onNewGodotInstanceRequested(args); + } + }); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 8e8f9933699..2e7b67194f8 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -60,8 +60,16 @@ public interface GodotHost { default void onGodotForceQuit(Godot instance) {} /** - * Invoked on the GL thread when the Godot instance wants to be restarted. It's up to the host + * Invoked on the UI thread when the Godot instance wants to be restarted. It's up to the host * to perform the appropriate action(s). */ default void onGodotRestartRequested(Godot instance) {} + + /** + * Invoked on the UI thread when a new Godot instance is requested. It's up to the host to + * perform the appropriate action(s). + * + * @param args Arguments used to initialize the new instance. + */ + default void onNewGodotInstanceRequested(String[] args) {} } diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index 34925684dab..f96af240752 100644 --- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -17,3 +17,5 @@ target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${GODOT_ROOT_DIR} ${GODOT_ROOT_DIR}/modules/gdnative/include) + +add_definitions(-DUNIX_ENABLED -DTOOLS_ENABLED) diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle index 158bb2b98ef..535a611d9ed 100644 --- a/platform/android/java/nativeSrcsConfigs/build.gradle +++ b/platform/android/java/nativeSrcsConfigs/build.gradle @@ -6,6 +6,7 @@ plugins { android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools + ndkVersion versions.ndkVersion defaultConfig { minSdkVersion versions.minSdk diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle index 584b6269009..56e1b6fd3a4 100644 --- a/platform/android/java/settings.gradle +++ b/platform/android/java/settings.gradle @@ -4,6 +4,7 @@ rootProject.name = "Godot" include ':app' include ':lib' include ':nativeSrcsConfigs' +include ':editor' include ':assetPacks:installTime' project(':assetPacks:installTime').projectDir = file("app/assetPacks/installTime") diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 8a46ea145a1..6f301a0c8a5 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -148,6 +148,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) { // lets cleanup + if (java_class_wrapper) { + memdelete(java_class_wrapper); + } if (godot_io_java) { delete godot_io_java; } @@ -159,6 +162,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env delete input_handler; } if (os_android) { + os_android->main_loop_end(); delete os_android; } } @@ -188,7 +192,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc } } - Error err = Main::setup("apk", cmdlen, (char **)cmdline, false); + Error err = Main::setup(OS_Android::ANDROID_EXEC_PATH, cmdlen, (char **)cmdline, false); if (cmdline) { if (j_cmdline) { for (int i = 0; i < cmdlen; ++i) { diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 943a8d67cc7..4b2c1de5098 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -80,6 +80,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V"); _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); + _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)V"); // get some Activity method pointers... _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); @@ -369,3 +370,16 @@ void GodotJavaWrapper::vibrate(int p_duration_ms) { env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); } } + +void GodotJavaWrapper::create_new_godot_instance(List args) { + if (_create_new_godot_instance) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + + jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); + for (int i = 0; i < args.size(); i++) { + env->SetObjectArrayElement(jargs, i, env->NewStringUTF(args[i].utf8().get_data())); + } + env->CallVoidMethod(godot_instance, _create_new_godot_instance, jargs); + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 47071300ded..a64677d3c9e 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -37,6 +37,7 @@ #include #include +#include "core/list.h" #include "string_android.h" // Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++ @@ -70,6 +71,7 @@ private: jmethodID _on_godot_setup_completed = 0; jmethodID _on_godot_main_loop_started = 0; jmethodID _get_class_loader = 0; + jmethodID _create_new_godot_instance = 0; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -106,6 +108,7 @@ public: bool is_activity_resumed(); void vibrate(int p_duration_ms); String get_input_fallback_mapping(); + void create_new_godot_instance(List args); }; #endif /* !JAVA_GODOT_WRAPPER_H */ diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 67194809234..569644dfa51 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -36,6 +36,7 @@ #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" #include "main/main.h" +#include "scene/main/scene_tree.h" #include "servers/visual/visual_server_raster.h" #include "servers/visual/visual_server_wrap_mt.h" @@ -50,6 +51,8 @@ #include "java_godot_io_wrapper.h" #include "java_godot_wrapper.h" +const char *OS_Android::ANDROID_EXEC_PATH = "apk"; + String _remove_symlink(const String &dir) { // Workaround for Android 6.0+ using a symlink. // Save the current directory. @@ -100,17 +103,27 @@ const char *OS_Android::get_audio_driver_name(int p_driver) const { void OS_Android::initialize_core() { OS_Unix::initialize_core(); +#ifdef TOOLS_ENABLED + FileAccess::make_default(FileAccess::ACCESS_RESOURCES); +#else if (use_apk_expansion) FileAccess::make_default(FileAccess::ACCESS_RESOURCES); else { FileAccess::make_default(FileAccess::ACCESS_RESOURCES); } +#endif FileAccess::make_default(FileAccess::ACCESS_USERDATA); FileAccess::make_default(FileAccess::ACCESS_FILESYSTEM); + +#ifdef TOOLS_ENABLED + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); +#else if (use_apk_expansion) DirAccess::make_default(DirAccess::ACCESS_RESOURCES); else DirAccess::make_default(DirAccess::ACCESS_RESOURCES); +#endif + DirAccess::make_default(DirAccess::ACCESS_USERDATA); DirAccess::make_default(DirAccess::ACCESS_FILESYSTEM); @@ -279,6 +292,15 @@ void OS_Android::set_keep_screen_on(bool p_enabled) { godot_java->set_keep_screen_on(p_enabled); } +void OS_Android::set_low_processor_usage_mode(bool p_enabled) { +#ifdef TOOLS_ENABLED + // Disabled as it causes flickers. We also expect the devices running Godot in editor mode to be high end. + OS_Unix::set_low_processor_usage_mode(false); +#else + OS_Unix::set_low_processor_usage_mode(p_enabled); +#endif +} + Size2 OS_Android::get_window_size() const { return Vector2(default_videomode.width, default_videomode.height); } @@ -313,8 +335,13 @@ bool OS_Android::main_loop_iterate() { } void OS_Android::main_loop_end() { - if (main_loop) + if (main_loop) { + SceneTree *scene_tree = Object::cast_to(main_loop); + if (scene_tree) { + scene_tree->quit(); + } main_loop->finish(); + } } void OS_Android::main_loop_focusout() { @@ -393,7 +420,11 @@ Error OS_Android::shell_open(String p_uri) { } String OS_Android::get_resource_dir() const { +#ifdef TOOLS_ENABLED + return OS_Unix::get_resource_dir(); +#else return "/"; //android has its own filesystem for resources inside the APK +#endif } String OS_Android::get_locale() const { @@ -452,6 +483,14 @@ String OS_Android::get_data_path() const { return get_user_data_dir(); } +String OS_Android::get_executable_path() const { + // Since unix process creation is restricted on Android, we bypass + // OS_Unix::get_executable_path() so we can return ANDROID_EXEC_PATH. + // Detection of ANDROID_EXEC_PATH allows to handle process creation in an Android compliant + // manner. + return OS::get_executable_path(); +} + String OS_Android::get_user_data_dir() const { if (data_dir_cache != String()) return data_dir_cache; @@ -524,6 +563,10 @@ void OS_Android::vibrate_handheld(int p_duration_ms) { godot_java->vibrate(p_duration_ms); } +String OS_Android::get_config_path() const { + return get_user_data_dir().plus_file("config"); +} + bool OS_Android::_check_internal_feature_support(const String &p_feature) { if (p_feature == "mobile") { //TODO support etc2 only if GLES3 driver is selected @@ -567,5 +610,18 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god AudioDriverManager::add_driver(&audio_driver_android); } +Error OS_Android::execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { + if (p_path == ANDROID_EXEC_PATH) { + return create_instance(p_arguments, r_child_id); + } else { + return OS_Unix::execute(p_path, p_arguments, p_blocking, r_child_id, r_pipe, r_exitcode, read_stderr, p_pipe_mutex, p_open_console); + } +} + +Error OS_Android::create_instance(const List &p_arguments, ProcessID *r_child_id) { + godot_java->create_new_godot_instance(p_arguments); + return OK; +} + OS_Android::~OS_Android() { } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index cd88f1a9c81..07fe1b58ce3 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -70,6 +70,8 @@ class OS_Android : public OS_Unix { bool transparency_enabled = false; public: + static const char *ANDROID_EXEC_PATH; + // functions used by main to initialize/deinitialize the OS virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; @@ -111,6 +113,7 @@ public: virtual void get_fullscreen_mode_list(List *p_list, int p_screen = 0) const; virtual void set_keep_screen_on(bool p_enabled); + virtual void set_low_processor_usage_mode(bool p_enabled); virtual Size2 get_window_size() const; virtual Rect2 get_window_safe_area() const; @@ -144,6 +147,7 @@ public: virtual ScreenOrientation get_screen_orientation() const; virtual Error shell_open(String p_uri); + virtual String get_executable_path() const; virtual String get_user_data_dir() const; virtual String get_data_path() const; virtual String get_cache_path() const; @@ -173,9 +177,16 @@ public: virtual String get_joy_guid(int p_device) const; void vibrate_handheld(int p_duration_ms); + virtual String get_config_path() const; + + virtual Error execute(const String &p_path, const List &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false); + virtual bool _check_internal_feature_support(const String &p_feature); OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion); ~OS_Android(); + +private: + Error create_instance(const List &p_arguments, ProcessID *r_child_id); }; #endif diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1c1a479b0bd..2eff6e7f023 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1388,6 +1388,9 @@ Vector2 Viewport::_get_window_offset() const { } Ref Viewport::_make_input_local(const Ref &ev) { + if (ev.is_null()) { + return ev; + } Vector2 vp_ofs = _get_window_offset(); Transform2D ai = get_final_transform().affine_inverse() * _get_input_pre_xform();