diff options
Diffstat (limited to 'platform/android')
16 files changed, 156 insertions, 55 deletions
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 11c0945ce0..445a6ea6ea 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -309,13 +309,10 @@ void DisplayServerAndroid::window_set_drop_files_callback(const Callable &p_call void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred) const { if (!p_callable.is_null()) { - const Variant *argp = &p_arg; - Variant ret; - Callable::CallError ce; if (p_deferred) { - p_callable.callp((const Variant **)&argp, 1, ret, ce); + p_callable.call(p_arg); } else { - p_callable.call_deferredp((const Variant **)&argp, 1); + p_callable.call_deferred(p_arg); } } } @@ -538,16 +535,9 @@ void DisplayServerAndroid::reset_window() { } void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) { - if (rect_changed_callback.is_null()) { - return; + if (rect_changed_callback.is_valid()) { + rect_changed_callback.call(Rect2i(0, 0, p_width, p_height)); } - - const Variant size = Rect2i(0, 0, p_width, p_height); - const Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - - rect_changed_callback.callp(reinterpret_cast<const Variant **>(&sizep), 1, ret, ce); } DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) { diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index aeaa7b9ce7..c3015ec260 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2238,6 +2238,19 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito #ifdef MODULE_MONO_ENABLED // Android export is still a work in progress, keep a message as a warning. err += TTR("Exporting to Android when using C#/.NET is experimental.") + "\n"; + + bool unsupported_arch = false; + Vector<ABI> enabled_abis = get_enabled_abis(p_preset); + for (ABI abi : enabled_abis) { + if (abi.arch != "arm64" && abi.arch != "x86_64") { + err += vformat(TTR("Android architecture %s not supported in C# projects."), abi.arch) + "\n"; + unsupported_arch = true; + } + } + if (unsupported_arch) { + r_error = err; + return false; + } #endif // Look for export templates (first official, and if defined custom templates). 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/GodotEditor.kt index 7cedfa6888..02709d4dc5 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/GodotEditor.kt @@ -91,10 +91,6 @@ open class GodotEditor : GodotActivity() { private val commandLineParams = ArrayList<String>() override fun onCreate(savedInstanceState: Bundle?) { - // 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)) - val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) Log.d(TAG, "Received parameters ${params.contentToString()}") updateCommandLineParams(params) diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 0e111d5247..04d196a6e9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -84,7 +84,7 @@ class Godot(private val context: Context) : SensorEventListener { } private val pluginRegistry: GodotPluginRegistry by lazy { - GodotPluginRegistry.initializePluginRegistry(this) + GodotPluginRegistry.getPluginRegistry() } private val mSensorManager: SensorManager by lazy { requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager @@ -190,7 +190,7 @@ class Godot(private val context: Context) : SensorEventListener { val activity = requireActivity() val window = activity.window window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) - GodotPluginRegistry.initializePluginRegistry(this) + GodotPluginRegistry.initializePluginRegistry(this, primaryHost.getHostPlugins(this)) if (io == null) { io = GodotIO(activity) } @@ -250,11 +250,7 @@ class Godot(private val context: Context) : SensorEventListener { } i++ } - if (newArgs.isEmpty()) { - commandLine = mutableListOf() - } else { - commandLine = newArgs - } + commandLine = if (newArgs.isEmpty()) { mutableListOf() } else { newArgs } if (useApkExpansion && mainPackMd5 != null && mainPackKey != null) { // Build the full path to the app's expansion files try { @@ -392,6 +388,10 @@ class Godot(private val context: Context) : SensorEventListener { // Fallback to openGl GodotGLRenderView(host, this, xrMode, useDebugOpengl) } + + renderView?.inputHandler?.enableLongPress(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))) + renderView?.inputHandler?.enablePanningAndScalingGestures(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))) + if (host == primaryHost) { renderView!!.startRenderer() } @@ -616,7 +616,7 @@ class Godot(private val context: Context) : SensorEventListener { private fun alert(message: String, title: String, okCallback: Runnable?) { val activity: Activity = getActivity() ?: return - runOnUiThread(Runnable { + runOnUiThread { val builder = AlertDialog.Builder(activity) builder.setMessage(message).setTitle(title) builder.setPositiveButton( @@ -627,7 +627,7 @@ class Godot(private val context: Context) : SensorEventListener { } val dialog = builder.create() dialog.show() - }) + } } /** @@ -685,9 +685,9 @@ class Godot(private val context: Context) : SensorEventListener { return false } - private fun setKeepScreenOn(p_enabled: Boolean) { + private fun setKeepScreenOn(enabled: Boolean) { runOnUiThread { - if (p_enabled) { + if (enabled) { getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } else { getActivity()?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -835,9 +835,7 @@ class Godot(private val context: Context) : SensorEventListener { } } - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - // Do something here if sensor accuracy changes. - } + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} /** * Used by the native code (java_godot_wrapper.h) to vibrate the device. @@ -865,7 +863,7 @@ class Godot(private val context: Context) : SensorEventListener { private fun getCommandLine(): MutableList<String> { val original: MutableList<String> = parseCommandLine() val hostCommandLine = primaryHost?.commandLine - if (hostCommandLine != null && hostCommandLine.isNotEmpty()) { + if (!hostCommandLine.isNullOrEmpty()) { original.addAll(hostCommandLine) } return original @@ -928,6 +926,19 @@ class Godot(private val context: Context) : SensorEventListener { } /** + * Return true if the given feature is supported. + */ + @Keep + private fun hasFeature(feature: String): Boolean { + for (plugin in pluginRegistry.allPlugins) { + if (plugin.supportsFeature(feature)) { + return true + } + } + return false + } + + /** * Get the list of gdextension modules to register. */ @Keep diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt index 4636f753af..a60f6e997e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -30,12 +30,15 @@ package org.godotengine.godot +import android.Manifest import android.app.Activity import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import androidx.annotation.CallSuper import androidx.fragment.app.FragmentActivity +import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.ProcessPhoenix /** @@ -62,6 +65,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { private set override fun onCreate(savedInstanceState: Bundle?) { + // 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)) + super.onCreate(savedInstanceState) setContentView(R.layout.godot_app_layout) @@ -148,6 +155,14 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE) { + Log.d(TAG, "Received permissions request result..") + for (i in permissions.indices) { + val permissionGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED + Log.d(TAG, "Permission ${permissions[i]} ${if (permissionGranted) { "granted"} else { "denied" }}") + } + } } override fun onBackPressed() { @@ -158,6 +173,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { return this } + override fun getGodot(): Godot? { + return godotFragment?.godot + } + /** * Used to initialize the Godot fragment instance in [onCreate]. */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index 9a8b10ea3e..120e1722e5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -30,6 +30,7 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.utils.BenchmarkUtils; import android.app.Activity; @@ -65,6 +66,7 @@ import com.google.android.vending.expansion.downloader.IStub; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Set; /** * Base fragment for Android apps intending to use Godot for part of the app's UI. @@ -122,6 +124,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } public ResultCallback resultCallback; + @Override public Godot getGodot() { return godot; } @@ -426,4 +429,13 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } return 0; } + + @Override + @CallSuper + public Set<GodotPlugin> getHostPlugins(Godot engine) { + if (parentHost != null) { + return parentHost.getHostPlugins(engine); + } + return Collections.emptySet(); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index e5333085dd..1862b9fa9b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -30,39 +30,42 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; + import android.app.Activity; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} engine. */ public interface GodotHost { /** - * Provides a set of command line parameters to setup the engine. + * Provides a set of command line parameters to setup the {@link Godot} engine. */ default List<String> getCommandLine() { return Collections.emptyList(); } /** - * Invoked on the render thread when the Godot setup is complete. + * Invoked on the render thread when setup of the {@link Godot} engine is complete. */ default void onGodotSetupCompleted() {} /** - * Invoked on the render thread when the Godot main loop has started. + * Invoked on the render thread when the {@link Godot} engine main loop has started. */ default void onGodotMainLoopStarted() {} /** - * Invoked on the render thread to terminate the given Godot instance. + * Invoked on the render thread to terminate the given {@link Godot} engine instance. */ default void onGodotForceQuit(Godot instance) {} /** - * Invoked on the render thread to terminate the Godot instance with the given id. + * Invoked on the render thread to terminate the {@link Godot} engine instance with the given id. * @param godotInstanceId id of the Godot instance to terminate. See {@code onNewGodotInstanceRequested} * * @return true if successful, false otherwise. @@ -90,7 +93,19 @@ public interface GodotHost { } /** - * Provide access to the Activity hosting the Godot engine. + * Provide access to the Activity hosting the {@link Godot} engine. */ Activity getActivity(); + + /** + * Provide access to the hosted {@link Godot} engine. + */ + Godot getGodot(); + + /** + * Returns a set of {@link GodotPlugin} to be registered with the hosted {@link Godot} engine. + */ + default Set<GodotPlugin> getHostPlugins(Godot engine) { + return Collections.emptySet(); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt index 68cd2c1358..795dc921c7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt @@ -8,8 +8,10 @@ import android.util.Log /** * Godot service responsible for hosting the Godot engine instance. + * + * Note: Still in development, so it's made private and inaccessible until completed. */ -class GodotService : Service() { +private class GodotService : Service() { companion object { private val TAG = GodotService::class.java.simpleName diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index c8b222254e..38c115ad7f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -95,7 +95,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { /** * Enable multi-fingers pan & scale gestures. This is false by default. - * + * <p> * Note: This may interfere with multi-touch handling / support. */ public void enablePanningAndScalingGestures(boolean enable) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java index 7f3a3ac7a3..c0912ca4dc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -316,6 +316,15 @@ public abstract class GodotPlugin { } /** + * Returns whether the plugin supports the given feature tag. + * + * @see <a href="https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html">Feature tags</a> + */ + public boolean supportsFeature(String featureTag) { + return false; + } + + /** * Runs the specified action on the UI thread. If the current thread is the UI * thread, then the action is executed immediately. If the current thread is * not the UI thread, the action is posted to the event queue of the UI thread. diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java index d338b72441..711bca02e7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -42,8 +42,8 @@ import android.util.Log; import androidx.annotation.Nullable; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Collection; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -64,9 +64,8 @@ public final class GodotPluginRegistry { private static GodotPluginRegistry instance; private final ConcurrentHashMap<String, GodotPlugin> registry; - private GodotPluginRegistry(Godot godot) { + private GodotPluginRegistry() { registry = new ConcurrentHashMap<>(); - loadPlugins(godot); } /** @@ -93,12 +92,14 @@ public final class GodotPluginRegistry { * documentation. * * @param godot Godot instance + * @param runtimePlugins Set of plugins provided at runtime for registration * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance * of each Godot Android plugins is available at runtime. */ - public static GodotPluginRegistry initializePluginRegistry(Godot godot) { + public static GodotPluginRegistry initializePluginRegistry(Godot godot, Set<GodotPlugin> runtimePlugins) { if (instance == null) { - instance = new GodotPluginRegistry(godot); + instance = new GodotPluginRegistry(); + instance.loadPlugins(godot, runtimePlugins); } return instance; @@ -108,7 +109,7 @@ public final class GodotPluginRegistry { * Return the plugin registry if it's initialized. * Throws a {@link IllegalStateException} exception if not. * - * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method. + * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot, Set)} has not been called prior to calling this method. */ public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException { if (instance == null) { @@ -118,7 +119,16 @@ public final class GodotPluginRegistry { return instance; } - private void loadPlugins(Godot godot) { + private void loadPlugins(Godot godot, Set<GodotPlugin> runtimePlugins) { + // Register the runtime plugins + if (runtimePlugins != null && !runtimePlugins.isEmpty()) { + for (GodotPlugin plugin : runtimePlugins) { + Log.i(TAG, "Registering runtime plugin " + plugin.getPluginName()); + registry.put(plugin.getPluginName(), plugin); + } + } + + // Register the manifest plugins try { final Activity activity = godot.getActivity(); ApplicationInfo appInfo = activity diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 8353fc8dc6..9a82204467 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -160,6 +160,7 @@ public final class PermissionsUtil { try { if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { + Log.d(TAG, "Requesting permission " + manifestPermission); try { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName()))); @@ -173,6 +174,7 @@ public final class PermissionsUtil { PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Requesting permission " + manifestPermission); requestedPermissions.add(manifestPermission); } } diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index edc934e927..d6455cbf1c 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -50,14 +50,14 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } int pc = E.param_types.size(); - if (pc > p_argcount) { + if (p_argcount < pc) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = pc; + r_error.expected = pc; continue; } - if (pc < p_argcount) { + if (p_argcount > pc) { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = pc; + r_error.expected = pc; continue; } uint32_t *ptypes = E.param_types.ptrw(); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 1703179b8e..cb6ebf14a8 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -82,6 +82,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(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"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -373,3 +374,15 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file); } } + +bool GodotJavaWrapper::has_feature(const String &p_feature) const { + if (_has_feature) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, false); + + jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data()); + return env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + } else { + return false; + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index f427a2937c..7c6327c9e1 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -73,6 +73,7 @@ private: jmethodID _begin_benchmark_measure = nullptr; jmethodID _end_benchmark_measure = nullptr; jmethodID _dump_benchmark = nullptr; + jmethodID _has_feature = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -110,6 +111,9 @@ public: // Return the list of gdextensions config file. Vector<String> get_gdextension_list_config_file() const; + + // Return true if the given feature is supported. + bool has_feature(const String &p_feature) const; }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 8f80516a9f..92dc5f909f 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -60,10 +60,10 @@ String _remove_symlink(const String &dir) { // Change directory to the external data directory. chdir(dir.utf8().get_data()); // Get the actual directory without the potential symlink. - char dir_name_wihout_symlink[2048]; - getcwd(dir_name_wihout_symlink, 2048); + char dir_name_without_symlink[2048]; + getcwd(dir_name_without_symlink, 2048); // Convert back to a String. - String dir_without_symlink(dir_name_wihout_symlink); + String dir_without_symlink(dir_name_without_symlink); // Restore original current directory. chdir(current_dir_name); return dir_without_symlink; @@ -749,6 +749,11 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) { return true; } #endif + + if (godot_java->has_feature(p_feature)) { + return true; + } + return false; } |