android: frontend: Implement basic software keyboard applet.

This commit is contained in:
bunnei 2019-08-30 21:24:48 -04:00 committed by xperia64
parent 8fddac5da8
commit 8106d85742
7 changed files with 174 additions and 16 deletions

View File

@ -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<EmulationActivity> 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);

View File

@ -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

View File

@ -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<jclass>(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");

View File

@ -11,6 +11,8 @@ namespace IDCache {
JNIEnv* GetEnvForThread();
jclass GetNativeLibraryClass();
jmethodID GetDisplayAlertMsg();
jmethodID GetDisplayAlertPrompt();
jmethodID GetAlertPromptButton();
jmethodID GetIsPortraitMode();
jmethodID GetLandscapeScreenLayout();

View File

@ -1,3 +1,7 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <iostream>
#include <memory>
#include <regex>
@ -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<jstring>(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<int>(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<int>(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<std::mutex> lock(running_mutex);
@ -72,7 +128,7 @@ static int RunCitra(const std::string& filepath) {
// Register frontend applets
Frontend::RegisterDefaultApplets();
system.RegisterSoftwareKeyboard(std::make_shared<AndroidKeyboard>(env));
system.RegisterSoftwareKeyboard(std::make_shared<AndroidKeyboard>());
{
// 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<jint*>(icon_data.data()));
env->SetIntArrayRegion(Banner, 0, env->GetArrayLength(Banner),
reinterpret_cast<jint*>(icon_data.data()));
return Banner;
}

View File

@ -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

View File

@ -8,4 +8,6 @@
<dimen name="spacing_small">4dp</dimen>
<dimen name="spacing_medlarge">12dp</dimen>
<dimen name="spacing_large">16dp</dimen>
<dimen name="dialog_margin">20dp</dimen>
</resources>