Przeglądaj źródła

Merge pull request #96624 from m4gr3d/android_editor_improvements

[Android editor] Improve support for XR projects
Rémi Verschelde 1 rok temu
rodzic
commit
0e9950bde1
22 zmienionych plików z 572 dodań i 99 usunięć
  1. 1 0
      .github/workflows/android_builds.yml
  2. 2 1
      main/main.cpp
  3. 13 16
      modules/openxr/openxr_api.cpp
  4. 1 0
      platform/android/java/app/build.gradle
  5. 1 0
      platform/android/java/app/settings.gradle
  6. 32 13
      platform/android/java/build.gradle
  7. 33 10
      platform/android/java/editor/build.gradle
  8. 39 0
      platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt
  9. 58 42
      platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
  10. 7 3
      platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
  11. 53 4
      platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
  12. 99 0
      platform/android/java/editor/src/meta/AndroidManifest.xml
  13. BIN
      platform/android/java/editor/src/meta/assets/vr_splash.png
  14. 94 0
      platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
  15. 71 0
      platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
  16. 2 2
      platform/android/java/lib/build.gradle
  17. 6 1
      platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
  18. 4 3
      platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
  19. 52 0
      platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt
  20. 3 1
      platform/android/java/nativeSrcsConfigs/CMakeLists.txt
  21. 1 0
      platform/android/java/settings.gradle
  22. 0 3
      platform/android/plugin/godot_plugin_jni.cpp

+ 1 - 0
.github/workflows/android_builds.yml

@@ -85,6 +85,7 @@ jobs:
         run: |
           cd platform/android/java
           ./gradlew generateGodotEditor
+          ./gradlew generateGodotMetaEditor
           cd ../../..
           ls -l bin/android_editor_builds/
 

+ 2 - 1
main/main.cpp

@@ -1037,7 +1037,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 		if (arg == "--audio-driver" ||
 				arg == "--display-driver" ||
 				arg == "--rendering-method" ||
-				arg == "--rendering-driver") {
+				arg == "--rendering-driver" ||
+				arg == "--xr-mode") {
 			if (N) {
 				forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(arg);
 				forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(N->get());

+ 13 - 16
modules/openxr/openxr_api.cpp

@@ -271,17 +271,14 @@ OpenXRAPI *OpenXRAPI::singleton = nullptr;
 Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers;
 
 bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) {
-	// @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled"
-
-	if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
-		// Disabled for now, using XR inside of the editor we'll be working on during the coming months.
-		return false;
-	} else {
-		if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
-			return GLOBAL_GET("xr/openxr/enabled");
+	if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
+		if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
+			return GLOBAL_GET("xr/openxr/enabled.editor");
 		} else {
-			return XRServer::get_xr_mode() == XRServer::XRMODE_ON;
+			return GLOBAL_GET("xr/openxr/enabled");
 		}
+	} else {
+		return XRServer::get_xr_mode() == XRServer::XRMODE_ON;
 	}
 }
 
@@ -557,14 +554,11 @@ bool OpenXRAPI::create_instance() {
 		extension_ptrs.push_back(enabled_extensions[i].get_data());
 	}
 
-	// Get our project name
-	String project_name = GLOBAL_GET("application/config/name");
-
 	// Create our OpenXR instance
 	XrApplicationInfo application_info{
-		"", // applicationName, we'll set this down below
+		"Godot Engine", // applicationName, if we're running a game we'll update this down below.
 		1, // applicationVersion, we don't currently have this
-		"Godot Game Engine", // engineName
+		"Godot Engine", // engineName
 		VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc.
 		XR_API_VERSION_1_0 // apiVersion
 	};
@@ -588,7 +582,11 @@ bool OpenXRAPI::create_instance() {
 		extension_ptrs.ptr() // enabledExtensionNames
 	};
 
-	copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE);
+	// Get our project name
+	String project_name = GLOBAL_GET("application/config/name");
+	if (!project_name.is_empty()) {
+		copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE);
+	}
 
 	XrResult result = xrCreateInstance(&instance_create_info, &instance);
 	ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance.");
