diff --git a/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java b/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java index f2d05a80a..1aeb727df 100644 --- a/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java +++ b/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java @@ -10,6 +10,9 @@ import android.app.AlertDialog; import android.content.res.Configuration; import android.preference.PreferenceManager; import android.view.Surface; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; import org.citra.citra_android.activities.EmulationActivity; import org.citra.citra_android.utils.Log; @@ -26,7 +29,15 @@ public final class NativeLibrary { */ public static final String TouchScreenDevice = "Touchscreen"; public static WeakReference sEmulationActivity = new WeakReference<>(null); + private static boolean alertResult = false; + private static String alertPromptResult = ""; + private static int alertPromptButton = 0; + private static final Object alertPromptLock = new Object(); + private static boolean alertPromptInProgress = false; + private static String alertPromptCaption = ""; + private static int alertPromptButtonConfig = 0; + private static EditText alertPromptEditText = null; static { try { @@ -353,6 +364,86 @@ public final class NativeLibrary { return result; } + public static void retryDisplayAlertPrompt() { + if (!alertPromptInProgress) { + return; + } + displayAlertPromptImpl(alertPromptCaption, alertPromptEditText.getText().toString(), alertPromptButtonConfig).show(); + } + + public static String displayAlertPrompt(String caption, String text, int buttonConfig) { + alertPromptCaption = caption; + alertPromptButtonConfig = buttonConfig; + alertPromptInProgress = true; + + // Show the AlertDialog on the main thread + sEmulationActivity.get().runOnUiThread(() -> displayAlertPromptImpl(alertPromptCaption, text, alertPromptButtonConfig).show()); + + // Wait for the lock to notify that it is complete + synchronized (alertPromptLock) { + try { + alertPromptLock.wait(); + } catch (Exception e) { + } + } + alertPromptInProgress = false; + + return alertPromptResult; + } + + public static AlertDialog.Builder displayAlertPromptImpl(String caption, String text, int buttonConfig) { + final EmulationActivity emulationActivity = sEmulationActivity.get(); + alertPromptResult = ""; + alertPromptButton = 0; + + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.leftMargin = params.rightMargin = CitraApplication.getAppContext().getResources().getDimensionPixelSize(R.dimen.dialog_margin); + + // Set up the input + alertPromptEditText = new EditText(DolphinApplication.getAppContext()); + alertPromptEditText.setText(text); + alertPromptEditText.setSingleLine(); + alertPromptEditText.setLayoutParams(params); + + FrameLayout container = new FrameLayout(emulationActivity); + container.addView(alertPromptEditText); + + AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity) + .setTitle(caption) + .setView(container) + .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> + { + alertPromptButton = buttonConfig; + alertPromptResult = alertPromptEditText.getText().toString(); + synchronized (alertPromptLock) { + alertPromptLock.notifyAll(); + } + }) + .setOnDismissListener(dialogInterface -> + { + alertPromptResult = ""; + synchronized (alertPromptLock) { + alertPromptLock.notifyAll(); + } + }); + + if (buttonConfig > 0) { + builder.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> + { + alertPromptResult = ""; + synchronized (alertPromptLock) { + alertPromptLock.notifyAll(); + } + }); + } + + return builder; + } + + public static int alertPromptButton() { + return alertPromptButton; + } + public static void setEmulationActivity(EmulationActivity emulationActivity) { Log.verbose("[NativeLibrary] Registering EmulationActivity."); sEmulationActivity = new WeakReference<>(emulationActivity); diff --git a/src/android/app/src/main/java/org/citra/citra_android/activities/EmulationActivity.java b/src/android/app/src/main/java/org/citra/citra_android/activities/EmulationActivity.java index 581f8583e..db20ec676 100644 --- a/src/android/app/src/main/java/org/citra/citra_android/activities/EmulationActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_android/activities/EmulationActivity.java @@ -237,6 +237,9 @@ public final class EmulationActivity extends AppCompatActivity { mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE); mScreenPath = savedInstanceState.getString(EXTRA_SCREEN_PATH); mPosition = savedInstanceState.getInt(EXTRA_GRID_POSITION); + + // If an alert prompt was in progress when state was restored, retry displaying it + NativeLibrary.retryDisplayAlertPrompt(); } @Override diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 9f67c5603..78d04d339 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -17,6 +17,8 @@ static JavaVM* s_java_vm; static jclass s_native_library_class; static jmethodID s_display_alert_msg; +static jmethodID s_display_alert_prompt; +static jmethodID s_alert_prompt_button; static jmethodID s_is_portrait_mode; static jmethodID s_landscape_screen_layout; @@ -49,6 +51,14 @@ jmethodID GetDisplayAlertMsg() { return s_display_alert_msg; } +jmethodID GetDisplayAlertPrompt() { + return s_display_alert_prompt; +} + +jmethodID GetAlertPromptButton() { + return s_alert_prompt_button; +} + jmethodID GetIsPortraitMode() { return s_is_portrait_mode; } @@ -85,6 +95,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg", "(Ljava/lang/String;Ljava/lang/String;Z)Z"); + s_display_alert_prompt = + env->GetStaticMethodID(s_native_library_class, "displayAlertPrompt", + "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;"); + s_alert_prompt_button = + env->GetStaticMethodID(s_native_library_class, "alertPromptButton", "()I"); s_is_portrait_mode = env->GetStaticMethodID(s_native_library_class, "isPortraitMode", "()Z"); s_landscape_screen_layout = env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I"); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index edb453d08..9d6bba545 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -11,6 +11,8 @@ namespace IDCache { JNIEnv* GetEnvForThread(); jclass GetNativeLibraryClass(); jmethodID GetDisplayAlertMsg(); +jmethodID GetDisplayAlertPrompt(); +jmethodID GetAlertPromptButton(); jmethodID GetIsPortraitMode(); jmethodID GetLandscapeScreenLayout(); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 92731ea5c..3bd2d4940 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1,3 +1,7 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + #include #include #include @@ -53,6 +57,58 @@ std::condition_variable running_cv; } // Anonymous namespace +static std::string GetJString(JNIEnv* env, jstring jstr) { + if (!jstr) { + return {}; + } + + const char* s = env->GetStringUTFChars(jstr, nullptr); + std::string result = s; + env->ReleaseStringUTFChars(jstr, s); + return result; +} + +static bool DisplayAlertMessage(const char* caption, const char* text, bool yes_no) { + JNIEnv* env = IDCache::GetEnvForThread(); + + // Execute the Java method. + jboolean result = env->CallStaticBooleanMethod( + IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(), env->NewStringUTF(caption), + env->NewStringUTF(text), yes_no ? JNI_TRUE : JNI_FALSE); + + return result != JNI_FALSE; +} + +static std::string DisplayAlertPrompt(const char* caption, const char* text, int buttonConfig) { + JNIEnv* env = IDCache::GetEnvForThread(); + + jstring value = reinterpret_cast(env->CallStaticObjectMethod( + IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertPrompt(), + env->NewStringUTF(caption), env->NewStringUTF(text), buttonConfig)); + + return GetJString(env, value); +} + +static int AlertPromptButton() { + JNIEnv* env = IDCache::GetEnvForThread(); + + // Execute the Java method. + return static_cast(env->CallStaticIntMethod(IDCache::GetNativeLibraryClass(), + IDCache::GetAlertPromptButton())); +} + +class AndroidKeyboard final : public Frontend::SoftwareKeyboard { +public: + void Execute(const Frontend::KeyboardConfig& config) override { + SoftwareKeyboard::Execute(config); + Finalize(DisplayAlertPrompt("Enter text", config.hint_text.c_str(), + static_cast(this->config.button_config)), + AlertPromptButton()); + } + + void ShowError(const std::string& error) override {} +}; + static int RunCitra(const std::string& filepath) { // Citra core only supports a single running instance std::lock_guard lock(running_mutex); @@ -72,7 +128,7 @@ static int RunCitra(const std::string& filepath) { // Register frontend applets Frontend::RegisterDefaultApplets(); - system.RegisterSoftwareKeyboard(std::make_shared(env)); + system.RegisterSoftwareKeyboard(std::make_shared()); { // Forces a config reload on game boot, if the user changed settings in the UI @@ -139,17 +195,6 @@ static int RunCitra(const std::string& filepath) { return {}; } -static std::string GetJString(JNIEnv* env, jstring jstr) { - if (!jstr) { - return {}; - } - - const char* s = env->GetStringUTFChars(jstr, nullptr); - std::string result = s; - env->ReleaseStringUTFChars(jstr, s); - return result; -} - void Java_org_citra_citra_1android_NativeLibrary_SurfaceChanged(JNIEnv* env, jobject obj, jobject surf) { s_surf = ANativeWindow_fromSurface(env, surf); @@ -250,7 +295,8 @@ jintArray Java_org_citra_citra_1android_NativeLibrary_GetBanner(JNIEnv* env, job } jintArray Banner = env->NewIntArray(icon_data.size()); - env->SetIntArrayRegion(Banner, 0, env->GetArrayLength(Banner), reinterpret_cast(icon_data.data())); + env->SetIntArrayRegion(Banner, 0, env->GetArrayLength(Banner), + reinterpret_cast(icon_data.data())); return Banner; } diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index bc2130a40..64929da8f 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -1,9 +1,8 @@ -// Copyright 2013 Dolphin Emulator Project +// Copyright 2019 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -// Initialise and run the emulator -static int RunCitra(const std::string& path); +#pragma once // Function calls from the Java side #ifdef __cplusplus diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index 0798c90a9..ae8cd232a 100644 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml @@ -8,4 +8,6 @@ 4dp 12dp 16dp + + 20dp