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 00000000000..7bddd4325a8 Binary files /dev/null and b/platform/android/java/editor/src/meta/assets/vr_splash.png 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 00000000000..9f0440e87d9 --- /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 { + 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; }