@@ -2583,7 +2581,6 @@ OpenXRAPI::OpenXRAPI() {
 
 	if (Engine::get_singleton()->is_editor_hint()) {
 		// Enabled OpenXR in the editor? Adjust our settings for the editor
-
 	} else {
 		// Load settings from project settings
 		int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor");

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

@@ -12,6 +12,7 @@ allprojects {
         mavenCentral()
         gradlePluginPortal()
         maven { url "https://plugins.gradle.org/m2/" }
+        maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
 
         // Godot user plugins custom maven repos
         String[] mavenRepos = getGodotPluginsMavenRepos()

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

@@ -11,6 +11,7 @@ pluginManagement {
         mavenCentral()
         gradlePluginPortal()
         maven { url "https://plugins.gradle.org/m2/" }
+        maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
     }
 }
 

+ 32 - 13
platform/android/java/build.gradle

@@ -18,12 +18,14 @@ allprojects {
         mavenCentral()
         gradlePluginPortal()
         maven { url "https://plugins.gradle.org/m2/" }
+        maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
     }
 }
 
 ext {
     supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]
     supportedFlavors = ["editor", "template"]
+    supportedEditorVendors = ["google", "meta"]
     supportedFlavorsBuildTypes = [
         "editor": ["dev", "debug", "release"],
         "template": ["dev", "debug", "release"]
@@ -92,15 +94,20 @@ def templateExcludedBuildTask() {
 /**
  * Generates the build tasks for the given flavor
  * @param flavor Must be one of the supported flavors ('template' / 'editor')
+ * @param editorVendor Must be one of the supported editor vendors ('google' / 'meta')
  */
-def generateBuildTasks(String flavor = "template") {
+def generateBuildTasks(String flavor = "template", String editorVendor = "google") {
     if (!supportedFlavors.contains(flavor)) {
         throw new GradleException("Invalid build flavor: $flavor")
     }
+    if (!supportedEditorVendors.contains(editorVendor)) {
+        throw new GradleException("Invalid editor vendor: $editorVendor")
+    }
 
+    String capitalizedEditorVendor = editorVendor.capitalize()
     def buildTasks = []
 
-    // Only build the apks and aar files for which we have native shared libraries unless we intend
+    // Only build the binary files for which we have native shared libraries unless we intend
     // to run the scons build tasks.
     boolean excludeSconsBuildTasks = excludeSconsBuildTasks()
     boolean isTemplate = flavor == "template"
@@ -163,28 +170,28 @@ def generateBuildTasks(String flavor = "template") {
                 }
             } else {
                 // Copy the generated editor apk to the bin directory.
-                String copyEditorApkTaskName = "copyEditor${capitalizedTarget}ApkToBin"
+                String copyEditorApkTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}ApkToBin"
                 if (tasks.findByName(copyEditorApkTaskName) != null) {
                     buildTasks += tasks.getByName(copyEditorApkTaskName)
                 } else {
                     buildTasks += tasks.create(name: copyEditorApkTaskName, type: Copy) {
-                        dependsOn ":editor:assemble${capitalizedTarget}"
-                        from("editor/build/outputs/apk/${target}")
+                        dependsOn ":editor:assemble${capitalizedEditorVendor}${capitalizedTarget}"
+                        from("editor/build/outputs/apk/${editorVendor}/${target}")
                         into(androidEditorBuildsDir)
-                        include("android_editor-${target}*.apk")
+                        include("android_editor-${editorVendor}-${target}*.apk")
                     }
                 }
 
                 // Copy the generated editor aab to the bin directory.
-                String copyEditorAabTaskName = "copyEditor${capitalizedTarget}AabToBin"
+                String copyEditorAabTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}AabToBin"
                 if (tasks.findByName(copyEditorAabTaskName) != null) {
                     buildTasks += tasks.getByName(copyEditorAabTaskName)
                 } else {
                     buildTasks += tasks.create(name: copyEditorAabTaskName, type: Copy) {
-                        dependsOn ":editor:bundle${capitalizedTarget}"
-                        from("editor/build/outputs/bundle/${target}")
+                        dependsOn ":editor:bundle${capitalizedEditorVendor}${capitalizedTarget}"
+                        from("editor/build/outputs/bundle/${editorVendor}${capitalizedTarget}")
                         into(androidEditorBuildsDir)
-                        include("android_editor-${target}*.aab")
+                        include("android_editor-${editorVendor}-${target}*.aab")
                     }
                 }
             }
@@ -197,15 +204,27 @@ def generateBuildTasks(String flavor = "template") {
 }
 
 /**
- * Generate the Godot Editor Android apk.
+ * Generate the Godot Editor Android binaries.
  *
  * Note: Unless the 'generateNativeLibs` argument is specified, 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.
+ * The task will only build the binaries for which the shared libraries is available.
  */
 task generateGodotEditor {
     gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
-    dependsOn = generateBuildTasks("editor")
+    dependsOn = generateBuildTasks("editor", "google")
+}
+
+/**
+ * Generate the Godot Editor Android binaries for Meta devices.
+ *
+ * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries
+ * must have been generated (via scons) prior to running this gradle task.
+ * The task will only build the binaries for which the shared libraries is available.
+ */
+task generateGodotMetaEditor {
+    gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
+    dependsOn = generateBuildTasks("editor", "meta")
 }
 
 /**

+ 33 - 10
platform/android/java/editor/build.gradle

@@ -5,16 +5,6 @@ plugins {
     id 'base'
 }
 
-dependencies {
-    implementation "androidx.fragment:fragment:$versions.fragmentVersion"
-    implementation project(":lib")
-
-    implementation "androidx.window:window:1.3.0"
-    implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
-    implementation "androidx.constraintlayout:constraintlayout:2.1.4"
-    implementation "org.bouncycastle:bcprov-jdk15to18:1.77"
-}
-
 ext {
     // Retrieve the build number from the environment variable; default to 0 if none is specified.
     // The build number is added as a suffix to the version code for upload to the Google Play store.
@@ -154,4 +144,37 @@ android {
             doNotStrip '**/*.so'
         }
     }
