From 9dc0543da7323e970f4349e65b416ef31056df20 Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Sat, 20 Apr 2024 10:24:11 -0700 Subject: [PATCH] Improve support for XR projects --- .github/workflows/android_builds.yml | 1 + main/main.cpp | 3 +- modules/openxr/openxr_api.cpp | 29 +++-- platform/android/java/app/build.gradle | 1 + platform/android/java/app/settings.gradle | 1 + platform/android/java/build.gradle | 45 +++++--- platform/android/java/editor/build.gradle | 43 ++++++-- .../org/godotengine/editor/GodotEditor.kt | 39 +++++++ .../{GodotEditor.kt => BaseGodotEditor.kt} | 100 ++++++++++-------- .../editor/EditorMessageDispatcher.kt | 10 +- .../java/org/godotengine/editor/GodotGame.kt | 57 +++++++++- .../java/editor/src/meta/AndroidManifest.xml | 99 +++++++++++++++++ .../java/editor/src/meta/assets/vr_splash.png | Bin 0 -> 14766 bytes .../org/godotengine/editor/GodotEditor.kt | 94 ++++++++++++++++ .../org/godotengine/editor/GodotXRGame.kt | 71 +++++++++++++ platform/android/java/lib/build.gradle | 4 +- .../godotengine/godot/GodotRenderView.java | 7 +- .../godot/GodotVulkanRenderView.java | 7 +- .../godotengine/godot/utils/DeviceUtils.kt | 52 +++++++++ .../java/nativeSrcsConfigs/CMakeLists.txt | 4 +- platform/android/java/settings.gradle | 1 + platform/android/plugin/godot_plugin_jni.cpp | 3 - 22 files changed, 572 insertions(+), 99 deletions(-) create mode 100644 platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt rename platform/android/java/editor/src/main/java/org/godotengine/editor/{GodotEditor.kt => BaseGodotEditor.kt} (86%) create mode 100644 platform/android/java/editor/src/meta/AndroidManifest.xml create mode 100644 platform/android/java/editor/src/meta/assets/vr_splash.png create mode 100644 platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt create mode 100644 platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt create mode 100644 platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index ee75d532828..69ab8308293 100644 --- a/.github/workflows/android_builds.yml +++ b/.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/ diff --git a/main/main.cpp b/main/main.cpp index 1af08bc0362..18ffedef187 100644 --- a/main/main.cpp +++ b/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()); diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index ecf7c05789f..a19a75e7225 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -271,17 +271,14 @@ OpenXRAPI *OpenXRAPI::singleton = nullptr; Vector 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"); diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 05b4f379b32..b9d15deec9a 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 dcac44e393e..e758d4e99a7 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 771bda69483..974f072c180 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 b8b42336361..54d6b9b5f34 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 00000000000..f15d9f77682 --- /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 similarity index 86% rename from platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt rename to platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index 405b2fb57f8..50daf44d2a2 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 { + 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) { // 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): EditorWindowInfo { + protected open fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { var hasEditor = false var i = 0 @@ -273,7 +275,7 @@ open class GodotEditor : GodotActivity() { } override fun onNewGodotInstanceRequested(args: Array): 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 b16e62149a3..f5a6ed7dabf 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 6b4bf255f21..e52d5663474 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 { + val permissionsToEnable = mutableListOf() + + // 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 00000000000..06442ac4e63 --- /dev/null +++ b/platform/android/java/editor/src/meta/AndroidManifest.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..7bddd4325a8e2f425a42715770971a7a855b4803 GIT binary patch literal 14766 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 { + 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): 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 00000000000..d71fbb53f21 --- /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) { + val updatedArgs = ArrayList() + 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 { + 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 81ab598b90e..f6aee434e55 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 30821eaa8e1..9db9ef6080e 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 d5b05913d8d..8b3880d32d6 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 00000000000..abe0c5f8854 --- /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 96b6dfc9f38..a7d2774db55 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 3137e742441..41418a4a45e 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/"} } } diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index e3cde145cb6..75c8dd95281 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/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 jni_singletons; @@ -43,7 +42,6 @@ static HashMap jni_singletons; void unregister_plugins_singletons() { for (const KeyValue &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; }