android: Handle core errors

The errors are handled in a similar manner to the Qt frontend: an AlertDialog
will pop up, prompting the user to select 'Abort' or 'Continue'.

Error messages are translatable as string values.
This commit is contained in:
zhupengfei 2020-08-14 23:25:26 +08:00 committed by bunnei
parent cf84342e35
commit c0942a59c8
5 changed files with 143 additions and 1 deletions

View File

@ -211,6 +211,84 @@ public final class NativeLibrary {
*/
public static native void SwapScreens(boolean swap_screens, int rotation);
public enum CoreError {
ErrorSystemFiles,
ErrorSavestate,
ErrorUnknown,
}
private static boolean coreErrorAlertResult = false;
/**
* Handles a core error.
* @return true: continue; false: abort
*/
public static boolean OnCoreError(CoreError error, String details) {
final EmulationActivity emulationActivity = sEmulationActivity.get();
if (emulationActivity == null) {
Log.error("[NativeLibrary] EmulationActivity not present");
return false;
}
String title, message;
switch (error) {
case ErrorSystemFiles: {
title = emulationActivity.getString(R.string.system_archive_not_found);
message = emulationActivity.getString(R.string.system_archive_not_found_message, details.isEmpty() ? emulationActivity.getString(R.string.system_archive_general) : details);
break;
}
case ErrorSavestate: {
title = emulationActivity.getString(R.string.save_load_error);
message = details;
break;
}
case ErrorUnknown: {
title = emulationActivity.getString(R.string.fatal_error);
message = emulationActivity.getString(R.string.fatal_error_message);
break;
}
default: {
return true;
}
}
// Create object used for waiting.
final Object lock = new Object();
final AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity)
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.continue_button, (dialog, which) -> {
coreErrorAlertResult = true;
synchronized (lock) {
lock.notify();
}
})
.setNegativeButton(R.string.abort_button, (dialog, which) -> {
coreErrorAlertResult = false;
synchronized (lock) {
lock.notify();
}
}).setOnDismissListener(dialog -> {
coreErrorAlertResult = true;
synchronized (lock) {
lock.notify();
}
});
// Show the AlertDialog on the main thread.
emulationActivity.runOnUiThread(builder::show);
// Wait for the lock to notify that it is complete.
synchronized (lock) {
try {
lock.wait();
} catch (Exception ignored) {
}
}
return coreErrorAlertResult;
}
public static boolean isPortraitMode() {
return CitraApplication.getAppContext().getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_PORTRAIT;

View File

@ -19,7 +19,9 @@ static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
static JavaVM* s_java_vm;
static jclass s_native_library_class;
static jclass s_core_error_class;
static jclass s_savestate_info_class;
static jmethodID s_on_core_error;
static jmethodID s_display_alert_msg;
static jmethodID s_display_alert_prompt;
static jmethodID s_alert_prompt_button;
@ -54,10 +56,18 @@ jclass GetNativeLibraryClass() {
return s_native_library_class;
}
jclass GetCoreErrorClass() {
return s_core_error_class;
}
jclass GetSavestateInfoClass() {
return s_savestate_info_class;
}
jmethodID GetOnCoreError() {
return s_on_core_error;
}
jmethodID GetDisplayAlertMsg() {
return s_display_alert_msg;
}
@ -118,6 +128,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
s_savestate_info_class = reinterpret_cast<jclass>(
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo")));
s_core_error_class = reinterpret_cast<jclass>(
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$CoreError")));
s_on_core_error = env->GetStaticMethodID(
s_native_library_class, "OnCoreError",
"(Lorg/citra/citra_emu/NativeLibrary$CoreError;Ljava/lang/String;)Z");
s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg",
"(Ljava/lang/String;Ljava/lang/String;Z)Z");
s_display_alert_prompt =
@ -150,6 +165,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_native_library_class);
env->DeleteGlobalRef(s_savestate_info_class);
env->DeleteGlobalRef(s_core_error_class);
MiiSelector::CleanupJNI(env);
SoftwareKeyboard::CleanupJNI(env);
Camera::StillImage::CleanupJNI(env);

View File

@ -12,7 +12,9 @@ namespace IDCache {
JNIEnv* GetEnvForThread();
jclass GetNativeLibraryClass();
jclass GetCoreErrorClass();
jclass GetSavestateInfoClass();
jmethodID GetOnCoreError();
jmethodID GetDisplayAlertMsg();
jmethodID GetDisplayAlertPrompt();
jmethodID GetAlertPromptButton();

View File

@ -96,6 +96,29 @@ static int AlertPromptButton() {
IDCache::GetAlertPromptButton()));
}
static jobject ToJavaCoreError(Core::System::ResultStatus result) {
static const std::map<Core::System::ResultStatus, const char*> CoreErrorNameMap{
{Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"},
{Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"},
{Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"},
};
const auto name = CoreErrorNameMap.count(result) ? CoreErrorNameMap.at(result) : "ErrorUnknown";
JNIEnv* env = IDCache::GetEnvForThread();
const jclass core_error_class = IDCache::GetCoreErrorClass();
return env->GetStaticObjectField(
core_error_class, env->GetStaticFieldID(core_error_class, name,
"Lorg/citra/citra_emu/NativeLibrary$CoreError;"));
}
static bool HandleCoreError(Core::System::ResultStatus result, const std::string& details) {
JNIEnv* env = IDCache::GetEnvForThread();
return env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnCoreError(),
ToJavaCoreError(result),
env->NewStringUTF(details.c_str())) != JNI_FALSE;
}
static Camera::NDK::Factory* g_ndk_factory{};
static void TryShutdown() {
@ -187,7 +210,20 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
// Start running emulation
while (is_running) {
if (!pause_emulation) {
system.RunLoop();
const auto result = system.RunLoop();
if (result == Core::System::ResultStatus::Success) {
continue;
}
if (result == Core::System::ResultStatus::ShutdownRequested) {
return result; // This also exits the emulation activity
} else {
InputManager::NDKMotionHandler()->DisableSensors();
if (!HandleCoreError(result, system.GetStatusDetails())) {
// Frontend requests us to abort
return result;
}
InputManager::NDKMotionHandler()->EnableSensors();
}
} else {
// Ensure no audio bleeds out while game is paused
const float volume = Settings::values.volume;

View File

@ -201,4 +201,14 @@
<!-- Microphone -->
<string name="microphone">Microphone</string>
<string name="microphone_permission_needed">Citra needs to access your microphone to emulate the 3DS\'s microphone.\n\nAlternatively, you can also change \"Audio Input Device\" in Audio Settings.</string>
<!-- Core Errors -->
<string name="abort_button">Abort</string>
<string name="continue_button">Continue</string>
<string name="system_archive_not_found">System Archive Not Found</string>
<string name="system_archive_not_found_message">%s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs.</string>
<string name="system_archive_general">A system archive</string>
<string name="save_load_error">Save/Load Error</string>
<string name="fatal_error">Fatal Error</string>
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
</resources>