+
+    flavorDimensions = ["vendor"]
+    productFlavors {
+        google {
+            dimension "vendor"
+            missingDimensionStrategy 'products', 'editor'
+        }
+        meta {
+            dimension "vendor"
+            missingDimensionStrategy 'products', 'editor'
+            ndk {
+                //noinspection ChromeOsAbiSupport
+                abiFilters "arm64-v8a"
+            }
+            applicationIdSuffix ".meta"
+            versionNameSuffix "-meta"
+            minSdkVersion 23
+            targetSdkVersion 32
+        }
+    }
+}
+
+dependencies {
+    implementation "androidx.fragment:fragment:$versions.fragmentVersion"
+    implementation project(":lib")
+
+    implementation "androidx.window:window:1.3.0"
+    implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
+    implementation "androidx.constraintlayout:constraintlayout:2.1.4"
+    implementation "org.bouncycastle:bcprov-jdk15to18:1.77"
+
+    // Meta dependencies
+    metaImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable"
 }

+ 39 - 0
platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt

@@ -0,0 +1,39 @@
+/**************************************************************************/
+/*  GodotEditor.kt                                                        */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+package org.godotengine.editor
+
+/**
+ * Primary window of the Godot Editor.
+ *
+ * This is the implementation of the editor used when running on regular Android devices.
+ */
+open class GodotEditor : BaseGodotEditor() {
+}

+ 58 - 42
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt → platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  GodotEditor.kt                                                        */
+/*  BaseGodotEditor.kt                                                    */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -52,6 +52,8 @@ import org.godotengine.godot.GodotLib
 import org.godotengine.godot.error.Error
 import org.godotengine.godot.utils.PermissionsUtil
 import org.godotengine.godot.utils.ProcessPhoenix
+import org.godotengine.godot.utils.isHorizonOSDevice
+import org.godotengine.godot.utils.isNativeXRDevice
 import java.util.*
 import kotlin.math.min
 
@@ -61,13 +63,11 @@ import kotlin.math.min
  * 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.
  */
