Selaa lähdekoodia

Android port of the Godot Editor

These set of changes focus primarily on getting the core logic and overall Godot Editor UI and functionality up and running natively on Android devices.
UI tweaks / cleanup / polish, as well configuration for Android specific functionality / restrictions will be addressed in follow-up PRs iteratively based on feedback.

Co-authored-by: thebestnom <[email protected]>
Fredy Huya-Kouadio 4 vuotta sitten
vanhempi
commit
cb0b2aefc3
25 muutettua tiedostoa jossa 609 lisäystä ja 47 poistoa
  1. 3 0
      editor/editor_node.cpp
  2. 9 2
      platform/android/SCsub
  3. 3 0
      platform/android/java/app/build.gradle
  4. 26 6
      platform/android/java/app/config.gradle
  5. 73 14
      platform/android/java/build.gradle
  6. 55 0
      platform/android/java/editor/build.gradle
  7. 4 0
      platform/android/java/editor/src/debug/res/values/strings.xml
  8. 67 0
      platform/android/java/editor/src/main/AndroidManifest.xml
  9. 110 0
      platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java
  10. 37 0
      platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java
  11. 41 0
      platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.java
  12. 4 0
      platform/android/java/editor/src/main/res/values/strings.xml
  13. 4 0
      platform/android/java/editor/src/release_debug/res/values/strings.xml
  14. 47 15
      platform/android/java/lib/build.gradle
  15. 20 7
      platform/android/java/lib/src/org/godotengine/godot/Godot.java
  16. 9 1
      platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
  17. 2 0
      platform/android/java/nativeSrcsConfigs/CMakeLists.txt
  18. 1 0
      platform/android/java/nativeSrcsConfigs/build.gradle
  19. 1 0
      platform/android/java/settings.gradle
  20. 5 1
      platform/android/java_godot_lib_jni.cpp
  21. 14 0
      platform/android/java_godot_wrapper.cpp
  22. 3 0
      platform/android/java_godot_wrapper.h
  23. 57 1
      platform/android/os_android.cpp
  24. 11 0
      platform/android/os_android.h
  25. 3 0
      scene/main/viewport.cpp

