diff --git a/plugin/src/main/cpp/export/export_plugin.cpp b/plugin/src/main/cpp/export/export_plugin.cpp
index 6245de4d..e365f273 100644
--- a/plugin/src/main/cpp/export/export_plugin.cpp
+++ b/plugin/src/main/cpp/export/export_plugin.cpp
@@ -91,8 +91,15 @@ Dictionary OpenXREditorExportPlugin::_get_vendor_toggle_option(const String &ven
true);
}
-OpenXREditorExportPlugin::HybridType OpenXREditorExportPlugin::_get_hybrid_app_setting_value() const {
- return (HybridType)(int)ProjectSettings::get_singleton()->get_setting_with_override("xr/openxr/hybrid_app");
+bool OpenXREditorExportPlugin::_is_hybrid_app_enabled() const {
+ return ProjectSettings::get_singleton()->get_setting_with_override("xr/hybrid_app/enabled");
+}
+
+OpenXRHybridApp::HybridMode OpenXREditorExportPlugin::_get_hybrid_app_launch_mode() const {
+ if (!_is_hybrid_app_enabled()) {
+ return OpenXRHybridApp::HYBRID_MODE_NONE;
+ }
+ return (OpenXRHybridApp::HybridMode)(int)ProjectSettings::get_singleton()->get_setting_with_override("xr/hybrid_app/launch_mode");
}
String OpenXREditorExportPlugin::_get_opening_activity_tag_for_panel_app() const {
@@ -127,6 +134,12 @@ String OpenXREditorExportPlugin::_get_common_activity_intent_filter_contents() c
)";
}
+
+ if (_is_hybrid_app_enabled()) {
+ contents += R"(
+
+)";
+ }
return contents;
}
@@ -198,11 +211,11 @@ Dictionary OpenXREditorExportPlugin::_get_export_options_overrides(const Ref\n";
}
- HybridType hybrid_type = _get_hybrid_app_setting_value();
- if (hybrid_type != HYBRID_TYPE_DISABLED) {
+ OpenXRHybridApp::HybridMode hybrid_launch_mode = _get_hybrid_app_launch_mode();
+ if (hybrid_launch_mode != OpenXRHybridApp::HYBRID_MODE_NONE) {
contents += _get_opening_activity_tag_for_panel_app();
contents +=
" \n"
" \n"
" \n"
+ " \n"
" \n";
- if (hybrid_type == HYBRID_TYPE_START_AS_PANEL) {
+ if (hybrid_launch_mode == OpenXRHybridApp::HYBRID_MODE_PANEL) {
contents += " \n";
}
@@ -545,6 +546,8 @@ String MetaEditorExportPlugin::_get_android_manifest_activity_element_contents(c
+
+
diff --git a/plugin/src/main/cpp/include/export/export_plugin.h b/plugin/src/main/cpp/include/export/export_plugin.h
index 39f0332a..15bb2686 100644
--- a/plugin/src/main/cpp/include/export/export_plugin.h
+++ b/plugin/src/main/cpp/include/export/export_plugin.h
@@ -29,6 +29,8 @@
#pragma once
+#include "classes/openxr_hybrid_app.h"
+
#include
#include
#include
@@ -70,12 +72,6 @@ class OpenXREditorExportPlugin : public EditorExportPlugin {
GDCLASS(OpenXREditorExportPlugin, EditorExportPlugin)
public:
- enum HybridType {
- HYBRID_TYPE_DISABLED,
- HYBRID_TYPE_START_AS_IMMERSIVE,
- HYBRID_TYPE_START_AS_PANEL,
- };
-
OpenXREditorExportPlugin();
String _get_name() const override;
@@ -114,7 +110,8 @@ class OpenXREditorExportPlugin : public EditorExportPlugin {
Dictionary _get_vendor_toggle_option(const String &vendor_name) const;
- HybridType _get_hybrid_app_setting_value() const;
+ bool _is_hybrid_app_enabled() const;
+ OpenXRHybridApp::HybridMode _get_hybrid_app_launch_mode() const;
String _get_opening_activity_tag_for_panel_app() const;
diff --git a/plugin/src/main/cpp/register_types.cpp b/plugin/src/main/cpp/register_types.cpp
index 580f4968..dc774e4e 100644
--- a/plugin/src/main/cpp/register_types.cpp
+++ b/plugin/src/main/cpp/register_types.cpp
@@ -291,19 +291,32 @@ void add_plugin_project_settings() {
}
{
- String hybrid_app_setting = "xr/openxr/hybrid_app";
- if (!project_settings->has_setting(hybrid_app_setting)) {
- project_settings->set_setting(hybrid_app_setting, OpenXRHybridApp::HYBRID_MODE_NONE);
+ String hybrid_app_enabled_setting = "xr/hybrid_app/enabled";
+ if (!project_settings->has_setting(hybrid_app_enabled_setting)) {
+ project_settings->set_setting(hybrid_app_enabled_setting, false);
}
- project_settings->set_initial_value(hybrid_app_setting, OpenXRHybridApp::HYBRID_MODE_NONE);
- project_settings->set_as_basic(hybrid_app_setting, false);
- Dictionary property_info;
- property_info["name"] = hybrid_app_setting;
- property_info["type"] = Variant::Type::INT;
- property_info["hint"] = PROPERTY_HINT_ENUM;
- property_info["hint_string"] = "Disabled:-1,Start As Immersive:0,Start As Panel:1";
- project_settings->add_property_info(property_info);
+ project_settings->set_initial_value(hybrid_app_enabled_setting, false);
+ project_settings->set_as_basic(hybrid_app_enabled_setting, true);
+ Dictionary hybrid_app_enabled_property_info;
+ hybrid_app_enabled_property_info["name"] = hybrid_app_enabled_setting;
+ hybrid_app_enabled_property_info["type"] = Variant::Type::BOOL;
+ hybrid_app_enabled_property_info["hint"] = PROPERTY_HINT_NONE;
+ project_settings->add_property_info(hybrid_app_enabled_property_info);
+
+ String hybrid_app_launch_mode_setting = "xr/hybrid_app/launch_mode";
+ if (!project_settings->has_setting(hybrid_app_launch_mode_setting)) {
+ project_settings->set_setting(hybrid_app_launch_mode_setting, OpenXRHybridApp::HYBRID_MODE_IMMERSIVE);
+ }
+
+ project_settings->set_initial_value(hybrid_app_launch_mode_setting, OpenXRHybridApp::HYBRID_MODE_IMMERSIVE);
+ project_settings->set_as_basic(hybrid_app_launch_mode_setting, true);
+ Dictionary hybrid_app_launch_mode_property_info;
+ hybrid_app_launch_mode_property_info["name"] = hybrid_app_launch_mode_setting;
+ hybrid_app_launch_mode_property_info["type"] = Variant::Type::INT;
+ hybrid_app_launch_mode_property_info["hint"] = PROPERTY_HINT_ENUM;
+ hybrid_app_launch_mode_property_info["hint_string"] = "Start As Immersive:0,Start As Panel:1";
+ project_settings->add_property_info(hybrid_app_launch_mode_property_info);
}
}
diff --git a/plugin/src/main/java/org/godotengine/openxr/vendors/GodotOpenXRHybridAppInternal.kt b/plugin/src/main/java/org/godotengine/openxr/vendors/GodotOpenXRHybridAppInternal.kt
index 060e0fa0..24af0184 100644
--- a/plugin/src/main/java/org/godotengine/openxr/vendors/GodotOpenXRHybridAppInternal.kt
+++ b/plugin/src/main/java/org/godotengine/openxr/vendors/GodotOpenXRHybridAppInternal.kt
@@ -30,13 +30,16 @@
package org.godotengine.openxr.vendors
import android.app.Activity
-import android.content.ComponentName
+import android.app.PendingIntent
import android.content.Intent
import android.util.Log
-import android.view.View;
+import android.view.View
import org.godotengine.godot.Godot
+import org.godotengine.godot.GodotHost
+import org.godotengine.godot.GodotLib
import org.godotengine.godot.plugin.GodotPlugin
import org.godotengine.godot.plugin.UsedByGodot
+import org.godotengine.godot.utils.isHorizonOSDevice
import org.godotengine.godot.utils.isNativeXRDevice
/**
@@ -44,55 +47,126 @@ import org.godotengine.godot.utils.isNativeXRDevice
*/
class GodotOpenXRHybridAppInternal(godot: Godot?) : GodotPlugin(godot) {
companion object {
- protected val TAG = GodotOpenXRHybridAppInternal::class.java.simpleName
+ private val TAG = GodotOpenXRHybridAppInternal::class.java.simpleName
- private const val IMMERSIVE_ACTIVITY = "com.godot.game.GodotApp"
- private const val PANEL_ACTIVITY = "org.godotengine.openxr.vendors.GodotPanelApp"
+ private const val EXTRA_HYBRID_LAUNCH_DATA = "godot_openxr_vendors_hybrid_launch_data"
- private const val IMMERSIVE_MODE = 0
- private const val PANEL_MODE = 1
+ // TODO: Duplicate from the Godot Android library; should remove once the lib dependency
+ // is updated
+ const val HYBRID_APP_PANEL_FEATURE = "godot_openxr_panel_app"
+ const val HYBRID_APP_PANEL_CATEGORY = "org.godotengine.xr.hybrid.PANEL"
+ const val HYBRID_APP_IMMERSIVE_CATEGORY = "org.godotengine.xr.hybrid.IMMERSIVE"
- private const val INTENT_EXTRA_DATA = "godot_openxr_vendors_data"
+ private fun getHybridMode(activity: Activity?): HybridMode {
+ if (activity !is GodotHost) {
+ return HybridMode.NONE
+ }
+
+ // Check if hybrid is enabled
+ val hybridEnabled = GodotLib.getGlobal("xr/hybrid_app/enabled").toBoolean()
+ if (!hybridEnabled) {
+ return HybridMode.NONE
+ }
+
+ if (activity.supportsFeature(HYBRID_APP_PANEL_FEATURE)) {
+ return HybridMode.PANEL
+ }
+
+ return HybridMode.IMMERSIVE
+ }
+ }
+
+ /**
+ * Should match OpenXRHybridApp#HybridMode.
+ */
+ private enum class HybridMode(private val nativeValue: Int) {
+ NONE( -1),
+ IMMERSIVE(0),
+ PANEL(1);
+
+ companion object {
+ fun fromNative(nativeValue: Int): HybridMode {
+ for (mode in HybridMode.entries) {
+ if (mode.nativeValue == nativeValue) {
+ return mode
+ }
+ }
+ return NONE
+ }
+ }
}
- private var hybridMode: Int = IMMERSIVE_MODE
+ private var hybridMode = HybridMode.IMMERSIVE
private var hybridLaunchData: String = ""
override fun getPluginName() = "GodotOpenXRHybridAppInternal"
override fun onMainCreate(activity: Activity): View? {
- if (activity::class.qualifiedName == PANEL_ACTIVITY) {
- hybridMode = PANEL_MODE
- } else {
- hybridMode = IMMERSIVE_MODE
- }
-
- hybridLaunchData = activity.intent.getStringExtra(INTENT_EXTRA_DATA) ?: ""
-
+ hybridLaunchData = activity.intent.getStringExtra(EXTRA_HYBRID_LAUNCH_DATA) ?: ""
return null
}
+ override fun onGodotSetupCompleted() {
+ super.onGodotSetupCompleted()
+ hybridMode = getHybridMode(activity)
+ }
+
@UsedByGodot
- fun hybridAppSwitchTo(mode: Int, data: String = ""): Boolean {
+ fun hybridAppSwitchTo(modeValue: Int, data: String = ""): Boolean {
+ val mode = HybridMode.fromNative(modeValue)
if (hybridMode == mode) return false
- val context = getActivity() ?: return false
+ val context = activity ?: return false
if (!isNativeXRDevice(context)) return false
- val activityName = if (mode == IMMERSIVE_MODE) IMMERSIVE_ACTIVITY else PANEL_ACTIVITY
- val newInstance = Intent()
- .setComponent(ComponentName(context, activityName))
+ val hybridCategory = if (mode == HybridMode.IMMERSIVE) HYBRID_APP_IMMERSIVE_CATEGORY else HYBRID_APP_PANEL_CATEGORY
+ val hybridLaunchIntent = Intent()
+ .addCategory(hybridCategory)
+ .setPackage(context.packageName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(INTENT_EXTRA_DATA, data)
+ .putExtra(EXTRA_HYBRID_LAUNCH_DATA, data)
+
+ // Resolve the component needed for the launch
+ val hybridLaunchComponentName = hybridLaunchIntent.resolveActivity(context.packageManager)
+ if (hybridLaunchComponentName == null) {
+ Log.e(TAG, "Unable to resolve hybrid mode launch intent $hybridLaunchIntent")
+ return false
+ } else {
+ Log.d(TAG, "Resolved hybrid launch component: $hybridLaunchComponentName")
+ hybridLaunchIntent.setComponent(hybridLaunchComponentName)
+ }
+
+ val launchIntent = if (mode == HybridMode.PANEL && isHorizonOSDevice(context)) {
+ // HorizonOS has a different launch flow for panel mode.
+ // Wrap the created Intent in a PendingIntent object
+ val pendingPanelIntent =
+ PendingIntent.getActivity(
+ context,
+ 0,
+ hybridLaunchIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+
+ // Create and send the Intent to launch the Home environment, providing the
+ // PendingIntent object as extra parameters
+ val homeIntent =
+ Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra("extra_launch_in_home_pending_intent", pendingPanelIntent)
+
+ homeIntent
+ } else {
+ hybridLaunchIntent
+ }
val godot = godot
if (godot != null) {
godot.destroyAndKillProcess {
- context.startActivity(newInstance)
+ context.startActivity(launchIntent)
}
} else {
- context.startActivity(newInstance)
+ context.startActivity(launchIntent)
context.finish()
}
diff --git a/plugin/src/main/java/org/godotengine/openxr/vendors/GodotPanelApp.kt b/plugin/src/main/java/org/godotengine/openxr/vendors/GodotPanelApp.kt
index 4303104b..3b2589d8 100644
--- a/plugin/src/main/java/org/godotengine/openxr/vendors/GodotPanelApp.kt
+++ b/plugin/src/main/java/org/godotengine/openxr/vendors/GodotPanelApp.kt
@@ -89,7 +89,7 @@ class GodotPanelApp : GodotActivity() {
}
override fun supportsFeature(featureTag: String): Boolean {
- if ("godot_openxr_panel_app" == featureTag) {
+ if (GodotOpenXRHybridAppInternal.HYBRID_APP_PANEL_FEATURE == featureTag) {
return true
}
diff --git a/samples/.gitignore b/samples/.gitignore
index fbf95ea5..32e1a3a8 100644
--- a/samples/.gitignore
+++ b/samples/.gitignore
@@ -2,3 +2,4 @@
*/.godot/
*/addons/godotopenxrvendors
*/android/
+builds/
diff --git a/samples/hybrid-app-sample/export_presets.cfg b/samples/hybrid-app-sample/export_presets.cfg
index 6d6deecd..ea8039e5 100644
--- a/samples/hybrid-app-sample/export_presets.cfg
+++ b/samples/hybrid-app-sample/export_presets.cfg
@@ -50,7 +50,7 @@ launcher_icons/adaptive_background_432x432=""
launcher_icons/adaptive_monochrome_432x432=""
graphics/opengl_debug=false
xr_features/xr_mode=1
-wear_os/swipe_to_dismiss=true
+gesture/swipe_to_dismiss=false
screen/immersive_mode=true
screen/support_small=true
screen/support_normal=true
@@ -245,3 +245,4 @@ pico_xr_features/face_tracking=0
pico_xr_features/hand_tracking=0
xr_features/enable_magicleap_plugin=false
magicleap_xr_features/hand_tracking=0
+meta_xr_features/instant_splash_screen=false
diff --git a/samples/hybrid-app-sample/project.godot b/samples/hybrid-app-sample/project.godot
index a626014b..09954e48 100644
--- a/samples/hybrid-app-sample/project.godot
+++ b/samples/hybrid-app-sample/project.godot
@@ -37,8 +37,10 @@ textures/vram_compression/import_etc2_astc=true
openxr/enabled=true
openxr/reference_space=2
+openxr/environment_blend_mode=2
openxr/foveation_level=3
openxr/foveation_dynamic=true
openxr/startup_alert=false
shaders/enabled=true
-openxr/hybrid_app=1
+hybrid_app/enabled=true
+hybrid_app/launch_mode=1