summaryrefslogtreecommitdiffstats
path: root/platform/android/java
diff options
context:
space:
mode:
authorFredia Huya-Kouadio <fhuya@meta.com>2024-04-20 10:24:11 -0700
committerFredia Huya-Kouadio <fhuya@meta.com>2024-09-06 00:35:50 -0700
commit9dc0543da7323e970f4349e65b416ef31056df20 (patch)
tree7350403dbba62a1a74830cbdb44978cdb7bda4ae /platform/android/java
parent835808ed8fa992c961d6989f0a0c48ed2abd69bd (diff)
downloadredot-engine-9dc0543da7323e970f4349e65b416ef31056df20.tar.gz
Improve support for XR projects
Diffstat (limited to 'platform/android/java')
-rw-r--r--platform/android/java/app/build.gradle1
-rw-r--r--platform/android/java/app/settings.gradle1
-rw-r--r--platform/android/java/build.gradle45
-rw-r--r--platform/android/java/editor/build.gradle43
-rw-r--r--platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt39
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt (renamed from platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt)100
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt10
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt57
-rw-r--r--platform/android/java/editor/src/meta/AndroidManifest.xml99
-rw-r--r--platform/android/java/editor/src/meta/assets/vr_splash.pngbin0 -> 14766 bytes
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt94
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt71
-rw-r--r--platform/android/java/lib/build.gradle4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt52
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt4
-rw-r--r--platform/android/java/settings.gradle1
18 files changed, 556 insertions, 79 deletions
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 05b4f379b3..b9d15deec9 100644
--- a/platform/android/java/app/build.gradle
+++ b/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()
diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle
index dcac44e393..e758d4e99a 100644
--- a/platform/android/java/app/settings.gradle
+++ b/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/"}
}
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 771bda6948..974f072c18 100644
--- a/platform/android/java/build.gradle
+++ b/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")
}
/**
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index b8b4233636..54d6b9b5f3 100644
--- a/platform/android/java/editor/build.gradle
+++ b/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"
}
diff --git a/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt
new file mode 100644
index 0000000000..f15d9f7768
--- /dev/null
+++ b/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() {
+}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
index 405b2fb57f..50daf44d2a 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/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)
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
index b16e62149a..f5a6ed7dab 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
+++ b/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)
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
index 6b4bf255f2..e52d566347 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
+++ b/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
}
}
diff --git a/platform/android/java/editor/src/meta/AndroidManifest.xml b/platform/android/java/editor/src/meta/AndroidManifest.xml
new file mode 100644
index 0000000000..06442ac4e6
--- /dev/null
+++ b/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>
diff --git a/platform/android/java/editor/src/meta/assets/vr_splash.png b/platform/android/java/editor/src/meta/assets/vr_splash.png
new file mode 100644
index 0000000000..7bddd4325a
--- /dev/null
+++ b/platform/android/java/editor/src/meta/assets/vr_splash.png
Binary files differ
diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
new file mode 100644
index 0000000000..9f0440e87d
--- /dev/null
+++ b/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)
+ }
+ }
+}
diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
new file mode 100644
index 0000000000..d71fbb53f2
--- /dev/null
+++ b/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
+ }
+}
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 81ab598b90..f6aee434e5 100644
--- a/platform/android/java/lib/build.gradle
+++ b/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"
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 30821eaa8e..9db9ef6080 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/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();
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index d5b05913d8..8b3880d32d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/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
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt
new file mode 100644
index 0000000000..abe0c5f885
--- /dev/null
+++ b/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()
+}
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index 96b6dfc9f3..a7d2774db5 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/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)
diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle
index 3137e74244..41418a4a45 100644
--- a/platform/android/java/settings.gradle
+++ b/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/"}
}
}