+ 3 - 0
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(false);
 			get_tree()->get_root()->set_as_audio_listener_2d(false);
 			get_tree()->get_root()->set_as_audio_listener_2d(false);
 			get_tree()->set_auto_accept_quit(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("files_dropped", this, "_dropped_files");
 			get_tree()->connect("global_menu_action", this, "_global_menu_action");
 			get_tree()->connect("global_menu_action", this, "_global_menu_action");
 
 

+ 9 - 2
platform/android/SCsub

@@ -50,10 +50,17 @@ else:
 if lib_arch_dir != "":
 if lib_arch_dir != "":
     if env["target"] == "release":
     if env["target"] == "release":
         lib_type_dir = "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"
         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(
     env_android.Command(
         out_dir + "/libgodot_android.so", "#bin/libgodot" + env["SHLIBSUFFIX"], Move("$TARGET", "$SOURCE")
         out_dir + "/libgodot_android.so", "#bin/libgodot" + env["SHLIBSUFFIX"], Move("$TARGET", "$SOURCE")
     )
     )

+ 3 - 0
platform/android/java/app/build.gradle

@@ -78,6 +78,7 @@ dependencies {
 android {
 android {
     compileSdkVersion versions.compileSdk
     compileSdkVersion versions.compileSdk
     buildToolsVersion versions.buildTools
     buildToolsVersion versions.buildTools
+    ndkVersion versions.ndkVersion
 
 
     compileOptions {
     compileOptions {
         sourceCompatibility versions.javaVersion
         sourceCompatibility versions.javaVersion
@@ -105,6 +106,8 @@ android {
         versionName getExportVersionName()
         versionName getExportVersionName()
         minSdkVersion getExportMinSdkVersion()
         minSdkVersion getExportMinSdkVersion()
         targetSdkVersion getExportTargetSdkVersion()
         targetSdkVersion getExportTargetSdkVersion()
+
+        missingDimensionStrategy 'tools', 'toolsDisabled'
 //CHUNK_ANDROID_DEFAULTCONFIG_BEGIN
 //CHUNK_ANDROID_DEFAULTCONFIG_BEGIN
 //CHUNK_ANDROID_DEFAULTCONFIG_END
 //CHUNK_ANDROID_DEFAULTCONFIG_END
     }
     }

+ 26 - 6
platform/android/java/app/config.gradle

@@ -76,7 +76,7 @@ ext.getGodotEditorVersion = { ->
     String editorVersion = project.hasProperty("godot_editor_version") ? project.property("godot_editor_version") : ""
     String editorVersion = project.hasProperty("godot_editor_version") ? project.property("godot_editor_version") : ""
     if (editorVersion == null || editorVersion.isEmpty()) {
     if (editorVersion == null || editorVersion.isEmpty()) {
         // Try the library version first
         // Try the library version first
-        editorVersion = getGodotLibraryVersion()
+        editorVersion = getGodotLibraryVersionName()
 
 
         if (editorVersion.isEmpty()) {
         if (editorVersion.isEmpty()) {
             // Fallback value.
             // Fallback value.
@@ -86,9 +86,24 @@ ext.getGodotEditorVersion = { ->
     return editorVersion
     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<String> requiredKeys ->
 ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
     // Attempt to read the version from the `version.py` file.
     // Attempt to read the version from the `version.py` file.
-    String libraryVersion = ""
+    String libraryVersionName = ""
+    int libraryVersionCode = 1
 
 
     File versionFile = new File("../../../version.py")
     File versionFile = new File("../../../version.py")
     if (versionFile.isFile()) {
     if (versionFile.isFile()) {
@@ -109,15 +124,20 @@ ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
         }
         }
 
 
         if (requiredKeys.empty) {
         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.
         // Fallback value in case we're unable to read the file.
-        libraryVersion = "custom_build"
+        libraryVersionName = "custom_build"
     }
     }
-    return libraryVersion
+    return [libraryVersionName, libraryVersionCode]
 }
 }
 
 
 ext.getGodotLibraryVersion = { ->
 ext.getGodotLibraryVersion = { ->

+ 73 - 14
platform/android/java/build.gradle

@@ -26,21 +26,22 @@ allprojects {
 
 
 ext {
 ext {
     supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"]
     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
     // 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 rootDir = "../../.."
 def binDir = "$rootDir/bin/"
 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.
  * Depends on the library build task to ensure the AAR file is generated prior to copying.
  */
  */
 task copyDebugAARToAppModule(type: Copy) {
 task copyDebugAARToAppModule(type: Copy) {
-    dependsOn ':lib:assembleDebug'
+    dependsOn ':lib:assembleToolsDisabledDebug'
     from('lib/build/outputs/aar')
     from('lib/build/outputs/aar')
     into('app/libs/debug')
     into('app/libs/debug')
     include('godot-lib.debug.aar')
     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.
  * Depends on the library build task to ensure the AAR file is generated prior to copying.
  */
  */
 task copyDebugAARToBin(type: Copy) {
 task copyDebugAARToBin(type: Copy) {
-    dependsOn ':lib:assembleDebug'
+    dependsOn ':lib:assembleToolsDisabledDebug'
     from('lib/build/outputs/aar')
     from('lib/build/outputs/aar')
     into(binDir)
     into(binDir)
     include('godot-lib.debug.aar')
     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.
  * Depends on the library build task to ensure the AAR file is generated prior to copying.
  */
  */
 task copyReleaseAARToAppModule(type: Copy) {
 task copyReleaseAARToAppModule(type: Copy) {
-    dependsOn ':lib:assembleRelease'
+    dependsOn ':lib:assembleToolsDisabledRelease'
     from('lib/build/outputs/aar')
     from('lib/build/outputs/aar')
     into('app/libs/release')
     into('app/libs/release')
     include('godot-lib.release.aar')
     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.
  * Depends on the library build task to ensure the AAR file is generated prior to copying.
  */
  */
 task copyReleaseAARToBin(type: Copy) {
 task copyReleaseAARToBin(type: Copy) {
-    dependsOn ':lib:assembleRelease'
+    dependsOn ':lib:assembleToolsDisabledRelease'
     from('lib/build/outputs/aar')
     from('lib/build/outputs/aar')
     into(binDir)
     into(binDir)
     include('godot-lib.release.aar')
     include('godot-lib.release.aar')
@@ -130,8 +131,12 @@ def templateExcludedBuildTask() {
     def excludedTasks = []
     def excludedTasks = []
     if (!isAndroidStudio()) {
     if (!isAndroidStudio()) {
         logger.lifecycle("Excluding Android studio build tasks")
         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
     return excludedTasks
@@ -142,6 +147,10 @@ def templateBuildTasks() {
 
 
     // Only build the apks and aar files for which we have native shared libraries.
     // Only build the apks and aar files for which we have native shared libraries.
     for (String target : supportedTargets) {
     for (String target : supportedTargets) {
+        if (target == "release_debug") {
+            // 'release_debug' is not supported for generating templates.
+            continue
+        }
         File targetLibs = new File("lib/libs/" + target)
         File targetLibs = new File("lib/libs/" + target)
         if (targetLibs != null
         if (targetLibs != null
             && targetLibs.isDirectory()
             && targetLibs.isDirectory()
@@ -167,6 +176,56 @@ def isAndroidStudio() {
     return sysProps != null && sysProps['idea.platform.prefix'] != null
     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.
  * Master task used to coordinate the tasks defined above to generate the set of Godot templates.
  */
  */

+ 55 - 0
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"
+        }
+    }
+}

+ 4 - 0
platform/android/java/editor/src/debug/res/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<string name="godot_editor_name_string">Godot Editor (debug)</string>
+</resources>

+ 67 - 0
platform/android/java/editor/src/main/AndroidManifest.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="org.godotengine.editor"
+    android:installLocation="auto">
+
+    <supports-screens
+        android:largeScreens="true"
+        android:normalScreens="true"
+        android:smallScreens="true"
+        android:xlargeScreens="true" />
+
+    <uses-feature
+        android:glEsVersion="0x00020000"
+        android:required="true" />
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/icon"
+        android:label="@string/godot_editor_name_string"
+        tools:ignore="GoogleAppIndexingWarning"
+        android:requestLegacyExternalStorage="true">
+
+        <activity
+            android:name=".GodotProjectManager"
+            android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
+            android:launchMode="singleTask"
+            android:resizeableActivity="false"
+            android:screenOrientation="landscape"
+            android:exported="true"
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+            android:process=":GodotProjectManager">
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".GodotEditor"
+            android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
+            android:process=":GodotEditor"
+            android:launchMode="singleTask"
+            android:resizeableActivity="false"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+        </activity>
+
+        <activity
+            android:name=".GodotGame"
+            android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
+            android:label="@string/godot_project_name_string"
+            android:process=":GodotGame"
+            android:launchMode="singleTask"
+            android:resizeableActivity="false"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+        </activity>
+
+    </application>
+
+</manifest>

+ 110 - 0
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<String> 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<String> 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);
+	}
+}

+ 37 - 0
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 {
+}

+ 41 - 0
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 {
+}

+ 4 - 0
platform/android/java/editor/src/main/res/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<string name="godot_editor_name_string">Godot Editor</string>
+</resources>

+ 4 - 0
platform/android/java/editor/src/release_debug/res/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<string name="godot_editor_name_string">Godot Editor (release-debug)</string>
+</resources>

+ 47 - 15
platform/android/java/lib/build.gradle

@@ -18,14 +18,13 @@ def pathToRootDir = "../../../../"
 android {
 android {
     compileSdkVersion versions.compileSdk
     compileSdkVersion versions.compileSdk
     buildToolsVersion versions.buildTools
     buildToolsVersion versions.buildTools
-
     ndkVersion versions.ndkVersion
     ndkVersion versions.ndkVersion
 
 
     defaultConfig {
     defaultConfig {
         minSdkVersion versions.minSdk
         minSdkVersion versions.minSdk
         targetSdkVersion versions.targetSdk
         targetSdkVersion versions.targetSdk
 
 
-        manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()]
+        manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersionName()]
     }
     }
 
 
     namespace = "org.godotengine.godot"
     namespace = "org.godotengine.godot"
@@ -35,6 +34,18 @@ android {
         targetCompatibility versions.javaVersion
         targetCompatibility versions.javaVersion
     }
     }
 
 
+    buildTypes {
+        release_debug {
+            initWith debug
+        }
+    }
+
+    flavorDimensions "tools"
+    productFlavors {
+        toolsEnabled {}
+        toolsDisabled {}
+    }
+
     lintOptions {
     lintOptions {
         abortOnError false
         abortOnError false
         disable 'MissingTranslation', 'UnusedResources'
         disable 'MissingTranslation', 'UnusedResources'
@@ -58,24 +69,39 @@ android {
             aidl.srcDirs = ['aidl']
             aidl.srcDirs = ['aidl']
             assets.srcDirs = ['assets']
             assets.srcDirs = ['assets']
         }
         }
+
         debug.jniLibs.srcDirs = ['libs/debug']
         debug.jniLibs.srcDirs = ['libs/debug']
+        release_debug.jniLibs.srcDirs = ['libs/release_debug']
         release.jniLibs.srcDirs = ['libs/release']
         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 ->
     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()
         def releaseTarget = buildType.toLowerCase()
         if (releaseTarget == null || releaseTarget == "") {
         if (releaseTarget == null || releaseTarget == "") {
             throw new GradleException("Invalid build type: " + buildType)
             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
         // Find scons' executable path
@@ -110,15 +136,21 @@ android {
             logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
             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)
+            }
+
+            // 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${buildType}JniLibFolders"].dependsOn taskName
+            // 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).
     // TODO: Enable when issues with AGP 7.1+ are resolved (https://github.com/GodotVR/godot_openxr/issues/187).

+ 20 - 7
platform/android/java/lib/src/org/godotengine/godot/Godot.java

@@ -47,7 +47,6 @@ import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.app.PendingIntent;
 import android.content.ClipData;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.ClipboardManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences;
@@ -449,9 +448,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 	}
 	}
 
 
 	public void restart() {
 	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) {
 	public void alert(final String message, final String title) {
@@ -995,9 +996,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 	private void forceQuit() {
 	private void forceQuit() {
 		// TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each
 		// 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.
 		// 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) {
 	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,
 		mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress,
 				progress.mOverallTotal));
 				progress.mOverallTotal));
 	}
 	}
+
 	public void initInputDevices() {
 	public void initInputDevices() {
 		mView.initInputDevices();
 		mView.initInputDevices();
 	}
 	}
+
+	@Keep
+	private void createNewGodotInstance(String[] args) {
+		runOnUiThread(() -> {
+			if (godotHost != null) {
+				godotHost.onNewGodotInstanceRequested(args);
+			}
+		});
+	}
 }
 }

+ 9 - 1
platform/android/java/lib/src/org/godotengine/godot/GodotHost.java

@@ -60,8 +60,16 @@ public interface GodotHost {
 	default void onGodotForceQuit(Godot instance) {}
 	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).
 	 * to perform the appropriate action(s).
 	 */
 	 */
 	default void onGodotRestartRequested(Godot instance) {}
 	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) {}
 }
 }

+ 2 - 0
platform/android/java/nativeSrcsConfigs/CMakeLists.txt

@@ -17,3 +17,5 @@ target_include_directories(${PROJECT_NAME}
         SYSTEM PUBLIC
         SYSTEM PUBLIC
         ${GODOT_ROOT_DIR}
         ${GODOT_ROOT_DIR}
         ${GODOT_ROOT_DIR}/modules/gdnative/include)
         ${GODOT_ROOT_DIR}/modules/gdnative/include)
+
+add_definitions(-DUNIX_ENABLED -DTOOLS_ENABLED)

+ 1 - 0
platform/android/java/nativeSrcsConfigs/build.gradle

@@ -6,6 +6,7 @@ plugins {
 android {
 android {
     compileSdkVersion versions.compileSdk
     compileSdkVersion versions.compileSdk
     buildToolsVersion versions.buildTools
     buildToolsVersion versions.buildTools
+    ndkVersion versions.ndkVersion
 
 
     defaultConfig {
     defaultConfig {
         minSdkVersion versions.minSdk
         minSdkVersion versions.minSdk

+ 1 - 0
platform/android/java/settings.gradle

@@ -4,6 +4,7 @@ rootProject.name = "Godot"
 include ':app'
 include ':app'
 include ':lib'
 include ':lib'
 include ':nativeSrcsConfigs'
 include ':nativeSrcsConfigs'
+include ':editor'
 
 
 include ':assetPacks:installTime'
 include ':assetPacks:installTime'
 project(':assetPacks:installTime').projectDir = file("app/assetPacks/installTime")
 project(':assetPacks:installTime').projectDir = file("app/assetPacks/installTime")

+ 5 - 1
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) {
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) {
 	// lets cleanup
 	// lets cleanup
+	if (java_class_wrapper) {
+		memdelete(java_class_wrapper);
+	}
 	if (godot_io_java) {
 	if (godot_io_java) {
 		delete 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;
 		delete input_handler;
 	}
 	}
 	if (os_android) {
 	if (os_android) {
+		os_android->main_loop_end();
 		delete os_android;
 		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 (cmdline) {
 		if (j_cmdline) {
 		if (j_cmdline) {
 			for (int i = 0; i < cmdlen; ++i) {
 			for (int i = 0; i < cmdlen; ++i) {

+ 14 - 0
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;");
 	_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_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
 	_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()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 some Activity method pointers...
 	_get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;");
 	_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);
 		env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms);
 	}
 	}
 }
 }
+
+void GodotJavaWrapper::create_new_godot_instance(List<String> 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);
+	}
+}

+ 3 - 0
platform/android/java_godot_wrapper.h

@@ -37,6 +37,7 @@
 #include <android/log.h>
 #include <android/log.h>
 #include <jni.h>
 #include <jni.h>
 
 
+#include "core/list.h"
 #include "string_android.h"
 #include "string_android.h"
 
 
 // Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++
 // 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_setup_completed = 0;
 	jmethodID _on_godot_main_loop_started = 0;
 	jmethodID _on_godot_main_loop_started = 0;
 	jmethodID _get_class_loader = 0;
 	jmethodID _get_class_loader = 0;
+	jmethodID _create_new_godot_instance = 0;
 
 
 public:
 public:
 	GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
 	GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -106,6 +108,7 @@ public:
 	bool is_activity_resumed();
 	bool is_activity_resumed();
 	void vibrate(int p_duration_ms);
 	void vibrate(int p_duration_ms);
 	String get_input_fallback_mapping();
 	String get_input_fallback_mapping();
+	void create_new_godot_instance(List<String> args);
 };
 };
 
 
 #endif /* !JAVA_GODOT_WRAPPER_H */
 #endif /* !JAVA_GODOT_WRAPPER_H */

+ 57 - 1
platform/android/os_android.cpp

@@ -36,6 +36,7 @@
 #include "drivers/unix/dir_access_unix.h"
 #include "drivers/unix/dir_access_unix.h"
 #include "drivers/unix/file_access_unix.h"
 #include "drivers/unix/file_access_unix.h"
 #include "main/main.h"
 #include "main/main.h"
+#include "scene/main/scene_tree.h"
 #include "servers/visual/visual_server_raster.h"
 #include "servers/visual/visual_server_raster.h"
 #include "servers/visual/visual_server_wrap_mt.h"
 #include "servers/visual/visual_server_wrap_mt.h"
 
 
@@ -50,6 +51,8 @@
 #include "java_godot_io_wrapper.h"
 #include "java_godot_io_wrapper.h"
 #include "java_godot_wrapper.h"
 #include "java_godot_wrapper.h"
 
 
+const char *OS_Android::ANDROID_EXEC_PATH = "apk";
+
 String _remove_symlink(const String &dir) {
 String _remove_symlink(const String &dir) {
 	// Workaround for Android 6.0+ using a symlink.
 	// Workaround for Android 6.0+ using a symlink.
 	// Save the current directory.
 	// 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() {
 void OS_Android::initialize_core() {
 	OS_Unix::initialize_core();
 	OS_Unix::initialize_core();
 
 
+#ifdef TOOLS_ENABLED
+	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
+#else
 	if (use_apk_expansion)
 	if (use_apk_expansion)
 		FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
 		FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
 	else {
 	else {
 		FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES);
 		FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES);
 	}
 	}
+#endif
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
+
+#ifdef TOOLS_ENABLED
+	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
+#else
 	if (use_apk_expansion)
 	if (use_apk_expansion)
 		DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
 		DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
 	else
 	else
 		DirAccess::make_default<DirAccessJAndroid>(DirAccess::ACCESS_RESOURCES);
 		DirAccess::make_default<DirAccessJAndroid>(DirAccess::ACCESS_RESOURCES);
+#endif
+
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
 	DirAccess::make_default<DirAccessUnix>(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);
 	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 {
 Size2 OS_Android::get_window_size() const {
 	return Vector2(default_videomode.width, default_videomode.height);
 	return Vector2(default_videomode.width, default_videomode.height);
 }
 }
@@ -313,8 +335,13 @@ bool OS_Android::main_loop_iterate() {
 }
 }
 
 
 void OS_Android::main_loop_end() {
 void OS_Android::main_loop_end() {
-	if (main_loop)
+	if (main_loop) {
+		SceneTree *scene_tree = Object::cast_to<SceneTree>(main_loop);
+		if (scene_tree) {
+			scene_tree->quit();
+		}
 		main_loop->finish();
 		main_loop->finish();
+	}
 }
 }
 
 
 void OS_Android::main_loop_focusout() {
 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 {
 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
 	return "/"; //android has its own filesystem for resources inside the APK
+#endif
 }
 }
 
 
 String OS_Android::get_locale() const {
 String OS_Android::get_locale() const {
@@ -452,6 +483,14 @@ String OS_Android::get_data_path() const {
 	return get_user_data_dir();
 	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 {
 String OS_Android::get_user_data_dir() const {
 	if (data_dir_cache != String())
 	if (data_dir_cache != String())
 		return data_dir_cache;
 		return data_dir_cache;
@@ -524,6 +563,10 @@ void OS_Android::vibrate_handheld(int p_duration_ms) {
 	godot_java->vibrate(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) {
 bool OS_Android::_check_internal_feature_support(const String &p_feature) {
 	if (p_feature == "mobile") {
 	if (p_feature == "mobile") {
 		//TODO support etc2 only if GLES3 driver is selected
 		//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);
 	AudioDriverManager::add_driver(&audio_driver_android);
 }
 }
 
 
+Error OS_Android::execute(const String &p_path, const List<String> &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<String> &p_arguments, ProcessID *r_child_id) {
+	godot_java->create_new_godot_instance(p_arguments);
+	return OK;
+}
+
 OS_Android::~OS_Android() {
 OS_Android::~OS_Android() {
 }
 }

+ 11 - 0
platform/android/os_android.h

@@ -70,6 +70,8 @@ class OS_Android : public OS_Unix {
 	bool transparency_enabled = false;
 	bool transparency_enabled = false;
 
 
 public:
 public:
+	static const char *ANDROID_EXEC_PATH;
+
 	// functions used by main to initialize/deinitialize the OS
 	// functions used by main to initialize/deinitialize the OS
 	virtual int get_video_driver_count() const;
 	virtual int get_video_driver_count() const;
 	virtual const char *get_video_driver_name(int p_driver) const;
 	virtual const char *get_video_driver_name(int p_driver) const;
@@ -111,6 +113,7 @@ public:
 	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
 	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
 
 
 	virtual void set_keep_screen_on(bool p_enabled);
 	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 Size2 get_window_size() const;
 	virtual Rect2 get_window_safe_area() const;
 	virtual Rect2 get_window_safe_area() const;
@@ -144,6 +147,7 @@ public:
 	virtual ScreenOrientation get_screen_orientation() const;
 	virtual ScreenOrientation get_screen_orientation() const;
 
 
 	virtual Error shell_open(String p_uri);
 	virtual Error shell_open(String p_uri);
+	virtual String get_executable_path() const;
 	virtual String get_user_data_dir() const;
 	virtual String get_user_data_dir() const;
 	virtual String get_data_path() const;
 	virtual String get_data_path() const;
 	virtual String get_cache_path() const;
 	virtual String get_cache_path() const;
@@ -173,9 +177,16 @@ public:
 	virtual String get_joy_guid(int p_device) const;
 	virtual String get_joy_guid(int p_device) const;
 	void vibrate_handheld(int p_duration_ms);
 	void vibrate_handheld(int p_duration_ms);
 
 
+	virtual String get_config_path() const;
+
+	virtual Error execute(const String &p_path, const List<String> &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);
 	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(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);
 	~OS_Android();
 	~OS_Android();
+
+private:
+	Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id);
 };
 };
 
 
 #endif
 #endif

+ 3 - 0
scene/main/viewport.cpp

@@ -1388,6 +1388,9 @@ Vector2 Viewport::_get_window_offset() const {
 }
 }
 
 
 Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) {
 Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) {
+	if (ev.is_null()) {
+		return ev;
+	}
 	Vector2 vp_ofs = _get_window_offset();
 	Vector2 vp_ofs = _get_window_offset();
 	Transform2D ai = get_final_transform().affine_inverse() * _get_input_pre_xform();
 	Transform2D ai = get_final_transform().affine_inverse() * _get_input_pre_xform();