From e7c4c8b993ce27a50b7a56f90247056048d20f7d Mon Sep 17 00:00:00 2001 From: t895 Date: Mon, 5 Feb 2024 06:07:29 -0500 Subject: android: Move JNI setup and helpers to common --- src/common/CMakeLists.txt | 8 +- src/common/android/android_common.cpp | 65 ++++ src/common/android/android_common.h | 26 ++ src/common/android/applets/software_keyboard.cpp | 277 +++++++++++++++ src/common/android/applets/software_keyboard.h | 78 +++++ src/common/android/id_cache.cpp | 428 +++++++++++++++++++++++ src/common/android/id_cache.h | 88 +++++ 7 files changed, 969 insertions(+), 1 deletion(-) create mode 100644 src/common/android/android_common.cpp create mode 100644 src/common/android/android_common.h create mode 100644 src/common/android/applets/software_keyboard.cpp create mode 100644 src/common/android/applets/software_keyboard.h create mode 100644 src/common/android/id_cache.cpp create mode 100644 src/common/android/id_cache.h (limited to 'src/common') diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 85926fc8f..8ff1326f2 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -179,9 +179,15 @@ endif() if(ANDROID) target_sources(common - PRIVATE + PUBLIC fs/fs_android.cpp fs/fs_android.h + android/android_common.cpp + android/android_common.h + android/id_cache.cpp + android/id_cache.h + android/applets/software_keyboard.cpp + android/applets/software_keyboard.h ) endif() diff --git a/src/common/android/android_common.cpp b/src/common/android/android_common.cpp new file mode 100644 index 000000000..e79005658 --- /dev/null +++ b/src/common/android/android_common.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "android_common.h" + +#include +#include + +#include + +#include "common/android/id_cache.h" +#include "common/string_util.h" + +namespace Common::Android { + +std::string GetJString(JNIEnv* env, jstring jstr) { + if (!jstr) { + return {}; + } + + const jchar* jchars = env->GetStringChars(jstr, nullptr); + const jsize length = env->GetStringLength(jstr); + const std::u16string_view string_view(reinterpret_cast(jchars), + static_cast(length)); + const std::string converted_string = Common::UTF16ToUTF8(string_view); + env->ReleaseStringChars(jstr, jchars); + + return converted_string; +} + +jstring ToJString(JNIEnv* env, std::string_view str) { + const std::u16string converted_string = Common::UTF8ToUTF16(str); + return env->NewString(reinterpret_cast(converted_string.data()), + static_cast(converted_string.size())); +} + +jstring ToJString(JNIEnv* env, std::u16string_view str) { + return ToJString(env, Common::UTF16ToUTF8(str)); +} + +double GetJDouble(JNIEnv* env, jobject jdouble) { + return env->GetDoubleField(jdouble, GetDoubleValueField()); +} + +jobject ToJDouble(JNIEnv* env, double value) { + return env->NewObject(GetDoubleClass(), GetDoubleConstructor(), value); +} + +s32 GetJInteger(JNIEnv* env, jobject jinteger) { + return env->GetIntField(jinteger, GetIntegerValueField()); +} + +jobject ToJInteger(JNIEnv* env, s32 value) { + return env->NewObject(GetIntegerClass(), GetIntegerConstructor(), value); +} + +bool GetJBoolean(JNIEnv* env, jobject jboolean) { + return env->GetBooleanField(jboolean, GetBooleanValueField()); +} + +jobject ToJBoolean(JNIEnv* env, bool value) { + return env->NewObject(GetBooleanClass(), GetBooleanConstructor(), value); +} + +} // namespace Common::Android diff --git a/src/common/android/android_common.h b/src/common/android/android_common.h new file mode 100644 index 000000000..d0ccb4ec2 --- /dev/null +++ b/src/common/android/android_common.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include +#include "common/common_types.h" + +namespace Common::Android { + +std::string GetJString(JNIEnv* env, jstring jstr); +jstring ToJString(JNIEnv* env, std::string_view str); +jstring ToJString(JNIEnv* env, std::u16string_view str); + +double GetJDouble(JNIEnv* env, jobject jdouble); +jobject ToJDouble(JNIEnv* env, double value); + +s32 GetJInteger(JNIEnv* env, jobject jinteger); +jobject ToJInteger(JNIEnv* env, s32 value); + +bool GetJBoolean(JNIEnv* env, jobject jboolean); +jobject ToJBoolean(JNIEnv* env, bool value); + +} // namespace Common::Android diff --git a/src/common/android/applets/software_keyboard.cpp b/src/common/android/applets/software_keyboard.cpp new file mode 100644 index 000000000..477e62b16 --- /dev/null +++ b/src/common/android/applets/software_keyboard.cpp @@ -0,0 +1,277 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "common/android/android_common.h" +#include "common/android/applets/software_keyboard.h" +#include "common/android/id_cache.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" + +static jclass s_software_keyboard_class; +static jclass s_keyboard_config_class; +static jclass s_keyboard_data_class; +static jmethodID s_swkbd_execute_normal; +static jmethodID s_swkbd_execute_inline; + +namespace Common::Android::SoftwareKeyboard { + +static jobject ToJKeyboardParams(const Core::Frontend::KeyboardInitializeParameters& config) { + JNIEnv* env = GetEnvForThread(); + jobject object = env->AllocObject(s_keyboard_config_class); + + env->SetObjectField(object, + env->GetFieldID(s_keyboard_config_class, "ok_text", "Ljava/lang/String;"), + ToJString(env, config.ok_text)); + env->SetObjectField( + object, env->GetFieldID(s_keyboard_config_class, "header_text", "Ljava/lang/String;"), + ToJString(env, config.header_text)); + env->SetObjectField(object, + env->GetFieldID(s_keyboard_config_class, "sub_text", "Ljava/lang/String;"), + ToJString(env, config.sub_text)); + env->SetObjectField( + object, env->GetFieldID(s_keyboard_config_class, "guide_text", "Ljava/lang/String;"), + ToJString(env, config.guide_text)); + env->SetObjectField( + object, env->GetFieldID(s_keyboard_config_class, "initial_text", "Ljava/lang/String;"), + ToJString(env, config.initial_text)); + env->SetShortField(object, + env->GetFieldID(s_keyboard_config_class, "left_optional_symbol_key", "S"), + static_cast(config.left_optional_symbol_key)); + env->SetShortField(object, + env->GetFieldID(s_keyboard_config_class, "right_optional_symbol_key", "S"), + static_cast(config.right_optional_symbol_key)); + env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "max_text_length", "I"), + static_cast(config.max_text_length)); + env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "min_text_length", "I"), + static_cast(config.min_text_length)); + env->SetIntField(object, + env->GetFieldID(s_keyboard_config_class, "initial_cursor_position", "I"), + static_cast(config.initial_cursor_position)); + env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "type", "I"), + static_cast(config.type)); + env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "password_mode", "I"), + static_cast(config.password_mode)); + env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "text_draw_type", "I"), + static_cast(config.text_draw_type)); + env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "key_disable_flags", "I"), + static_cast(config.key_disable_flags.raw)); + env->SetBooleanField(object, + env->GetFieldID(s_keyboard_config_class, "use_blur_background", "Z"), + static_cast(config.use_blur_background)); + env->SetBooleanField(object, + env->GetFieldID(s_keyboard_config_class, "enable_backspace_button", "Z"), + static_cast(config.enable_backspace_button)); + env->SetBooleanField(object, + env->GetFieldID(s_keyboard_config_class, "enable_return_button", "Z"), + static_cast(config.enable_return_button)); + env->SetBooleanField(object, + env->GetFieldID(s_keyboard_config_class, "disable_cancel_button", "Z"), + static_cast(config.disable_cancel_button)); + + return object; +} + +AndroidKeyboard::ResultData AndroidKeyboard::ResultData::CreateFromFrontend(jobject object) { + JNIEnv* env = GetEnvForThread(); + const jstring string = reinterpret_cast(env->GetObjectField( + object, env->GetFieldID(s_keyboard_data_class, "text", "Ljava/lang/String;"))); + return ResultData{GetJString(env, string), + static_cast(env->GetIntField( + object, env->GetFieldID(s_keyboard_data_class, "result", "I")))}; +} + +AndroidKeyboard::~AndroidKeyboard() = default; + +void AndroidKeyboard::InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) { + if (is_inline) { + LOG_WARNING( + Frontend, + "(STUBBED) called, backend requested to initialize the inline software keyboard."); + + submit_inline_callback = std::move(submit_inline_callback_); + } else { + LOG_WARNING( + Frontend, + "(STUBBED) called, backend requested to initialize the normal software keyboard."); + + submit_normal_callback = std::move(submit_normal_callback_); + } + + parameters = std::move(initialize_parameters); + + LOG_INFO(Frontend, + "\nKeyboardInitializeParameters:" + "\nok_text={}" + "\nheader_text={}" + "\nsub_text={}" + "\nguide_text={}" + "\ninitial_text={}" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\ninitial_cursor_position={}" + "\ntype={}" + "\npassword_mode={}" + "\ntext_draw_type={}" + "\nkey_disable_flags={}" + "\nuse_blur_background={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + Common::UTF16ToUTF8(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text), + Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text), + Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length, + parameters.min_text_length, parameters.initial_cursor_position, parameters.type, + parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw, + parameters.use_blur_background, parameters.enable_backspace_button, + parameters.enable_return_button, parameters.disable_cancel_button); +} + +void AndroidKeyboard::ShowNormalKeyboard() const { + LOG_DEBUG(Frontend, "called, backend requested to show the normal software keyboard."); + + ResultData data{}; + + // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber. + std::thread([&] { + data = ResultData::CreateFromFrontend(GetEnvForThread()->CallStaticObjectMethod( + s_software_keyboard_class, s_swkbd_execute_normal, ToJKeyboardParams(parameters))); + }).join(); + + SubmitNormalText(data); +} + +void AndroidKeyboard::ShowTextCheckDialog( + Service::AM::Frontend::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + LOG_WARNING(Frontend, "(STUBBED) called, backend requested to show the text check dialog."); +} + +void AndroidKeyboard::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const { + LOG_WARNING(Frontend, + "(STUBBED) called, backend requested to show the inline software keyboard."); + + LOG_INFO(Frontend, + "\nInlineAppearParameters:" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\nkey_top_scale_x={}" + "\nkey_top_scale_y={}" + "\nkey_top_translate_x={}" + "\nkey_top_translate_y={}" + "\ntype={}" + "\nkey_disable_flags={}" + "\nkey_top_as_floating={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + appear_parameters.max_text_length, appear_parameters.min_text_length, + appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, + appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, + appear_parameters.type, appear_parameters.key_disable_flags.raw, + appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, + appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); + + // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber. + m_is_inline_active = true; + std::thread([&] { + GetEnvForThread()->CallStaticVoidMethod(s_software_keyboard_class, s_swkbd_execute_inline, + ToJKeyboardParams(parameters)); + }).join(); +} + +void AndroidKeyboard::HideInlineKeyboard() const { + LOG_WARNING(Frontend, + "(STUBBED) called, backend requested to hide the inline software keyboard."); +} + +void AndroidKeyboard::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) const { + LOG_WARNING(Frontend, + "(STUBBED) called, backend requested to change the inline keyboard text."); + + LOG_INFO(Frontend, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, + text_parameters.input_text, text_parameters.cursor_position); +} + +void AndroidKeyboard::ExitKeyboard() const { + LOG_WARNING(Frontend, "(STUBBED) called, backend requested to exit the software keyboard."); +} + +void AndroidKeyboard::SubmitInlineKeyboardText(std::u16string submitted_text) { + if (!m_is_inline_active) { + return; + } + + m_current_text += submitted_text; + + submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, + static_cast(m_current_text.size())); +} + +void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) { + static constexpr int KEYCODE_BACK = 4; + static constexpr int KEYCODE_ENTER = 66; + static constexpr int KEYCODE_DEL = 67; + + if (!m_is_inline_active) { + return; + } + + switch (key_code) { + case KEYCODE_BACK: + case KEYCODE_ENTER: + m_is_inline_active = false; + submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::DecidedEnter, m_current_text, + static_cast(m_current_text.size())); + break; + case KEYCODE_DEL: + m_current_text.pop_back(); + submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, + static_cast(m_current_text.size())); + break; + } +} + +void AndroidKeyboard::SubmitNormalText(const ResultData& data) const { + submit_normal_callback(data.result, Common::UTF8ToUTF16(data.text), true); +} + +void InitJNI(JNIEnv* env) { + s_software_keyboard_class = reinterpret_cast( + env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard"))); + s_keyboard_config_class = reinterpret_cast(env->NewGlobalRef( + env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig"))); + s_keyboard_data_class = reinterpret_cast(env->NewGlobalRef( + env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData"))); + + s_swkbd_execute_normal = env->GetStaticMethodID( + s_software_keyboard_class, "executeNormal", + "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/" + "applets/keyboard/SoftwareKeyboard$KeyboardData;"); + s_swkbd_execute_inline = env->GetStaticMethodID( + s_software_keyboard_class, "executeInline", + "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V"); +} + +void CleanupJNI(JNIEnv* env) { + env->DeleteGlobalRef(s_software_keyboard_class); + env->DeleteGlobalRef(s_keyboard_config_class); + env->DeleteGlobalRef(s_keyboard_data_class); +} + +} // namespace Common::Android::SoftwareKeyboard diff --git a/src/common/android/applets/software_keyboard.h b/src/common/android/applets/software_keyboard.h new file mode 100644 index 000000000..9fd09d27c --- /dev/null +++ b/src/common/android/applets/software_keyboard.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "core/frontend/applets/software_keyboard.h" + +namespace Common::Android::SoftwareKeyboard { + +class AndroidKeyboard final : public Core::Frontend::SoftwareKeyboardApplet { +public: + ~AndroidKeyboard() override; + + void Close() const override { + ExitKeyboard(); + } + + void InitializeKeyboard(bool is_inline, + Core::Frontend::KeyboardInitializeParameters initialize_parameters, + SubmitNormalCallback submit_normal_callback_, + SubmitInlineCallback submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Frontend::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; + + void SubmitInlineKeyboardText(std::u16string submitted_text); + + void SubmitInlineKeyboardInput(int key_code); + +private: + struct ResultData { + static ResultData CreateFromFrontend(jobject object); + + std::string text; + Service::AM::Frontend::SwkbdResult result{}; + }; + + void SubmitNormalText(const ResultData& result) const; + + Core::Frontend::KeyboardInitializeParameters parameters{}; + + mutable SubmitNormalCallback submit_normal_callback; + mutable SubmitInlineCallback submit_inline_callback; + +private: + mutable bool m_is_inline_active{}; + std::u16string m_current_text; +}; + +// Should be called in JNI_Load +void InitJNI(JNIEnv* env); + +// Should be called in JNI_Unload +void CleanupJNI(JNIEnv* env); + +} // namespace Common::Android::SoftwareKeyboard + +// Native function calls +extern "C" { +JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateFilters( + JNIEnv* env, jclass clazz, jstring text); + +JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateInput( + JNIEnv* env, jclass clazz, jstring text); +} diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp new file mode 100644 index 000000000..f39262db9 --- /dev/null +++ b/src/common/android/id_cache.cpp @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "applets/software_keyboard.h" +#include "common/android/id_cache.h" +#include "common/assert.h" +#include "common/fs/fs_android.h" +#include "video_core/rasterizer_interface.h" + +static JavaVM* s_java_vm; +static jclass s_native_library_class; +static jclass s_disk_cache_progress_class; +static jclass s_load_callback_stage_class; +static jclass s_game_dir_class; +static jmethodID s_game_dir_constructor; +static jmethodID s_exit_emulation_activity; +static jmethodID s_disk_cache_load_progress; +static jmethodID s_on_emulation_started; +static jmethodID s_on_emulation_stopped; +static jmethodID s_on_program_changed; + +static jclass s_game_class; +static jmethodID s_game_constructor; +static jfieldID s_game_title_field; +static jfieldID s_game_path_field; +static jfieldID s_game_program_id_field; +static jfieldID s_game_developer_field; +static jfieldID s_game_version_field; +static jfieldID s_game_is_homebrew_field; + +static jclass s_string_class; +static jclass s_pair_class; +static jmethodID s_pair_constructor; +static jfieldID s_pair_first_field; +static jfieldID s_pair_second_field; + +static jclass s_overlay_control_data_class; +static jmethodID s_overlay_control_data_constructor; +static jfieldID s_overlay_control_data_id_field; +static jfieldID s_overlay_control_data_enabled_field; +static jfieldID s_overlay_control_data_landscape_position_field; +static jfieldID s_overlay_control_data_portrait_position_field; +static jfieldID s_overlay_control_data_foldable_position_field; + +static jclass s_patch_class; +static jmethodID s_patch_constructor; +static jfieldID s_patch_enabled_field; +static jfieldID s_patch_name_field; +static jfieldID s_patch_version_field; +static jfieldID s_patch_type_field; +static jfieldID s_patch_program_id_field; +static jfieldID s_patch_title_id_field; + +static jclass s_double_class; +static jmethodID s_double_constructor; +static jfieldID s_double_value_field; + +static jclass s_integer_class; +static jmethodID s_integer_constructor; +static jfieldID s_integer_value_field; + +static jclass s_boolean_class; +static jmethodID s_boolean_constructor; +static jfieldID s_boolean_value_field; + +static constexpr jint JNI_VERSION = JNI_VERSION_1_6; + +namespace Common::Android { + +JNIEnv* GetEnvForThread() { + thread_local static struct OwnedEnv { + OwnedEnv() { + status = s_java_vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (status == JNI_EDETACHED) + s_java_vm->AttachCurrentThread(&env, nullptr); + } + + ~OwnedEnv() { + if (status == JNI_EDETACHED) + s_java_vm->DetachCurrentThread(); + } + + int status; + JNIEnv* env = nullptr; + } owned; + return owned.env; +} + +jclass GetNativeLibraryClass() { + return s_native_library_class; +} + +jclass GetDiskCacheProgressClass() { + return s_disk_cache_progress_class; +} + +jclass GetDiskCacheLoadCallbackStageClass() { + return s_load_callback_stage_class; +} + +jclass GetGameDirClass() { + return s_game_dir_class; +} + +jmethodID GetGameDirConstructor() { + return s_game_dir_constructor; +} + +jmethodID GetExitEmulationActivity() { + return s_exit_emulation_activity; +} + +jmethodID GetDiskCacheLoadProgress() { + return s_disk_cache_load_progress; +} + +jmethodID GetOnEmulationStarted() { + return s_on_emulation_started; +} + +jmethodID GetOnEmulationStopped() { + return s_on_emulation_stopped; +} + +jmethodID GetOnProgramChanged() { + return s_on_program_changed; +} + +jclass GetGameClass() { + return s_game_class; +} + +jmethodID GetGameConstructor() { + return s_game_constructor; +} + +jfieldID GetGameTitleField() { + return s_game_title_field; +} + +jfieldID GetGamePathField() { + return s_game_path_field; +} + +jfieldID GetGameProgramIdField() { + return s_game_program_id_field; +} + +jfieldID GetGameDeveloperField() { + return s_game_developer_field; +} + +jfieldID GetGameVersionField() { + return s_game_version_field; +} + +jfieldID GetGameIsHomebrewField() { + return s_game_is_homebrew_field; +} + +jclass GetStringClass() { + return s_string_class; +} + +jclass GetPairClass() { + return s_pair_class; +} + +jmethodID GetPairConstructor() { + return s_pair_constructor; +} + +jfieldID GetPairFirstField() { + return s_pair_first_field; +} + +jfieldID GetPairSecondField() { + return s_pair_second_field; +} + +jclass GetOverlayControlDataClass() { + return s_overlay_control_data_class; +} + +jmethodID GetOverlayControlDataConstructor() { + return s_overlay_control_data_constructor; +} + +jfieldID GetOverlayControlDataIdField() { + return s_overlay_control_data_id_field; +} + +jfieldID GetOverlayControlDataEnabledField() { + return s_overlay_control_data_enabled_field; +} + +jfieldID GetOverlayControlDataLandscapePositionField() { + return s_overlay_control_data_landscape_position_field; +} + +jfieldID GetOverlayControlDataPortraitPositionField() { + return s_overlay_control_data_portrait_position_field; +} + +jfieldID GetOverlayControlDataFoldablePositionField() { + return s_overlay_control_data_foldable_position_field; +} + +jclass GetPatchClass() { + return s_patch_class; +} + +jmethodID GetPatchConstructor() { + return s_patch_constructor; +} + +jfieldID GetPatchEnabledField() { + return s_patch_enabled_field; +} + +jfieldID GetPatchNameField() { + return s_patch_name_field; +} + +jfieldID GetPatchVersionField() { + return s_patch_version_field; +} + +jfieldID GetPatchTypeField() { + return s_patch_type_field; +} + +jfieldID GetPatchProgramIdField() { + return s_patch_program_id_field; +} + +jfieldID GetPatchTitleIdField() { + return s_patch_title_id_field; +} + +jclass GetDoubleClass() { + return s_double_class; +} + +jmethodID GetDoubleConstructor() { + return s_double_constructor; +} + +jfieldID GetDoubleValueField() { + return s_double_value_field; +} + +jclass GetIntegerClass() { + return s_integer_class; +} + +jmethodID GetIntegerConstructor() { + return s_integer_constructor; +} + +jfieldID GetIntegerValueField() { + return s_integer_value_field; +} + +jclass GetBooleanClass() { + return s_boolean_class; +} + +jmethodID GetBooleanConstructor() { + return s_boolean_constructor; +} + +jfieldID GetBooleanValueField() { + return s_boolean_value_field; +} + +#ifdef __cplusplus +extern "C" { +#endif + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + s_java_vm = vm; + + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) + return JNI_ERR; + + // Initialize Java classes + const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); + s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); + s_disk_cache_progress_class = reinterpret_cast(env->NewGlobalRef( + env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); + s_load_callback_stage_class = reinterpret_cast(env->NewGlobalRef(env->FindClass( + "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); + + const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir"); + s_game_dir_class = reinterpret_cast(env->NewGlobalRef(game_dir_class)); + s_game_dir_constructor = env->GetMethodID(game_dir_class, "", "(Ljava/lang/String;Z)V"); + env->DeleteLocalRef(game_dir_class); + + // Initialize methods + s_exit_emulation_activity = + env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); + s_disk_cache_load_progress = + env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); + s_on_emulation_started = + env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); + s_on_emulation_stopped = + env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); + s_on_program_changed = + env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); + + const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); + s_game_class = reinterpret_cast(env->NewGlobalRef(game_class)); + s_game_constructor = env->GetMethodID(game_class, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;Ljava/lang/String;Z)V"); + s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); + s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); + s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); + s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); + s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); + s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); + env->DeleteLocalRef(game_class); + + const jclass string_class = env->FindClass("java/lang/String"); + s_string_class = reinterpret_cast(env->NewGlobalRef(string_class)); + env->DeleteLocalRef(string_class); + + const jclass pair_class = env->FindClass("kotlin/Pair"); + s_pair_class = reinterpret_cast(env->NewGlobalRef(pair_class)); + s_pair_constructor = + env->GetMethodID(pair_class, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); + s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); + env->DeleteLocalRef(pair_class); + + const jclass overlay_control_data_class = + env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData"); + s_overlay_control_data_class = + reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); + s_overlay_control_data_constructor = + env->GetMethodID(overlay_control_data_class, "", + "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V"); + s_overlay_control_data_id_field = + env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); + s_overlay_control_data_enabled_field = + env->GetFieldID(overlay_control_data_class, "enabled", "Z"); + s_overlay_control_data_landscape_position_field = + env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); + s_overlay_control_data_portrait_position_field = + env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); + s_overlay_control_data_foldable_position_field = + env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); + env->DeleteLocalRef(overlay_control_data_class); + + const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); + s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); + s_patch_constructor = env->GetMethodID( + patch_class, "", + "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); + s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); + s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); + s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); + s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); + s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); + s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); + env->DeleteLocalRef(patch_class); + + const jclass double_class = env->FindClass("java/lang/Double"); + s_double_class = reinterpret_cast(env->NewGlobalRef(double_class)); + s_double_constructor = env->GetMethodID(double_class, "", "(D)V"); + s_double_value_field = env->GetFieldID(double_class, "value", "D"); + env->DeleteLocalRef(double_class); + + const jclass int_class = env->FindClass("java/lang/Integer"); + s_integer_class = reinterpret_cast(env->NewGlobalRef(int_class)); + s_integer_constructor = env->GetMethodID(int_class, "", "(I)V"); + s_integer_value_field = env->GetFieldID(int_class, "value", "I"); + env->DeleteLocalRef(int_class); + + const jclass boolean_class = env->FindClass("java/lang/Boolean"); + s_boolean_class = reinterpret_cast(env->NewGlobalRef(boolean_class)); + s_boolean_constructor = env->GetMethodID(boolean_class, "", "(Z)V"); + s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); + env->DeleteLocalRef(boolean_class); + + // Initialize Android Storage + Common::FS::Android::RegisterCallbacks(env, s_native_library_class); + + // Initialize applets + Common::Android::SoftwareKeyboard::InitJNI(env); + + return JNI_VERSION; +} + +void JNI_OnUnload(JavaVM* vm, void* reserved) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { + return; + } + + // UnInitialize Android Storage + Common::FS::Android::UnRegisterCallbacks(); + env->DeleteGlobalRef(s_native_library_class); + env->DeleteGlobalRef(s_disk_cache_progress_class); + env->DeleteGlobalRef(s_load_callback_stage_class); + env->DeleteGlobalRef(s_game_dir_class); + env->DeleteGlobalRef(s_game_class); + env->DeleteGlobalRef(s_string_class); + env->DeleteGlobalRef(s_pair_class); + env->DeleteGlobalRef(s_overlay_control_data_class); + env->DeleteGlobalRef(s_patch_class); + env->DeleteGlobalRef(s_double_class); + env->DeleteGlobalRef(s_integer_class); + env->DeleteGlobalRef(s_boolean_class); + + // UnInitialize applets + SoftwareKeyboard::CleanupJNI(env); +} + +#ifdef __cplusplus +} +#endif + +} // namespace Common::Android diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h new file mode 100644 index 000000000..47802f96c --- /dev/null +++ b/src/common/android/id_cache.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "video_core/rasterizer_interface.h" + +namespace Common::Android { + +JNIEnv* GetEnvForThread(); + +/** + * Starts a new thread to run JNI. Intended to be used when you must run JNI from a fiber. + * @tparam T Typename of the return value for the work param + * @param work Lambda that runs JNI code. This function will take care of attaching this thread to + * the JVM + * @return The result from the work lambda param + */ +template +T RunJNIOnFiber(const std::function& work) { + std::future j_result = std::async(std::launch::async, [&] { + auto env = GetEnvForThread(); + return work(env); + }); + return j_result.get(); +} + +jclass GetNativeLibraryClass(); + +jclass GetDiskCacheProgressClass(); +jclass GetDiskCacheLoadCallbackStageClass(); +jclass GetGameDirClass(); +jmethodID GetGameDirConstructor(); +jmethodID GetDiskCacheLoadProgress(); + +jmethodID GetExitEmulationActivity(); +jmethodID GetOnEmulationStarted(); +jmethodID GetOnEmulationStopped(); +jmethodID GetOnProgramChanged(); + +jclass GetGameClass(); +jmethodID GetGameConstructor(); +jfieldID GetGameTitleField(); +jfieldID GetGamePathField(); +jfieldID GetGameProgramIdField(); +jfieldID GetGameDeveloperField(); +jfieldID GetGameVersionField(); +jfieldID GetGameIsHomebrewField(); + +jclass GetStringClass(); +jclass GetPairClass(); +jmethodID GetPairConstructor(); +jfieldID GetPairFirstField(); +jfieldID GetPairSecondField(); + +jclass GetOverlayControlDataClass(); +jmethodID GetOverlayControlDataConstructor(); +jfieldID GetOverlayControlDataIdField(); +jfieldID GetOverlayControlDataEnabledField(); +jfieldID GetOverlayControlDataLandscapePositionField(); +jfieldID GetOverlayControlDataPortraitPositionField(); +jfieldID GetOverlayControlDataFoldablePositionField(); + +jclass GetPatchClass(); +jmethodID GetPatchConstructor(); +jfieldID GetPatchEnabledField(); +jfieldID GetPatchNameField(); +jfieldID GetPatchVersionField(); +jfieldID GetPatchTypeField(); +jfieldID GetPatchProgramIdField(); +jfieldID GetPatchTitleIdField(); + +jclass GetDoubleClass(); +jmethodID GetDoubleConstructor(); +jfieldID GetDoubleValueField(); + +jclass GetIntegerClass(); +jmethodID GetIntegerConstructor(); +jfieldID GetIntegerValueField(); + +jclass GetBooleanClass(); +jmethodID GetBooleanConstructor(); +jfieldID GetBooleanValueField(); + +} // namespace Common::Android -- cgit v1.2.3