Path: blob/master/platform/android/java_godot_lib_jni.cpp
11351 views
/**************************************************************************/1/* java_godot_lib_jni.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "java_godot_lib_jni.h"3132#include "android_input_handler.h"33#include "api/java_class_wrapper.h"34#include "dir_access_jandroid.h"35#include "display_server_android.h"36#include "file_access_android.h"37#include "file_access_filesystem_jandroid.h"38#include "java_godot_io_wrapper.h"39#include "java_godot_wrapper.h"40#include "jni_utils.h"41#include "net_socket_android.h"42#include "os_android.h"43#include "plugin/godot_plugin_jni.h"44#include "thread_jandroid.h"45#include "tts_android.h"4647#include "core/config/engine.h"48#include "core/config/project_settings.h"49#include "core/input/input.h"50#include "core/os/main_loop.h"51#include "main/main.h"52#include "servers/rendering/rendering_server.h"5354#ifndef XR_DISABLED55#include "servers/xr/xr_server.h"56#endif // XR_DISABLED5758#ifdef TOOLS_ENABLED59#include "editor/settings/editor_settings.h"60#endif6162#include <android/asset_manager_jni.h>63#include <android/input.h>64#include <android/native_window_jni.h>65#include <unistd.h>6667static JavaClassWrapper *java_class_wrapper = nullptr;68static OS_Android *os_android = nullptr;69static AndroidInputHandler *input_handler = nullptr;70static GodotJavaWrapper *godot_java = nullptr;71static GodotIOJavaWrapper *godot_io_java = nullptr;7273enum StartupStep {74STEP_TERMINATED = -1,75STEP_SETUP,76STEP_SHOW_LOGO,77STEP_STARTED78};7980static SafeNumeric<int> step; // Shared between UI and render threads8182static Vector3 accelerometer;83static Vector3 gravity;84static Vector3 magnetometer;85static Vector3 gyroscope;8687static void _terminate(JNIEnv *env, bool p_restart = false) {88if (step.get() == STEP_TERMINATED) {89return;90}9192step.set(STEP_TERMINATED); // Ensure no further steps are attempted and no further events are sent9394// lets cleanup95// Unregister android plugins96unregister_plugins_singletons();9798if (java_class_wrapper) {99memdelete(java_class_wrapper);100}101if (input_handler) {102delete input_handler;103}104// Whether restarting is handled by 'Main::cleanup()'105bool restart_on_cleanup = false;106if (os_android) {107restart_on_cleanup = os_android->is_restart_on_exit_set();108os_android->main_loop_end();109Main::cleanup();110delete os_android;111}112if (godot_io_java) {113delete godot_io_java;114}115116TTS_Android::terminate();117FileAccessAndroid::terminate();118DirAccessJAndroid::terminate();119FileAccessFilesystemJAndroid::terminate();120NetSocketAndroid::terminate();121122cleanup_android_class_loader();123124if (godot_java) {125godot_java->on_godot_terminating(env);126if (!restart_on_cleanup) {127if (p_restart) {128godot_java->restart(env);129} else {130godot_java->force_quit(env);131}132}133delete godot_java;134}135}136137extern "C" {138139JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height) {140if (godot_io_java) {141godot_io_java->set_vk_height(p_height);142}143}144145JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion) {146JavaVM *jvm;147env->GetJavaVM(&jvm);148149init_thread_jandroid(jvm, env);150setup_android_class_loader();151152// create our wrapper classes153godot_java = new GodotJavaWrapper(env, p_godot_instance);154godot_io_java = new GodotIOJavaWrapper(env, p_godot_io);155156FileAccessAndroid::setup(p_asset_manager);157DirAccessJAndroid::setup(p_directory_access_handler);158FileAccessFilesystemJAndroid::setup(p_file_access_handler);159NetSocketAndroid::setup(p_net_utils);160161os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion);162163return true;164}165166JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) {167_terminate(env, false);168}169170JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts) {171setup_android_thread();172173const char **cmdline = nullptr;174jstring *j_cmdline = nullptr;175int cmdlen = 0;176if (p_cmdline) {177cmdlen = env->GetArrayLength(p_cmdline);178if (cmdlen) {179cmdline = (const char **)memalloc((cmdlen + 1) * sizeof(const char *));180ERR_FAIL_NULL_V_MSG(cmdline, false, "Out of memory.");181cmdline[cmdlen] = nullptr;182j_cmdline = (jstring *)memalloc(cmdlen * sizeof(jstring));183ERR_FAIL_NULL_V_MSG(j_cmdline, false, "Out of memory.");184185for (int i = 0; i < cmdlen; i++) {186jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i);187const char *rawString = env->GetStringUTFChars(string, nullptr);188189cmdline[i] = rawString;190j_cmdline[i] = string;191}192}193}194195Error err = Main::setup(OS_Android::ANDROID_EXEC_PATH, cmdlen, (char **)cmdline, false);196if (cmdline) {197if (j_cmdline) {198for (int i = 0; i < cmdlen; ++i) {199env->ReleaseStringUTFChars(j_cmdline[i], cmdline[i]);200}201memfree(j_cmdline);202}203memfree(cmdline);204}205206// Note: --help and --version return ERR_HELP, but this should be translated to 0 if exit codes are propagated.207if (err != OK) {208return false;209}210211TTS_Android::setup(p_godot_tts);212213java_class_wrapper = memnew(JavaClassWrapper);214return true;215}216217JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height) {218if (os_android) {219os_android->set_display_size(Size2i(p_width, p_height));220221// No need to reset the surface during startup222if (step.get() > STEP_SETUP) {223if (p_surface) {224ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);225os_android->set_native_window(native_window);226}227DisplayServerAndroid::get_singleton()->reset_window();228DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height);229}230}231}232233JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface) {234if (os_android) {235if (step.get() == STEP_SETUP) {236// During startup237if (p_surface) {238ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);239os_android->set_native_window(native_window);240}241} else {242// Rendering context recreated because it was lost; restart app to let it reload everything243_terminate(env, true);244}245}246}247248JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) {249if (step.get() <= STEP_SETUP) {250return;251}252253if (DisplayServerAndroid *dsa = Object::cast_to<DisplayServerAndroid>(DisplayServer::get_singleton())) {254dsa->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST);255}256}257258JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *env, jclass clazz, jint event, jint id, jint pos) {259TTS_Android::_java_utterance_callback(event, id, pos);260}261262JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) {263if (step.get() == STEP_TERMINATED) {264return true;265}266267if (step.get() == STEP_SETUP) {268// Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id,269// but for Godot purposes, the main thread is the one running the game loop270Main::setup2(false); // The logo is shown in the next frame otherwise we run into rendering issues271input_handler = new AndroidInputHandler();272step.increment();273return true;274}275276if (step.get() == STEP_SHOW_LOGO) {277bool xr_enabled = false;278#ifndef XR_DISABLED279// Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,280// so we skip this step if xr is enabled.281if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {282xr_enabled = GLOBAL_GET_CACHED(bool, "xr/shaders/enabled");283} else {284xr_enabled = XRServer::get_xr_mode() == XRServer::XRMODE_ON;285}286#endif // XR_DISABLED287if (!xr_enabled) {288Main::setup_boot_logo();289}290291step.increment();292return true;293}294295if (step.get() == STEP_STARTED) {296if (Main::start() != EXIT_SUCCESS) {297return true; // should exit instead and print the error298}299300godot_java->on_godot_setup_completed(env);301os_android->main_loop_begin();302godot_java->on_godot_main_loop_started(env);303step.increment();304}305306DisplayServerAndroid::get_singleton()->process_accelerometer(accelerometer);307DisplayServerAndroid::get_singleton()->process_gravity(gravity);308DisplayServerAndroid::get_singleton()->process_magnetometer(magnetometer);309DisplayServerAndroid::get_singleton()->process_gyroscope(gyroscope);310311bool should_swap_buffers = false;312if (os_android->main_loop_iterate(&should_swap_buffers)) {313_terminate(env, false);314}315316return should_swap_buffers;317}318319// Called on the UI thread320JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative, jfloat p_pressure, jfloat p_tilt_x, jfloat p_tilt_y) {321if (step.get() <= STEP_SETUP) {322return;323}324325input_handler->process_mouse_event(p_event_type, p_button_mask, Point2(p_x, p_y), Vector2(p_delta_x, p_delta_y), p_double_click, p_source_mouse_relative, p_pressure, Vector2(p_tilt_x, p_tilt_y));326}327328// Called on the UI thread329JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint pointer_count, jfloatArray position, jboolean p_double_tap) {330if (step.get() <= STEP_SETUP) {331return;332}333334Vector<AndroidInputHandler::TouchPos> points;335for (int i = 0; i < pointer_count; i++) {336jfloat p[6];337env->GetFloatArrayRegion(position, i * 6, 6, p);338AndroidInputHandler::TouchPos tp;339tp.id = (int)p[0];340tp.pos = Point2(p[1], p[2]);341tp.pressure = p[3];342tp.tilt = Vector2(p[4], p[5]);343points.push_back(tp);344}345346input_handler->process_touch_event(ev, pointer, points, p_double_tap);347}348349// Called on the UI thread350JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnify(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_factor) {351if (step.get() <= STEP_SETUP) {352return;353}354input_handler->process_magnify(Point2(p_x, p_y), p_factor);355}356357// Called on the UI thread358JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_pan(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y) {359if (step.get() <= STEP_SETUP) {360return;361}362input_handler->process_pan(Point2(p_x, p_y), Vector2(p_delta_x, p_delta_y));363}364365// Called on the UI thread366JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) {367if (step.get() <= STEP_SETUP) {368return;369}370371AndroidInputHandler::JoypadEvent jevent;372jevent.device = p_device;373jevent.type = AndroidInputHandler::JOY_EVENT_BUTTON;374jevent.index = p_button;375jevent.pressed = p_pressed;376377input_handler->process_joy_event(jevent);378}379380// Called on the UI thread381JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value) {382if (step.get() <= STEP_SETUP) {383return;384}385386AndroidInputHandler::JoypadEvent jevent;387jevent.device = p_device;388jevent.type = AndroidInputHandler::JOY_EVENT_AXIS;389jevent.index = p_axis;390jevent.value = p_value;391392input_handler->process_joy_event(jevent);393}394395// Called on the UI thread396JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y) {397if (step.get() <= STEP_SETUP) {398return;399}400401AndroidInputHandler::JoypadEvent jevent;402jevent.device = p_device;403jevent.type = AndroidInputHandler::JOY_EVENT_HAT;404BitField<HatMask> hat = HatMask::CENTER;405if (p_hat_x != 0) {406if (p_hat_x < 0) {407hat.set_flag(HatMask::LEFT);408} else {409hat.set_flag(HatMask::RIGHT);410}411}412if (p_hat_y != 0) {413if (p_hat_y < 0) {414hat.set_flag(HatMask::UP);415} else {416hat.set_flag(HatMask::DOWN);417}418}419jevent.hat = hat;420421input_handler->process_joy_event(jevent);422}423424// Called on the UI thread425JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name) {426if (os_android) {427String name = jstring_to_string(p_name, env);428Input::get_singleton()->joy_connection_changed(p_device, p_connected, name);429}430}431432// Called on the UI thread433JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_physical_keycode, jint p_unicode, jint p_key_label, jboolean p_pressed, jboolean p_echo) {434if (step.get() <= STEP_SETUP) {435return;436}437input_handler->process_key_event(p_physical_keycode, p_unicode, p_key_label, p_pressed, p_echo);438}439440JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {441accelerometer = Vector3(x, y, z);442}443444JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {445gravity = Vector3(x, y, z);446}447448JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {449magnetometer = Vector3(x, y, z);450}451452JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {453gyroscope = Vector3(x, y, z);454}455456JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) {457if (step.get() <= STEP_SETUP) {458return;459}460461os_android->main_loop_focusin();462}463464JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) {465if (step.get() <= STEP_SETUP) {466return;467}468469os_android->main_loop_focusout();470}471472JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) {473String js = jstring_to_string(path, env);474475Variant setting_with_override = GLOBAL_GET(js);476String setting_value = (setting_with_override.get_type() == Variant::NIL) ? "" : setting_with_override;477return env->NewStringUTF(setting_value.utf8().get_data());478}479480JNIEXPORT jobjectArray JNICALL Java_org_godotengine_godot_GodotLib_getRendererInfo(JNIEnv *env, jclass clazz) {481String rendering_driver = RenderingServer::get_singleton()->get_current_rendering_driver_name();482String rendering_method = RenderingServer::get_singleton()->get_current_rendering_method();483484jobjectArray result = env->NewObjectArray(2, jni_find_class(env, "java/lang/String"), nullptr);485env->SetObjectArrayElement(result, 0, env->NewStringUTF(rendering_driver.utf8().get_data()));486env->SetObjectArrayElement(result, 1, env->NewStringUTF(rendering_method.utf8().get_data()));487488return result;489}490491JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key) {492String editor_setting_value = "";493#ifdef TOOLS_ENABLED494String godot_setting_key = jstring_to_string(p_setting_key, env);495Variant editor_setting = EDITOR_GET(godot_setting_key);496editor_setting_value = (editor_setting.get_type() == Variant::NIL) ? "" : editor_setting;497#else498WARN_PRINT("Access to the Editor Settings in only available on Editor builds");499#endif500501return env->NewStringUTF(editor_setting_value.utf8().get_data());502}503504JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setEditorSetting(JNIEnv *env, jclass clazz, jstring p_key, jobject p_data) {505#ifdef TOOLS_ENABLED506if (EditorSettings::get_singleton() != nullptr) {507String key = jstring_to_string(p_key, env);508Variant data = _jobject_to_variant(env, p_data);509EditorSettings::get_singleton()->set(key, data);510}511#else512WARN_PRINT("Access to the Editor Settings in only available on Editor builds");513#endif514}515516JNIEXPORT jobject JNICALL Java_org_godotengine_godot_GodotLib_getEditorProjectMetadata(JNIEnv *env, jclass clazz, jstring p_section, jstring p_key, jobject p_default_value) {517jvalue result;518519#ifdef TOOLS_ENABLED520if (EditorSettings::get_singleton() != nullptr) {521String section = jstring_to_string(p_section, env);522String key = jstring_to_string(p_key, env);523Variant default_value = _jobject_to_variant(env, p_default_value);524Variant data = EditorSettings::get_singleton()->get_project_metadata(section, key, default_value);525result = _variant_to_jvalue(env, data.get_type(), &data, true);526}527#else528WARN_PRINT("Access to the Editor Settings Project Metadata is only available on Editor builds");529#endif530531return result.l;532}533534JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setEditorProjectMetadata(JNIEnv *env, jclass clazz, jstring p_section, jstring p_key, jobject p_data) {535#ifdef TOOLS_ENABLED536if (EditorSettings::get_singleton() != nullptr) {537String section = jstring_to_string(p_section, env);538String key = jstring_to_string(p_key, env);539Variant data = _jobject_to_variant(env, p_data);540EditorSettings::get_singleton()->set_project_metadata(section, key, data);541}542#else543WARN_PRINT("Access to the Editor Settings Project Metadata is only available on Editor builds");544#endif545}546547JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz) {548DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();549if (ds) {550ds->emit_system_theme_changed();551}552}553554JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hardwareKeyboardConnected(JNIEnv *env, jclass clazz, jboolean p_connected) {555DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();556if (ds) {557ds->emit_hardware_keyboard_connection_changed(p_connected);558}559}560561JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths) {562DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();563if (ds) {564Vector<String> selected_paths;565566jint length = env->GetArrayLength(p_selected_paths);567for (jint i = 0; i < length; ++i) {568jstring java_string = (jstring)env->GetObjectArrayElement(p_selected_paths, i);569String path = jstring_to_string(java_string, env);570selected_paths.push_back(path);571env->DeleteLocalRef(java_string);572}573ds->emit_file_picker_callback(p_ok, selected_paths);574}575}576577JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {578String permission = jstring_to_string(p_permission, env);579if (permission == "android.permission.RECORD_AUDIO" && p_result) {580AudioDriver::get_singleton()->input_start();581}582583if (os_android->get_main_loop()) {584os_android->get_main_loop()->emit_signal(SNAME("on_request_permissions_result"), permission, p_result == JNI_TRUE);585}586}587588JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) {589if (step.get() <= STEP_SETUP) {590return;591}592593// We force redraw to ensure we render at least once when resuming the app.594Main::force_redraw();595if (os_android->get_main_loop()) {596os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);597}598}599600JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz) {601if (step.get() <= STEP_SETUP) {602return;603}604605if (os_android->get_main_loop()) {606os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);607}608}609610JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz) {611Input *input = Input::get_singleton();612if (input) {613return !input->is_agile_input_event_flushing();614}615return false;616}617618JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getProjectResourceDir(JNIEnv *env, jclass clazz) {619const String resource_dir = OS::get_singleton()->get_resource_dir();620return env->NewStringUTF(resource_dir.utf8().get_data());621}622JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_isEditorHint(JNIEnv *env, jclass clazz) {623Engine *engine = Engine::get_singleton();624if (engine) {625return engine->is_editor_hint();626}627return false;628}629630JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_isProjectManagerHint(JNIEnv *env, jclass clazz) {631Engine *engine = Engine::get_singleton();632if (engine) {633return engine->is_project_manager_hint();634}635return false;636}637}638639640