-open class GodotEditor : GodotActivity() {
+abstract class BaseGodotEditor : GodotActivity() {
 
 	companion object {
-		private val TAG = GodotEditor::class.java.simpleName
+		private val TAG = BaseGodotEditor::class.java.simpleName
 
 		private const val WAIT_FOR_DEBUGGER = false
 
@@ -81,12 +81,13 @@ open class GodotEditor : GodotActivity() {
 		// Command line arguments
 		private const val FULLSCREEN_ARG = "--fullscreen"
 		private const val FULLSCREEN_ARG_SHORT = "-f"
-		private const val EDITOR_ARG = "--editor"
-		private const val EDITOR_ARG_SHORT = "-e"
-		private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
-		private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
-		private const val BREAKPOINTS_ARG = "--breakpoints"
-		private const val BREAKPOINTS_ARG_SHORT = "-b"
+		internal const val EDITOR_ARG = "--editor"
+		internal const val EDITOR_ARG_SHORT = "-e"
+		internal const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
+		internal const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
+		internal const val BREAKPOINTS_ARG = "--breakpoints"
+		internal const val BREAKPOINTS_ARG_SHORT = "-b"
+		internal const val XR_MODE_ARG = "--xr-mode"
 
 		// Info for the various classes used by the editor
 		internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
@@ -122,6 +123,20 @@ open class GodotEditor : GodotActivity() {
 
 	internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO
 
+	/**
+	 * Set of permissions to be excluded when requesting all permissions at startup.
+	 *
+	 * The permissions in this set will be requested on demand based on use cases.
+	 */
+	@CallSuper
+	protected open fun getExcludedPermissions(): MutableSet<String> {
+		return mutableSetOf(
+			// The RECORD_AUDIO permission is requested when the "audio/driver/enable_input" project
+			// setting is enabled.
+			Manifest.permission.RECORD_AUDIO
+		)
+	}
+
 	override fun onCreate(savedInstanceState: Bundle?) {
 		installSplashScreen()
 
@@ -131,8 +146,8 @@ open class GodotEditor : GodotActivity() {
 		}
 
 		// We exclude certain permissions from the set we request at startup, as they'll be
-		// requested on demand based on use-cases.
-		PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
+		// requested on demand based on use cases.
+		PermissionsUtil.requestManifestPermissions(this, getExcludedPermissions())
 
 		val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
 		Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}")
@@ -152,8 +167,6 @@ open class GodotEditor : GodotActivity() {
 		val longPressEnabled = enableLongPressGestures()
 		val panScaleEnabled = enablePanAndScaleGestures()
 
-		checkForProjectPermissionsToEnable()
-
 		runOnUiThread {
 			// Enable long press, panning and scaling gestures
 			godotFragment?.godot?.renderView?.inputHandler?.apply {
@@ -171,17 +184,6 @@ open class GodotEditor : GodotActivity() {
 		}
 	}
 
-	/**
-	 * Check for project permissions to enable
-	 */
-	protected open fun checkForProjectPermissionsToEnable() {
-		// Check for RECORD_AUDIO permission
-		val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
-		if (audioInputEnabled) {
-			PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this)
-		}
-	}
-
 	@CallSuper
 	protected open fun updateCommandLineParams(args: List<String>) {
 		// Update the list of command line params with the new args
@@ -196,7 +198,7 @@ open class GodotEditor : GodotActivity() {
 
 	final override fun getCommandLine() = commandLineParams
 
-	protected open fun getEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+	protected open fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo {
 		var hasEditor = false
 
 		var i = 0
@@ -273,7 +275,7 @@ open class GodotEditor : GodotActivity() {
 	}
 
 	override fun onNewGodotInstanceRequested(args: Array<String>): Int {
-		val editorWindowInfo = getEditorWindowInfo(args)
+		val editorWindowInfo = retrieveEditorWindowInfo(args)
 
 		// Launch a new activity
 		val sourceView = godotFragment?.view
@@ -405,20 +407,26 @@ open class GodotEditor : GodotActivity() {
 
 		return when (policy) {
 			LaunchPolicy.AUTO -> {
-				try {
-					when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
-						ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME
-						ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT
-						ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE -> LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE
-						else -> {
-							// ANDROID_WINDOW_AUTO
-							defaultLaunchPolicy
+				if (isHorizonOSDevice()) {
+					// Horizon OS UX is more desktop-like and has support for launching adjacent
+					// windows. So we always want to launch in adjacent mode when auto is selected.
+					LaunchPolicy.ADJACENT
+				} else {
+					try {
+						when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
+							ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME
+							ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT
+							ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE -> LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE
+							else -> {
+								// ANDROID_WINDOW_AUTO
+								defaultLaunchPolicy
+							}
 						}
+					} catch (e: NumberFormatException) {
+						Log.w(TAG, "Error parsing the Android window placement editor setting", e)
+						// Fall-back to the default launch policy
+						defaultLaunchPolicy
 					}
-				} catch (e: NumberFormatException) {
-					Log.w(TAG, "Error parsing the Android window placement editor setting", e)
-					// Fall-back to the default launch policy
-					defaultLaunchPolicy
 				}
 			}
 
@@ -431,8 +439,16 @@ open class GodotEditor : GodotActivity() {
 	/**
 	 * Returns true the if the device supports picture-in-picture (PiP)
 	 */
-	protected open fun hasPiPSystemFeature() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
-		packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
+	protected open fun hasPiPSystemFeature(): Boolean {
+		if (isNativeXRDevice()) {
+			// Known native XR devices do not support PiP.
+			// Will need to revisit as they update their OS.
+			return false
+		}
+
+		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
+			packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
+	}
 
 	override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
 		super.onActivityResult(requestCode, resultCode, data)

+ 7 - 3
platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt

@@ -42,9 +42,9 @@ import android.util.Log
 import java.util.concurrent.ConcurrentHashMap
 
 /**
- * Used by the [GodotEditor] classes to dispatch messages across processes.
+ * Used by the [BaseGodotEditor] classes to dispatch messages across processes.
  */
-internal class EditorMessageDispatcher(private val editor: GodotEditor) {
+internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
 
 	companion object {
 		private val TAG = EditorMessageDispatcher::class.java.simpleName
@@ -173,7 +173,11 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) {
 		// to the sender.
 		val senderId = messengerBundle.getInt(KEY_EDITOR_ID)
 		val senderMessenger: Messenger? = messengerBundle.getParcelable(KEY_EDITOR_MESSENGER)
-		registerMessenger(senderId, senderMessenger)
+		registerMessenger(senderId, senderMessenger) {
+			// Terminate current instance when parent is no longer available.
+			Log.d(TAG, "Terminating current editor instance because parent is no longer available")
+			editor.finish()
+		}
 
 		// Register ourselves to the sender so that it can communicate with us.
 		registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId)

+ 53 - 4
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt

@@ -30,6 +30,7 @@
 
 package org.godotengine.editor
 
+import android.Manifest
 import android.annotation.SuppressLint
 import android.app.PictureInPictureParams
 import android.content.Intent
@@ -38,12 +39,15 @@ import android.os.Build
 import android.os.Bundle
 import android.util.Log
 import android.view.View
+import androidx.annotation.CallSuper
 import org.godotengine.godot.GodotLib
+import org.godotengine.godot.utils.PermissionsUtil
+import org.godotengine.godot.utils.ProcessPhoenix
 
 /**
  * Drives the 'run project' window of the Godot Editor.
  */
-class GodotGame : GodotEditor() {
+open class GodotGame : GodotEditor() {
 
 	companion object {
 		private val TAG = GodotGame::class.java.simpleName
@@ -136,8 +140,53 @@ class GodotGame : GodotEditor() {
 
 	override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
 
-	override fun checkForProjectPermissionsToEnable() {
-		// Nothing to do.. by the time we get here, the project permissions will have already
-		// been requested by the Editor window.
+	override fun onGodotSetupCompleted() {
+		super.onGodotSetupCompleted()
+		Log.v(TAG, "OnGodotSetupCompleted")
+
+		// Check if we should be running in XR instead (if available) as it's possible we were
+		// launched from the project manager which doesn't have that information.
+		val launchingArgs = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
+		if (launchingArgs != null) {
+			val editorWindowInfo = retrieveEditorWindowInfo(launchingArgs)
+			if (editorWindowInfo != getEditorWindowInfo()) {
+				val relaunchIntent = getNewGodotInstanceIntent(editorWindowInfo, launchingArgs)
+				relaunchIntent.putExtra(EXTRA_NEW_LAUNCH, true)
+					.putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, intent.getBundleExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD))
+
+				Log.d(TAG, "Relaunching XR project using ${editorWindowInfo.windowClassName} with parameters ${launchingArgs.contentToString()}")
+				val godot = godot
+				if (godot != null) {
+					godot.destroyAndKillProcess {
+						ProcessPhoenix.triggerRebirth(this, relaunchIntent)
+					}
+				} else {
+					ProcessPhoenix.triggerRebirth(this, relaunchIntent)
+				}
+				return
+			}
+		}
+
+		// Request project runtime permissions if necessary
+		val permissionsToEnable = getProjectPermissionsToEnable()
+		if (permissionsToEnable.isNotEmpty()) {
+			PermissionsUtil.requestPermissions(this, permissionsToEnable)
+		}
+	}
+
+	/**
+	 * Check for project permissions to enable
+	 */
+	@CallSuper
+	protected open fun getProjectPermissionsToEnable(): MutableList<String> {
+		val permissionsToEnable = mutableListOf<String>()
+
+		// Check for RECORD_AUDIO permission
+		val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
+		if (audioInputEnabled) {
+			permissionsToEnable.add(Manifest.permission.RECORD_AUDIO)
+		}
+
+		return permissionsToEnable
 	}
 }

+ 99 - 0
platform/android/java/editor/src/meta/AndroidManifest.xml

@@ -0,0 +1,99 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:horizonos="http://schemas.horizonos/sdk">
+
+    <horizonos:uses-horizonos-sdk
+        horizonos:minSdkVersion="69"
+        horizonos:targetSdkVersion="69" />
+
+    <uses-feature
+        android:name="android.hardware.vr.headtracking"
+        android:required="true"
+        android:version="1"/>
+
+    <!-- Oculus Quest hand tracking -->
+    <uses-permission android:name="com.oculus.permission.HAND_TRACKING" />
+    <uses-feature
+        android:name="oculus.software.handtracking"
+        android:required="false" />
+
+    <!-- Passthrough feature flag -->
+    <uses-feature android:name="com.oculus.feature.PASSTHROUGH"
+        android:required="false" />
+
+    <!-- Overlay keyboard support -->
+    <uses-feature android:name="oculus.software.overlay_keyboard" android:required="false"/>
+
+    <!-- Render model -->
+    <uses-permission android:name="com.oculus.permission.RENDER_MODEL" />
+    <uses-feature android:name="com.oculus.feature.RENDER_MODEL" android:required="false" />
+
+    <!-- Anchor api -->
+    <uses-permission android:name="com.oculus.permission.USE_ANCHOR_API" />
+
+    <!-- Scene api -->
+    <uses-permission android:name="com.oculus.permission.USE_SCENE" />
+
+    <application>
+
+        <activity
+            android:name=".GodotEditor"
+            android:exported="true"
+            android:screenOrientation="landscape"
+            tools:node="merge"
+            tools:replace="android:screenOrientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.oculus.intent.category.2D" />
+            </intent-filter>
+
+            <meta-data android:name="com.oculus.vrshell.free_resizing_lock_aspect_ratio" android:value="true"/>
+        </activity>
+
+        <activity
+            android:name=".GodotXRGame"
+            android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
+            android:process=":GodotXRGame"
+            android:launchMode="singleTask"
+            android:icon="@mipmap/ic_play_window"
+            android:label="@string/godot_game_activity_name"
+            android:exported="false"
+            android:screenOrientation="landscape"
+            android:resizeableActivity="false"
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="com.oculus.intent.category.VR" />
+                <category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
+            </intent-filter>
+        </activity>
+
+        <!-- Supported Meta devices -->
+        <meta-data
+            android:name="com.oculus.supportedDevices"
+            android:value="quest3|questpro"
+            tools:replace="android:value" />
+
+        <!--
+        We remove this meta-data originating from the vendors plugin as we only need the loader for
+        now since the project being edited provides its own version of the vendors plugin.
+
+        This needs to be removed once we start implementing the immersive version of the project
+        manager and editor windows.
+         -->
+        <meta-data
+            android:name="org.godotengine.plugin.v2.GodotOpenXRMeta"
+            android:value="org.godotengine.openxr.vendors.meta.GodotOpenXRMeta"
+            tools:node="remove" />
+
+        <!-- Enable system splash screen -->
+        <meta-data android:name="com.oculus.ossplash" android:value="true"/>
+        <!-- Enable passthrough background during the splash screen -->
+        <meta-data android:name="com.oculus.ossplash.background" android:value="passthrough-contextual"/>
+
+    </application>
+
+</manifest>

BIN
platform/android/java/editor/src/meta/assets/vr_splash.png


+ 94 - 0
platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt

@@ -0,0 +1,94 @@
+/**************************************************************************/
+/*  GodotEditor.kt                                                        */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+package org.godotengine.editor
+
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.utils.isNativeXRDevice
+
+/**
+ * Primary window of the Godot Editor.
+ *
+ * This is the implementation of the editor used when running on Meta devices.
+ */
+open class GodotEditor : BaseGodotEditor() {
+
+	companion object {
+		private val TAG = GodotEditor::class.java.simpleName
+
+		internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame")
+
+		internal const val USE_ANCHOR_API_PERMISSION = "com.oculus.permission.USE_ANCHOR_API"
+		internal const val USE_SCENE_PERMISSION = "com.oculus.permission.USE_SCENE"
+	}
+
+	override fun getExcludedPermissions(): MutableSet<String> {
+		val excludedPermissions = super.getExcludedPermissions()
+		// The USE_ANCHOR_API and USE_SCENE permissions are requested when the "xr/openxr/enabled"
+		// project setting is enabled.
+		excludedPermissions.add(USE_ANCHOR_API_PERMISSION)
+		excludedPermissions.add(USE_SCENE_PERMISSION)
+		return excludedPermissions
+	}
+
+	override fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+		var hasEditor = false
+		var xrModeOn = false
+
+		var i = 0
+		while (i < args.size) {
+			when (args[i++]) {
+				EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true
+				XR_MODE_ARG -> {
+					val argValue = args[i++]
+					xrModeOn = xrModeOn || ("on" == argValue)
+				}
+			}
+		}
+
+		return if (hasEditor) {
+			EDITOR_MAIN_INFO
+		} else {
+			val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean()
+			if (openxrEnabled && isNativeXRDevice()) {
+				XR_RUN_GAME_INFO
+			} else {
+				RUN_GAME_INFO
+			}
+		}
+	}
+
+	override fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? {
+		return when (instanceId) {
+			XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO
+			else -> super.getEditorWindowInfoForInstanceId(instanceId)
+		}
+	}
+}

+ 71 - 0
platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt

@@ -0,0 +1,71 @@
+/*************************************************************************/
+/*  GodotXRGame.kt                                                       */
+/*************************************************************************/
+/*                       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.GodotLib
+import org.godotengine.godot.utils.PermissionsUtil
+import org.godotengine.godot.xr.XRMode
+
+/**
+ * Provide support for running XR apps / games from the editor window.
+ */
+open class GodotXRGame: GodotGame() {
+
+	override fun overrideOrientationRequest() = true
+
+	override fun updateCommandLineParams(args: List<String>) {
+		val updatedArgs = ArrayList<String>()
+		if (!args.contains(XRMode.OPENXR.cmdLineArg)) {
+			updatedArgs.add(XRMode.OPENXR.cmdLineArg)
+		}
+		if (!args.contains(XR_MODE_ARG)) {
+			updatedArgs.add(XR_MODE_ARG)
+			updatedArgs.add("on")
+		}
+		updatedArgs.addAll(args)
+
+		super.updateCommandLineParams(updatedArgs)
+	}
+
+	override fun getEditorWindowInfo() = XR_RUN_GAME_INFO
+
+	override fun getProjectPermissionsToEnable(): MutableList<String> {
+		val permissionsToEnable = super.getProjectPermissionsToEnable()
+
+		val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean()
+		if (openxrEnabled) {
+			permissionsToEnable.add(USE_ANCHOR_API_PERMISSION)
+			permissionsToEnable.add(USE_SCENE_PERMISSION)
+		}
+
+		return permissionsToEnable
+	}
+}

+ 2 - 2
platform/android/java/lib/build.gradle

@@ -51,7 +51,7 @@ android {
         }
     }
 
-    flavorDimensions "products"
+    flavorDimensions = ["products"]
     productFlavors {
         editor {}
         template {}
@@ -104,7 +104,7 @@ android {
         }
 
         boolean devBuild = buildType == "dev"
-        boolean debugSymbols = devBuild || isAndroidStudio()
+        boolean debugSymbols = devBuild
         boolean runTests = devBuild
         boolean productionBuild = !devBuild
         boolean storeRelease = buildType == "release"

+ 6 - 1
platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java

@@ -31,6 +31,7 @@
 package org.godotengine.godot;
 
 import org.godotengine.godot.input.GodotInputHandler;
+import org.godotengine.godot.utils.DeviceUtils;
 
 import android.view.SurfaceView;
 
@@ -63,7 +64,11 @@ public interface GodotRenderView {
 
 	void setPointerIcon(int pointerType);
 
+	/**
+	 * @return true if pointer capture is supported.
+	 */
 	default boolean canCapturePointer() {
-		return getInputHandler().canCapturePointer();
+		// Pointer capture is not supported on Horizon OS
+		return !DeviceUtils.isHorizonOSDevice() && getInputHandler().canCapturePointer();
 	}
 }

+ 4 - 3
platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java

@@ -68,6 +68,7 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
 			setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
 		}
 		setFocusableInTouchMode(true);
+		setClickable(false);
 	}
 
 	@Override
@@ -132,17 +133,17 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
 
 	@Override
 	public boolean onKeyUp(final int keyCode, KeyEvent event) {
-		return mInputHandler.onKeyUp(keyCode, event);
+		return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
 	}
 
 	@Override
 	public boolean onKeyDown(final int keyCode, KeyEvent event) {
-		return mInputHandler.onKeyDown(keyCode, event);
+		return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
 	}
 
 	@Override
 	public boolean onGenericMotionEvent(MotionEvent event) {
-		return mInputHandler.onGenericMotionEvent(event);
+		return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
 	}
 
 	@Override

+ 52 - 0
platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt

@@ -0,0 +1,52 @@
+/**************************************************************************/
+/*  DeviceUtils.kt                                                        */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+/**
+ * Contains utility methods for detecting specific devices.
+ */
+@file:JvmName("DeviceUtils")
+
+package org.godotengine.godot.utils
+
+import android.os.Build
+
+/**
+ * Returns true if running on Meta's Horizon OS.
+ */
+fun isHorizonOSDevice(): Boolean {
+	return "Oculus".equals(Build.BRAND, true)
+}
+
+/**
+ * Returns true if running on a native Android XR device.
+ */
+fun isNativeXRDevice(): Boolean {
+	return isHorizonOSDevice()
+}

+ 3 - 1
platform/android/java/nativeSrcsConfigs/CMakeLists.txt

@@ -8,6 +8,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
 
 set(GODOT_ROOT_DIR ../../../..)
 set(ANDROID_ROOT_DIR "${GODOT_ROOT_DIR}/platform/android" CACHE STRING "")
+set(OPENXR_INCLUDE_DIR "${GODOT_ROOT_DIR}/thirdparty/openxr/include" CACHE STRING "")
 
 # Get sources
 file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**)
@@ -17,6 +18,7 @@ add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
 target_include_directories(${PROJECT_NAME}
         SYSTEM PUBLIC
         ${GODOT_ROOT_DIR}
-        ${ANDROID_ROOT_DIR})
+        ${ANDROID_ROOT_DIR}
+        ${OPENXR_INCLUDE_DIR})
 
 add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED)

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

@@ -14,6 +14,7 @@ pluginManagement {
         mavenCentral()
         gradlePluginPortal()
         maven { url "https://plugins.gradle.org/m2/" }
+        maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
     }
 }
 

+ 0 - 3
platform/android/plugin/godot_plugin_jni.cpp

@@ -35,7 +35,6 @@
 #include "string_android.h"
 
 #include "core/config/engine.h"
-#include "core/config/project_settings.h"
 #include "core/error/error_macros.h"
 
 static HashMap<String, JNISingleton *> jni_singletons;
@@ -43,7 +42,6 @@ static HashMap<String, JNISingleton *> jni_singletons;
 void unregister_plugins_singletons() {
 	for (const KeyValue<String, JNISingleton *> &E : jni_singletons) {
 		Engine::get_singleton()->remove_singleton(E.key);
-		ProjectSettings::get_singleton()->set(E.key, Variant());
 
 		if (E.value) {
 			memdelete(E.value);
@@ -64,7 +62,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeR
 	jni_singletons[singname] = s;
 
 	Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
-	ProjectSettings::get_singleton()->set(singname, s);
 	return true;
 }