diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 92ab20b9997..57998b77788 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -635,6 +635,9 @@ void EditorNode::_notification(int p_what) {
 			get_tree()->get_root()->set_snap_2d_transforms_to_pixel(false);
 			get_tree()->get_root()->set_snap_2d_vertices_to_pixel(false);
 			get_tree()->set_auto_accept_quit(false);
+#ifdef ANDROID_ENABLED
+			get_tree()->set_quit_on_go_back(false);
+#endif
 			get_tree()->get_root()->connect("files_dropped", callable_mp(this, &EditorNode::_dropped_files));
 
 			command_palette->register_shortcuts_as_command();
diff --git a/main/main.cpp b/main/main.cpp
index f41fa136bac..c7a644d7b31 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2276,9 +2276,10 @@ bool Main::start() {
 
 		bool embed_subwindows = GLOBAL_DEF("display/window/subwindows/embed_subwindows", true);
 
-		if (OS::get_singleton()->is_single_window() || (!project_manager && !editor && embed_subwindows)) {
+		if (OS::get_singleton()->is_single_window() || (!project_manager && !editor && embed_subwindows) || !DisplayServer::get_singleton()->has_feature(DisplayServer::Feature::FEATURE_SUBWINDOWS)) {
 			sml->get_root()->set_embedding_subwindows(true);
 		}
+
 		ResourceLoader::add_custom_loaders();
 		ResourceSaver::add_custom_savers();
 
diff --git a/platform/android/SCsub b/platform/android/SCsub
index d031d14499b..1a3c158d2e3 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -53,10 +53,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 = "debug"
+    else:  # debug
+        lib_type_dir = "dev"
 
-    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 5d1a9d7b99a..b6303d1bc91 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -33,6 +33,11 @@ allprojects {
     }
 }
 
+configurations {
+    // Initializes a placeholder for the devImplementation dependency configuration.
+    devImplementation {}
+}
+
 dependencies {
     implementation libraries.kotlinStdLib
     implementation libraries.androidxFragment
@@ -45,6 +50,7 @@ dependencies {
         // Custom build mode. In this scenario this project is the only one around and the Godot
         // library is available through the pre-generated godot-lib.*.aar android archive files.
         debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar'])
+        devImplementation fileTree(dir: 'libs/dev', include: ['*.jar', '*.aar'])
         releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
     }
 
@@ -66,6 +72,7 @@ dependencies {
 android {
     compileSdkVersion versions.compileSdk
     buildToolsVersion versions.buildTools
+    ndkVersion versions.ndkVersion
 
     compileOptions {
         sourceCompatibility versions.javaVersion
@@ -93,6 +100,8 @@ android {
         versionName getExportVersionName()
         minSdkVersion getExportMinSdkVersion()
         targetSdkVersion getExportTargetSdkVersion()
+
+        missingDimensionStrategy 'products', 'template'
     }
 
     lintOptions {
@@ -146,6 +155,18 @@ android {
             }
         }
 
+        dev {
+            initWith debug
+            // Signing and zip-aligning are skipped for prebuilt builds, but
+            // performed for custom builds.
+            zipAlignEnabled shouldZipAlign()
+            if (shouldSign()) {
+                signingConfig signingConfigs.debug
+            } else {
+                signingConfig null
+            }
+        }
+
         release {
             // Signing and zip-aligning are skipped for prebuilt builds, but
             // performed for custom builds.
@@ -167,6 +188,7 @@ android {
             assets.srcDirs = ['assets']
         }
         debug.jniLibs.srcDirs = ['libs/debug', 'libs/debug/vulkan_validation_layers']
+        dev.jniLibs.srcDirs = ['libs/dev']
         release.jniLibs.srcDirs = ['libs/release']
     }
 
@@ -183,6 +205,12 @@ task copyAndRenameDebugApk(type: Copy) {
     rename "android_debug.apk", getExportFilename()
 }
 
+task copyAndRenameDevApk(type: Copy) {
+    from "$buildDir/outputs/apk/dev/android_dev.apk"
+    into getExportPath()
+    rename "android_dev.apk", getExportFilename()
+}
+
 task copyAndRenameReleaseApk(type: Copy) {
     from "$buildDir/outputs/apk/release/android_release.apk"
     into getExportPath()
@@ -195,6 +223,12 @@ task copyAndRenameDebugAab(type: Copy) {
     rename "build-debug.aab", getExportFilename()
 }
 
+task copyAndRenameDevAab(type: Copy) {
+    from "$buildDir/outputs/bundle/dev/build-dev.aab"
+    into getExportPath()
+    rename "build-dev.aab", getExportFilename()
+}
+
 task copyAndRenameReleaseAab(type: Copy) {
     from "$buildDir/outputs/bundle/release/build-release.aab"
     into getExportPath()
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index c238d1b3615..1b2976e7152 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<String> requiredKeys ->
     // Attempt to read the version from the `version.py` file.
-    String libraryVersion = ""
+    String libraryVersionName = ""
+    int libraryVersionCode = 0
 
     File versionFile = new File("../../../version.py")
     if (versionFile.isFile()) {
@@ -109,15 +124,35 @@ ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
         }
 
         if (requiredKeys.empty) {
-            libraryVersion = map.values().join(".")
+            libraryVersionName = map.values().join(".")
+            try {
+                if (map.containsKey("patch")) {
+                    libraryVersionCode = Integer.parseInt(map["patch"])
+                }
+
+                if (map.containsKey("minor")) {
+                    libraryVersionCode += (Integer.parseInt(map["minor"]) * 100)
+                }
+
+                if (map.containsKey("major")) {
+                    libraryVersionCode += (Integer.parseInt(map["major"]) * 10000)
+                }
+            } catch (NumberFormatException ignore) {
+                libraryVersionCode = 1
+            }
         }
     }
 
-    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
+
+    if (libraryVersionCode == 0) {
+        libraryVersionCode = 1
+    }
+
+    return [libraryVersionName, libraryVersionCode]
 }
 
 ext.getGodotLibraryVersion = { ->
@@ -127,7 +162,10 @@ ext.getGodotLibraryVersion = { ->
 
 ext.getGodotPublishVersion = { ->
     List<String> requiredKeys = ["major", "minor", "patch", "status"]
-    return generateGodotLibraryVersion(requiredKeys)
+    String versionName = ""
+    int versionCode = 1
+    (versionName, versionCode) = generateGodotLibraryVersion(requiredKeys)
+    return versionName
 }
 
 final String VALUE_SEPARATOR_REGEX = "\\|"
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 83bc68c9929..e16ca65df5b 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"]
+    supportedTargetsMap = [release: "release", dev: "debug", debug: "release_debug"]
+    supportedFlavors = ["editor", "template"]
 
-    // 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()
 }
 
 /**
@@ -54,6 +55,17 @@ task copyDebugBinaryToBin(type: Copy) {
     include('android_debug.apk')
 }
 
+/**
+ * Copy the generated 'android_dev.apk' binary template into the Godot bin directory.
+ * Depends on the app build task to ensure the binary is generated prior to copying.
+ */
+task copyDevBinaryToBin(type: Copy) {
+    dependsOn ':app:assembleDev'
+    from('app/build/outputs/apk/dev')
+    into(binDir)
+    include('android_dev.apk')
+}
+
 /**
  * Copy the generated 'android_release.apk' binary template into the Godot bin directory.
  * Depends on the app build task to ensure the binary is generated prior to copying.
@@ -70,7 +82,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:assembleTemplateDebug'
     from('lib/build/outputs/aar')
     into('app/libs/debug')
     include('godot-lib.debug.aar')
@@ -81,18 +93,40 @@ 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:assembleTemplateDebug'
     from('lib/build/outputs/aar')
     into(binDir)
     include('godot-lib.debug.aar')
 }
 
+/**
+ * Copy the Godot android library archive dev file into the app module dev libs directory.
+ * Depends on the library build task to ensure the AAR file is generated prior to copying.
+ */
+task copyDevAARToAppModule(type: Copy) {
+    dependsOn ':lib:assembleTemplateDev'
+    from('lib/build/outputs/aar')
+    into('app/libs/dev')
+    include('godot-lib.dev.aar')
+}
+
+/**
+ * Copy the Godot android library archive dev file into the root bin directory.
+ * Depends on the library build task to ensure the AAR file is generated prior to copying.
+ */
+task copyDevAARToBin(type: Copy) {
+    dependsOn ':lib:assembleTemplateDev'
+    from('lib/build/outputs/aar')
+    into(binDir)
+    include('godot-lib.dev.aar')
+}
+
 /**
  * Copy the Godot android library archive release file into the app module release libs directory.
  * 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:assembleTemplateRelease'
     from('lib/build/outputs/aar')
     into('app/libs/release')
     include('godot-lib.release.aar')
@@ -103,7 +137,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:assembleTemplateRelease'
     from('lib/build/outputs/aar')
     into(binDir)
     include('godot-lib.release.aar')
@@ -111,7 +145,7 @@ task copyReleaseAARToBin(type: Copy) {
 
 /**
  * Generate Godot custom build template by zipping the source files from the app directory, as well
- * as the AAR files generated by 'copyDebugAAR' and 'copyReleaseAAR'.
+ * as the AAR files generated by 'copyDebugAAR', 'copyDevAAR' and 'copyReleaseAAR'.
  * The zip file also includes some gradle tools to allow building of the custom build.
  */
 task zipCustomBuild(type: Zip) {
@@ -130,8 +164,18 @@ 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 : supportedTargetsMap.keySet()) {
+                if (buildType == "release" && flavor == "editor") {
+                    // The editor can't be used with target=release as debugging tools are then not
+                    // included, and it would crash on errors instead of reporting them.
+                    continue
+                }
+
+                for (String abi : selectedAbis) {
+                    excludedTasks += ":lib:" + getSconsTaskName(flavor, buildType, abi)
+                }
+            }
         }
     }
     return excludedTasks
@@ -141,7 +185,7 @@ def templateBuildTasks() {
     def tasks = []
 
     // Only build the apks and aar files for which we have native shared libraries.
-    for (String target : supportedTargets) {
+    for (String target : supportedTargetsMap.keySet()) {
         File targetLibs = new File("lib/libs/" + target)
         if (targetLibs != null
             && targetLibs.isDirectory()
@@ -167,6 +211,50 @@ 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.apk')
+}
+
+task copyEditorDevBinaryToBin(type: Copy) {
+    dependsOn ':editor:assembleDev'
+    from('editor/build/outputs/apk/dev')
+    into(binDir)
+    include('android_editor_dev.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 : supportedTargetsMap.keySet()) {
+        if (target == "release") {
+            // The editor can't be used with target=release as debugging tools are then not
+            // included, and it would crash on errors instead of reporting them.
+            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.
  */
@@ -191,7 +279,27 @@ task generateDevTemplate {
 }
 
 /**
- * Clean the generated artifacts.
+ * Clean the generated editor artifacts.
+ */
+task cleanGodotEditor(type: Delete) {
+    // Delete the generated native tools libs
+    delete("lib/libs/tools")
+
+    // Delete the library generated AAR files
+    delete("lib/build/outputs/aar")
+
+    // Delete the generated binary apks
+    delete("editor/build/outputs/apk")
+
+    // Delete the Godot editor apks in the Godot bin directory
+    delete("$binDir/android_editor.apk")
+    delete("$binDir/android_editor_dev.apk")
+
+    finalizedBy getTasksByName("clean", true)
+}
+
+/**
+ * Clean the generated template artifacts.
  */
 task cleanGodotTemplates(type: Delete) {
     // Delete the generated native libs
@@ -208,9 +316,11 @@ task cleanGodotTemplates(type: Delete) {
 
     // Delete the Godot templates in the Godot bin directory
     delete("$binDir/android_debug.apk")
+    delete("$binDir/android_dev.apk")
     delete("$binDir/android_release.apk")
     delete("$binDir/android_source.zip")
     delete("$binDir/godot-lib.debug.aar")
+    delete("$binDir/godot-lib.dev.aar")
     delete("$binDir/godot-lib.release.aar")
 
     finalizedBy getTasksByName("clean", true)
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
new file mode 100644
index 00000000000..3312f61ad31
--- /dev/null
+++ b/platform/android/java/editor/build.gradle
@@ -0,0 +1,74 @@
+// 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.v4"
+        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 'products', 'editor'
+    }
+
+    compileOptions {
+        sourceCompatibility versions.javaVersion
+        targetCompatibility versions.javaVersion
+    }
+
+    buildTypes {
+        dev {
+            initWith debug
+            applicationIdSuffix ".dev"
+        }
+
+        debug {
+            initWith release
+
+            // Need to swap with the release signing config when this is ready for public release.
+            signingConfig signingConfigs.debug
+        }
+
+        release {
+            // This buildtype is disabled below.
+            // The editor can't be used with target=release only, as debugging tools are then not
+            // included, and it would crash on errors instead of reporting them.
+        }
+    }
+
+    packagingOptions {
+        // 'doNotStrip' is enabled for development within Android Studio
+        if (shouldNotStrip()) {
+            doNotStrip '**/*.so'
+        }
+    }
+
+    // Disable 'release' buildtype.
+    // The editor can't be used with target=release only, as debugging tools are then not
+    // included, and it would crash on errors instead of reporting them.
+    variantFilter { variant ->
+        if (variant.buildType.name == "release") {
+            setIgnore(true)
+        }
+    }
+
+    applicationVariants.all { variant ->
+        variant.outputs.all { output ->
+            def suffix = variant.name == "dev" ? "_dev" : ""
+            output.outputFileName = "android_editor${suffix}.apk"
+        }
+    }
+}
diff --git a/platform/android/java/editor/src/dev/res/values/strings.xml b/platform/android/java/editor/src/dev/res/values/strings.xml
new file mode 100644
index 00000000000..45fae3fd391
--- /dev/null
+++ b/platform/android/java/editor/src/dev/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<string name="godot_editor_name_string">Godot Editor 4.x (dev)</string>
+</resources>
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 @@
+<?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>
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<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);
+	}
+}
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..e8ce34f34dd
--- /dev/null
+++ b/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 4.x</string>
+</resources>
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 120a40a31d1..c806de1ded9 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 {
+        dev {
+            initWith debug
+        }
+    }
+
+    flavorDimensions "products"
+    productFlavors {
+        editor {}
+        template {}
+    }
+
     lintOptions {
         abortOnError false
         disable 'MissingTranslation', 'UnusedResources'
@@ -58,24 +69,50 @@ android {
             aidl.srcDirs = ['aidl']
             assets.srcDirs = ['assets']
         }
+
         debug.jniLibs.srcDirs = ['libs/debug']
+        dev.jniLibs.srcDirs = ['libs/dev']
         release.jniLibs.srcDirs = ['libs/release']
+
+        // Editor jni library
+        editorDebug.jniLibs.srcDirs = ['libs/tools/debug']
+        editorDev.jniLibs.srcDirs = ['libs/tools/dev']
+    }
+
+    // Disable 'editorRelease'.
+    // The editor can't be used with target=release as debugging tools are then not
+    // included, and it would crash on errors instead of reporting them.
+    variantFilter { variant ->
+        if (variant.name == "editorRelease") {
+            setIgnore(true)
+        }
     }
 
     libraryVariants.all { variant ->
+        def flavorName = variant.getFlavorName()
+        if (flavorName == null || flavorName == "") {
+            throw new GradleException("Invalid product flavor: $flavorName")
+        }
+
+        boolean toolsFlag = flavorName == "editor"
+
+        def buildType = variant.buildType.name
+        if (buildType == null || buildType == "" || !supportedTargetsMap.containsKey(buildType)) {
+            throw new GradleException("Invalid build type: $buildType")
+        }
+
+        def sconsTarget = supportedTargetsMap[buildType]
+        if (sconsTarget == null || sconsTarget == "") {
+            throw new GradleException("Invalid scons target: $sconsTarget")
+        }
+
+        // Update the name of the generated library
+        def outputSuffix = "${buildType}.aar"
+        if (toolsFlag) {
+            outputSuffix = "tools.$outputSuffix"
+        }
         variant.outputs.all { output ->
-            output.outputFileName = "godot-lib.${variant.name}.aar"
-        }
-
-        def buildType = variant.buildType.name.capitalize()
-
-        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)
+            output.outputFileName = "godot-lib.${outputSuffix}"
         }
 
         // Find scons' executable path
@@ -88,13 +125,11 @@ android {
         for (ext in sconsExts) {
             String sconsNameExt = sconsName + ext
             logger.lifecycle("Checking $sconsNameExt")
-
             sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt)
             if (sconsExecutableFile != null) {
                 // We're done!
                 break
             }
-
             // Check all the options in path
             List<File> allOptions = org.gradle.internal.os.OperatingSystem.current().findAllInPath(sconsNameExt)
             if (!allOptions.isEmpty()) {
@@ -103,27 +138,32 @@ android {
                 break
             }
         }
-
         if (sconsExecutableFile == null) {
             throw new GradleException("Unable to find executable path for the '$sconsName' command.")
         } else {
             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=${sconsTarget}", "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.capitalize()}JniLibFolders"].dependsOn taskName
+        }
     }
 
     // TODO: Enable when issues with AGP 7.1+ are resolved (https://github.com/GodotVR/godot_openxr/issues/187).
 //    publishing {
-//        singleVariant("release") {
+//        singleVariant("templateRelease") {
 //            withSourcesJar()
 //            withJavadocJar()
 //        }
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 78848c109a9..8a86136daf8 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;
@@ -333,9 +332,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) {
@@ -859,9 +860,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) {
@@ -1010,6 +1013,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 		mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress,
 				progress.mOverallTotal));
 	}
+
 	public void initInputDevices() {
 		mRenderView.initInputDevices();
 	}
@@ -1018,4 +1022,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 	private GodotRenderView getRenderView() { // used by native side to get renderView
 		return mRenderView;
 	}
+
+	@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 966c02f7d70..59cfe21c491 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -16,3 +16,5 @@ add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
 target_include_directories(${PROJECT_NAME}
         SYSTEM PUBLIC
         ${GODOT_ROOT_DIR})
+
+add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DTOOLS_ENABLED)
diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle
index 158bb2b98ef..0cb769b5396 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
@@ -28,8 +29,6 @@ android {
         }
     }
 
-    ndkVersion versions.ndkVersion
-
     externalNativeBuild {
         cmake {
             path "CMakeLists.txt"
diff --git a/platform/android/java/scripts/publish-module.gradle b/platform/android/java/scripts/publish-module.gradle
index 6b2aea5caf9..32b749e493d 100644
--- a/platform/android/java/scripts/publish-module.gradle
+++ b/platform/android/java/scripts/publish-module.gradle
@@ -7,20 +7,15 @@ version = PUBLISH_VERSION
 afterEvaluate {
     publishing {
         publications {
-            release(MavenPublication) {
+            templateRelease(MavenPublication) {
+                from components.templateRelease
+
                 // The coordinates of the library, being set from variables that
                 // we'll set up later
                 groupId ossrhGroupId
                 artifactId PUBLISH_ARTIFACT_ID
                 version PUBLISH_VERSION
 
-                // Two artifacts, the `aar` (or `jar`) and the sources
-                if (project.plugins.findPlugin("com.android.library")) {
-                    from components.release
-                } else {
-                    from components.java
-                }
-
                 // Mostly self-explanatory metadata
                 pom {
                     name = PUBLISH_ARTIFACT_ID
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 249717921f2..658f9281ab1 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -107,6 +107,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;
 	}
@@ -117,6 +120,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;
 	}
 }
@@ -146,7 +150,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 754267c834b..2c8378e685d 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -77,6 +77,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;");
@@ -351,3 +352,16 @@ void GodotJavaWrapper::vibrate(int 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);
+	}
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 42ae91480f7..f04fda7c3de 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -37,6 +37,7 @@
 #include <android/log.h>
 #include <jni.h>
 
+#include "core/templates/list.h"
 #include "java_godot_view_wrapper.h"
 #include "string_android.h"
 
@@ -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);
@@ -103,6 +105,7 @@ public:
 	bool is_activity_resumed();
 	void vibrate(int p_duration_ms);
 	String get_input_fallback_mapping();
+	void create_new_godot_instance(List<String> args);
 };
 
 #endif /* !JAVA_GODOT_WRAPPER_H */
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index b17b0f31398..438fc04eb61 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -35,6 +35,7 @@
 #include "drivers/unix/file_access_unix.h"
 #include "main/main.h"
 #include "platform/android/display_server_android.h"
+#include "scene/main/scene_tree.h"
 
 #include "dir_access_jandroid.h"
 #include "file_access_android.h"
@@ -45,6 +46,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.
@@ -81,18 +84,28 @@ void OS_Android::alert(const String &p_alert, const String &p_title) {
 void OS_Android::initialize_core() {
 	OS_Unix::initialize_core();
 
+#ifdef TOOLS_ENABLED
+	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
+#else
 	if (use_apk_expansion) {
 		FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
 	} else {
 		FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES);
 	}
+#endif
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
+
+#ifdef TOOLS_ENABLED
+	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
+#else
 	if (use_apk_expansion) {
 		DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
 	} else {
 		DirAccess::make_default<DirAccessJAndroid>(DirAccess::ACCESS_RESOURCES);
 	}
+#endif
+
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
 
@@ -178,6 +191,10 @@ bool OS_Android::main_loop_iterate() {
 
 void OS_Android::main_loop_end() {
 	if (main_loop) {
+		SceneTree *scene_tree = Object::cast_to<SceneTree>(main_loop);
+		if (scene_tree) {
+			scene_tree->quit();
+		}
 		main_loop->finalize();
 	}
 }
@@ -197,7 +214,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 {
@@ -222,6 +243,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.is_empty()) {
 		return data_dir_cache;
@@ -294,6 +323,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") {
 		return true;
@@ -343,5 +376,26 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god
 	DisplayServerAndroid::register_android_driver();
 }
 
+Error OS_Android::execute(const String &p_path, const List<String> &p_arguments, 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);
+	} else {
+		return OS_Unix::execute(p_path, p_arguments, r_pipe, r_exitcode, read_stderr, p_pipe_mutex, p_open_console);
+	}
+}
+
+Error OS_Android::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
+	if (p_path == ANDROID_EXEC_PATH) {
+		return create_instance(p_arguments, r_child_id);
+	} else {
+		return OS_Unix::create_process(p_path, p_arguments, r_child_id, 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() {
 }
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index f523f172c69..c0c51272242 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -66,6 +66,8 @@ private:
 	GodotIOJavaWrapper *godot_io_java;
 
 public:
+	static const char *ANDROID_EXEC_PATH;
+
 	virtual void initialize_core() override;
 	virtual void initialize() override;
 
@@ -108,6 +110,7 @@ public:
 	ANativeWindow *get_native_window() const;
 
 	virtual Error shell_open(String p_uri) override;
+	virtual String get_executable_path() const override;
 	virtual String get_user_data_dir() const override;
 	virtual String get_data_path() const override;
 	virtual String get_cache_path() const override;
@@ -121,6 +124,12 @@ public:
 
 	void vibrate_handheld(int p_duration_ms) override;
 
+	virtual String get_config_path() const override;
+
+	virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
+	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
+	virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
+
 	virtual bool _check_internal_feature_support(const String &p_feature) override;
 	OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);
 	~OS_Android();