diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt index 2920d3f4330..0b25611a365 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt @@ -1178,12 +1178,25 @@ class Godot private constructor(val context: Context) { fun isProjectManagerHint() = isEditorBuild() && GodotLib.isProjectManagerHint() /** - * Return true if the given feature is supported. + * Returns true if the feature for the given feature tag is supported in the currently running instance, depending + * on the platform, build, etc. + * + * For reference, see https://docs.godotengine.org/en/stable/classes/class_os.html#class-os-method-has-feature + */ + fun hasFeature(feature: String): Boolean { + return GodotLib.hasFeature(feature) + } + + /** + * Internal method used to query whether the host or the registered plugins supports a given feature. + * + * This is invoked by the native code, and should not be confused with [hasFeature] which is the Android version of + * https://docs.godotengine.org/en/stable/classes/class_os.html#class-os-method-has-feature */ @Keep - private fun hasFeature(feature: String): Boolean { + private fun checkInternalFeatureSupport(feature: String): Boolean { if (primaryHost?.supportsFeature(feature) == true) { - return true; + return true } for (plugin in pluginRegistry.allPlugins) { diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java index fdd0d544485..b57fff1e239 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java @@ -80,11 +80,13 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { private final GodotInputHandler inputHandler; private final GodotRenderer godotRenderer; private final SparseArray customPointerIcons = new SparseArray<>(); + private final boolean isXrDevice; public GodotGLRenderView(Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl, boolean shouldBeTranslucent) { super(godot.getContext()); this.godot = godot; + isXrDevice = godot.hasFeature("xr_runtime"); this.inputHandler = inputHandler; this.godotRenderer = new GodotRenderer(); setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); @@ -172,6 +174,12 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { inputHandler.onPointerCaptureChange(hasCapture); } + @Override + public boolean canCapturePointer() { + // Pointer capture is not supported on XR devices. + return !isXrDevice && inputHandler.canCapturePointer(); + } + @Override public void requestPointerCapture() { if (canCapturePointer()) { diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotLib.java index c39d1013752..8ad186abe1b 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotLib.java @@ -314,4 +314,6 @@ public class GodotLib { static native boolean isEditorHint(); static native boolean isProjectManagerHint(); + + static native boolean hasFeature(String feature); } diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java index 9fe4fb20aa7..c998af7485d 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java @@ -31,7 +31,6 @@ package org.godotengine.godot; import org.godotengine.godot.input.GodotInputHandler; -import org.godotengine.godot.utils.DeviceUtils; import android.view.SurfaceView; @@ -67,8 +66,5 @@ public interface GodotRenderView { /** * @return true if pointer capture is supported. */ - default boolean canCapturePointer() { - // Pointer capture is not supported on native XR devices. - return !DeviceUtils.isNativeXRDevice(getView().getContext()) && getInputHandler().canCapturePointer(); - } + boolean canCapturePointer(); } diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java index 6287065f114..9060baf4480 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java @@ -55,11 +55,13 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { private final GodotInputHandler mInputHandler; private final VkRenderer mRenderer; private final SparseArray customPointerIcons = new SparseArray<>(); + private final boolean isXrDevice; public GodotVulkanRenderView(Godot godot, GodotInputHandler inputHandler, boolean shouldBeTranslucent) { super(godot.getContext()); this.godot = godot; + isXrDevice = godot.hasFeature("xr_runtime"); mInputHandler = inputHandler; mRenderer = new VkRenderer(); setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); @@ -151,6 +153,11 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { return mInputHandler.onGenericMotionEvent(event); } + @Override + public boolean canCapturePointer() { + // Pointer capture is not supported on XR devices. + return !isXrDevice && mInputHandler.canCapturePointer(); + } @Override public void requestPointerCapture() { if (canCapturePointer()) { diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/utils/DeviceUtils.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/utils/DeviceUtils.kt deleted file mode 100644 index 3dbada05e3c..00000000000 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/utils/DeviceUtils.kt +++ /dev/null @@ -1,60 +0,0 @@ -/**************************************************************************/ -/* 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.content.Context -import android.os.Build - -/** - * Returns true if running on Meta Horizon OS. - */ -fun isHorizonOSDevice(context: Context): Boolean { - return context.packageManager.hasSystemFeature("oculus.hardware.standalone_vr") -} - -/** - * Returns true if running on PICO OS. - */ -fun isPicoOSDevice(): Boolean { - return ("Pico".equals(Build.BRAND, true)) -} - -/** - * Returns true if running on a native Android XR device. - */ -fun isNativeXRDevice(context: Context): Boolean { - return isHorizonOSDevice(context) || isPicoOSDevice() -} diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/utils/PermissionsUtil.java index 5f6c068a246..13a2ad30c29 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/utils/PermissionsUtil.java @@ -30,6 +30,8 @@ package org.godotengine.godot.utils; +import org.godotengine.godot.Godot; + import android.Manifest; import android.app.Activity; import android.content.Context; @@ -50,7 +52,6 @@ import androidx.core.content.ContextCompat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -159,7 +160,7 @@ public final class PermissionsUtil { case "CAMERA": permissions.add(Manifest.permission.CAMERA); - if (DeviceUtils.isHorizonOSDevice(activity)) { + if (Godot.getInstance(activity).hasFeature("horizonos")) { // On HorizonOS, these permissions are required to get access to all the device's cameras. permissions.add("horizonos.permission.AVATAR_CAMERA"); permissions.add("horizonos.permission.HEADSET_CAMERA"); diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/xr/HybridAppUtils.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/xr/HybridAppUtils.kt deleted file mode 100644 index 907d226d053..00000000000 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/xr/HybridAppUtils.kt +++ /dev/null @@ -1,79 +0,0 @@ -/**************************************************************************/ -/* HybridAppUtils.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 and constants for hybrid apps. - */ -@file:JvmName("HybridAppUtils") - -package org.godotengine.godot.xr - -import android.util.Log -import org.godotengine.godot.GodotLib - -private const val TAG = "HybridAppUtils" - -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 - } - } -} - -const val HYBRID_APP_FEATURE = "godot_openxr_hybrid_app" -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" - -fun isHybridAppEnabled() = GodotLib.getGlobal("xr/hybrid_app/enabled").toBoolean() - -fun getHybridAppLaunchMode(): HybridMode { - if (!isHybridAppEnabled()) { - return HybridMode.NONE - } - - try { - val launchModeValue = GodotLib.getGlobal("xr/hybrid_app/launch_mode").toInt() - return HybridMode.fromNative(launchModeValue) - } catch (e: Exception) { - Log.w(TAG, "Unable to retrieve 'xr/hybrid_app/launch_mode' project setting", e) - return HybridMode.NONE - } -} diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index dc8983f116f..b3ec6f1c890 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -674,4 +674,13 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_isProjectManagerH } return false; } + +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_hasFeature(JNIEnv *env, jclass clazz, jstring p_feature) { + OS *os = OS::get_singleton(); + if (os) { + String feature = jstring_to_string(p_feature, env); + return os->has_feature(feature); + } + return false; +} } diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index d6b38663cdb..930f08e08cd 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -77,4 +77,5 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInp JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getProjectResourceDir(JNIEnv *env, jclass clazz); JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_isEditorHint(JNIEnv *env, jclass clazz); JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_isProjectManagerHint(JNIEnv *env, jclass clazz); +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_hasFeature(JNIEnv *env, jclass clazz, jstring p_feature); } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 087b0bb02cd..13d7ed21784 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -80,7 +80,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;Ljava/lang/String;)V"); _dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V"); _get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;"); - _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z"); + _check_internal_feature_support = p_env->GetMethodID(godot_class, "checkInternalFeatureSupport", "(Ljava/lang/String;)Z"); _sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); _verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I"); _enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V"); @@ -517,13 +517,13 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { } } -bool GodotJavaWrapper::has_feature(const String &p_feature) const { - if (_has_feature) { +bool GodotJavaWrapper::check_internal_feature_support(const String &p_feature) const { + if (_check_internal_feature_support) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, false); jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data()); - bool result = env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + bool result = env->CallBooleanMethod(godot_instance, _check_internal_feature_support, j_feature); env->DeleteLocalRef(j_feature); return result; } else { diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 62e8e6ec827..e0db941f765 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -76,7 +76,7 @@ private: jmethodID _begin_benchmark_measure = nullptr; jmethodID _end_benchmark_measure = nullptr; jmethodID _dump_benchmark = nullptr; - jmethodID _has_feature = nullptr; + jmethodID _check_internal_feature_support = nullptr; jmethodID _sign_apk = nullptr; jmethodID _verify_apk = nullptr; jmethodID _enable_immersive_mode = nullptr; @@ -129,7 +129,7 @@ public: Vector get_gdextension_list_config_file() const; // Return true if the given feature is supported. - bool has_feature(const String &p_feature) const; + bool check_internal_feature_support(const String &p_feature) const; // Sign and verify apks Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 227bb9e5f3a..daccd38ce3b 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -878,7 +878,7 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) { } #endif - if (godot_java->has_feature(p_feature)) { + if (godot_java->check_internal_feature_support(p_feature)) { return true; }