From 991eb6ae832a729facb01aa26f8e2c20b815c0e9 Mon Sep 17 00:00:00 2001 From: Ryan Meredith Date: Fri, 25 Sep 2020 11:50:04 -0400 Subject: [PATCH 1/2] Android: Use DialogFragment for AlertMessage --- .../dolphinemu/dolphinemu/NativeLibrary.java | 100 ++++++++---------- .../activities/EmulationActivity.java | 3 +- .../dolphinemu/dialogs/AlertMessage.java | 81 ++++++++++++++ .../fragments/EmulationFragment.java | 7 +- Source/Android/jni/MainAndroid.cpp | 6 ++ Source/Core/Core/Core.cpp | 7 +- Source/Core/Core/Core.h | 1 + 7 files changed, 142 insertions(+), 63 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 757aa74dfc..0e15304f19 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -8,10 +8,10 @@ package org.dolphinemu.dolphinemu; import android.util.DisplayMetrics; import android.view.Surface; - -import androidx.appcompat.app.AlertDialog; +import android.widget.Toast; import org.dolphinemu.dolphinemu.activities.EmulationActivity; +import org.dolphinemu.dolphinemu.dialogs.AlertMessage; import org.dolphinemu.dolphinemu.utils.CompressCallback; import org.dolphinemu.dolphinemu.utils.Log; import org.dolphinemu.dolphinemu.utils.Rumble; @@ -25,6 +25,9 @@ import java.util.LinkedHashMap; */ public final class NativeLibrary { + private static final Object sAlertMessageLock = new Object(); + private static boolean sIsShowingAlertMessage = false; + private static WeakReference sEmulationActivity = new WeakReference<>(null); /** @@ -393,6 +396,8 @@ public final class NativeLibrary */ public static native void StopEmulation(); + public static native boolean IsBooting(); + public static native void WaitUntilDoneBooting(); /** @@ -440,8 +445,6 @@ public final class NativeLibrary public static native void SetObscuredPixelsTop(int height); - private static boolean alertResult = false; - public static boolean displayAlertMsg(final String caption, final String text, final boolean yesNo) { @@ -454,74 +457,57 @@ public final class NativeLibrary } else { - // Create object used for waiting. - final Object lock = new Object(); - AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity, - R.style.DolphinDialogBase) - .setTitle(caption) - .setMessage(text); - - // If not yes/no dialog just have one button that dismisses modal, - // otherwise have a yes and no button that sets alertResult accordingly. - if (!yesNo) + // AlertMessages while the core is booting will deadlock when WaitUntilDoneBooting is called. + // Report the AlertMessage text as a toast instead. + if (IsBooting()) { - builder - .setCancelable(false) - .setPositiveButton("OK", (dialog, whichButton) -> - { - dialog.dismiss(); - synchronized (lock) - { - lock.notify(); - } - }); + emulationActivity.runOnUiThread( + () -> Toast.makeText(emulationActivity.getApplicationContext(), text, + Toast.LENGTH_LONG) + .show()); } else { - alertResult = false; + sIsShowingAlertMessage = true; - builder - .setPositiveButton("Yes", (dialog, whichButton) -> - { - alertResult = true; - dialog.dismiss(); - synchronized (lock) - { - lock.notify(); - } - }) - .setNegativeButton("No", (dialog, whichButton) -> - { - alertResult = false; - dialog.dismiss(); - synchronized (lock) - { - lock.notify(); - } - }); - } + emulationActivity.runOnUiThread(() -> AlertMessage.newInstance(caption, text, yesNo) + .show(emulationActivity.getSupportFragmentManager(), "AlertMessage")); - // Show the AlertDialog on the main thread. - emulationActivity.runOnUiThread(builder::show); - - // Wait for the lock to notify that it is complete. - synchronized (lock) - { - try + // Wait for the lock to notify that it is complete. + synchronized (sAlertMessageLock) { - lock.wait(); + try + { + sAlertMessageLock.wait(); + } + catch (Exception ignored) + { + } } - catch (Exception ignored) + + if (yesNo) { + result = AlertMessage.getAlertResult(); } } - - if (yesNo) - result = alertResult; } + sIsShowingAlertMessage = false; return result; } + public static boolean IsShowingAlertMessage() + { + return sIsShowingAlertMessage; + } + + public static void NotifyAlertMessageLock() + { + synchronized (sAlertMessageLock) + { + sAlertMessageLock.notify(); + } + } + public static void setEmulationActivity(EmulationActivity emulationActivity) { Log.verbose("[NativeLibrary] Registering EmulationActivity."); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index d3ab87cf00..8497e9fe5a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -274,6 +274,7 @@ public final class EmulationActivity extends AppCompatActivity mPlatform = gameToEmulate.getIntExtra(EXTRA_PLATFORM, 0); sUserPausedEmulation = gameToEmulate.getBooleanExtra(EXTRA_USER_PAUSED_EMULATION, false); activityRecreated = false; + Toast.makeText(this, R.string.emulation_menu_help, Toast.LENGTH_LONG).show(); } else { @@ -297,8 +298,6 @@ public final class EmulationActivity extends AppCompatActivity // Set these options now so that the SurfaceView the game renders into is the right size. enableFullscreenImmersive(); - Toast.makeText(this, getString(R.string.emulation_menu_help), Toast.LENGTH_LONG).show(); - Rumble.initRumble(this); setContentView(R.layout.activity_emulation); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java new file mode 100644 index 0000000000..9aa9b8030b --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java @@ -0,0 +1,81 @@ +package org.dolphinemu.dolphinemu.dialogs; + +import android.app.Dialog; +import android.os.Bundle; + +import org.dolphinemu.dolphinemu.NativeLibrary; +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.activities.EmulationActivity; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +public final class AlertMessage extends DialogFragment +{ + private static boolean sAlertResult = false; + private static final String ARG_TITLE = "title"; + private static final String ARG_MESSAGE = "message"; + private static final String ARG_YES_NO = "yesNo"; + + public static AlertMessage newInstance(String title, String message, boolean yesNo) + { + AlertMessage fragment = new AlertMessage(); + + Bundle args = new Bundle(); + args.putString(ARG_TITLE, title); + args.putString(ARG_MESSAGE, message); + args.putBoolean(ARG_YES_NO, yesNo); + fragment.setArguments(args); + + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + final EmulationActivity emulationActivity = NativeLibrary.getEmulationActivity(); + String title = requireArguments().getString(ARG_TITLE); + String message = requireArguments().getString(ARG_MESSAGE); + boolean yesNo = requireArguments().getBoolean(ARG_YES_NO); + setCancelable(false); + + AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity, + R.style.DolphinDialogBase) + .setTitle(title) + .setMessage(message); + + // If not yes/no dialog just have one button that dismisses modal, + // otherwise have a yes and no button that sets sAlertResult accordingly. + if (!yesNo) + { + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> + { + dialog.dismiss(); + NativeLibrary.NotifyAlertMessageLock(); + }); + } + else + { + builder.setPositiveButton(android.R.string.yes, (dialog, which) -> + { + sAlertResult = true; + dialog.dismiss(); + NativeLibrary.NotifyAlertMessageLock(); + }) + .setNegativeButton(android.R.string.no, (dialog, which) -> + { + sAlertResult = false; + dialog.dismiss(); + NativeLibrary.NotifyAlertMessageLock(); + }); + } + return builder.create(); + } + + public static boolean getAlertResult() + { + return sAlertResult; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java index bba6ee64ad..bc75f2a123 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -112,7 +112,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C @Override public void onPause() { - if (mEmulationState.isRunning()) + if (mEmulationState.isRunning() && !NativeLibrary.IsShowingAlertMessage()) mEmulationState.pause(); super.onPause(); } @@ -323,7 +323,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mSurface = null; Log.debug("[EmulationFragment] Surface destroyed."); - if (state != State.STOPPED) + if (state != State.STOPPED && !NativeLibrary.IsShowingAlertMessage()) { // In order to avoid dereferencing nullptr, we must not destroy the surface while booting // the core, so wait here if necessary. An easy (but not 100% consistent) way to reach @@ -362,7 +362,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C else if (state == State.PAUSED) { NativeLibrary.SurfaceChanged(mSurface); - if (!EmulationActivity.getHasUserPausedEmulation()) + if (!EmulationActivity.getHasUserPausedEmulation() && + !NativeLibrary.IsShowingAlertMessage()) { Log.debug("[EmulationFragment] Resuming emulation."); NativeLibrary.UnPauseEmulation(); diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index c845b9847e..928892b923 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -300,6 +300,12 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulatio s_emulation_end_event.Wait(); } +JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsBooting(JNIEnv* env, + jobject obj) +{ + return static_cast(Core::IsBooting()); +} + JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WaitUntilDoneBooting(JNIEnv* env, jobject obj) { diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index e92c01ab08..5977e78a91 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -170,6 +170,11 @@ void DisplayMessage(std::string message, int time_in_ms) OSD::AddMessage(std::move(message), time_in_ms); } +bool IsBooting() +{ + return s_is_booting.IsSet() || !s_hardware_initialized; +} + bool IsRunning() { return (GetState() != State::Uninitialized || s_hardware_initialized) && !s_is_stopping; @@ -674,7 +679,7 @@ State GetState() void WaitUntilDoneBooting() { - if (s_is_booting.IsSet() || !s_hardware_initialized) + if (IsBooting()) s_done_booting.Wait(); } diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index 54dfb24644..8f220b1a12 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -99,6 +99,7 @@ void UndeclareAsCPUThread(); std::string StopMessage(bool main_thread, std::string_view message); +bool IsBooting(); bool IsRunning(); bool IsRunningAndStarted(); // is running and the CPU loop has been entered bool IsRunningInCurrentThread(); // this tells us whether we are running in the CPU thread. From c3f34ac3fa1c05416e1d7e2d88876e523dafe265 Mon Sep 17 00:00:00 2001 From: Ryan Meredith Date: Fri, 25 Sep 2020 11:50:59 -0400 Subject: [PATCH 2/2] Android: Add "Ignore for this session" to Warning AlertMessages --- .../dolphinemu/dolphinemu/NativeLibrary.java | 11 ++++++++--- .../activities/EmulationActivity.java | 15 +++++++++++++++ .../dolphinemu/dialogs/AlertMessage.java | 17 ++++++++++++++++- .../Android/app/src/main/res/values/strings.xml | 1 + Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- Source/Android/jni/MainAndroid.cpp | 4 ++-- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 0e15304f19..44c1ab4f15 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -446,7 +446,7 @@ public final class NativeLibrary public static native void SetObscuredPixelsTop(int height); public static boolean displayAlertMsg(final String caption, final String text, - final boolean yesNo) + final boolean yesNo, final boolean isWarning) { Log.error("[NativeLibrary] Alert: " + text); final EmulationActivity emulationActivity = sEmulationActivity.get(); @@ -455,6 +455,10 @@ public final class NativeLibrary { Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic alert."); } + else if (emulationActivity.isIgnoringWarnings() && isWarning) + { + return true; + } else { // AlertMessages while the core is booting will deadlock when WaitUntilDoneBooting is called. @@ -470,8 +474,9 @@ public final class NativeLibrary { sIsShowingAlertMessage = true; - emulationActivity.runOnUiThread(() -> AlertMessage.newInstance(caption, text, yesNo) - .show(emulationActivity.getSupportFragmentManager(), "AlertMessage")); + emulationActivity.runOnUiThread( + () -> AlertMessage.newInstance(caption, text, yesNo, isWarning) + .show(emulationActivity.getSupportFragmentManager(), "AlertMessage")); // Wait for the lock to notify that it is complete. synchronized (sAlertMessageLock) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index 8497e9fe5a..17b420c2c4 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -85,12 +85,14 @@ public final class EmulationActivity extends AppCompatActivity private String mSelectedGameId; private int mPlatform; private String[] mPaths; + private boolean mIgnoreWarnings; private static boolean sUserPausedEmulation; public static final String EXTRA_SELECTED_GAMES = "SelectedGames"; public static final String EXTRA_SELECTED_TITLE = "SelectedTitle"; public static final String EXTRA_SELECTED_GAMEID = "SelectedGameId"; public static final String EXTRA_PLATFORM = "Platform"; + public static final String EXTRA_IGNORE_WARNINGS = "IgnoreWarnings"; public static final String EXTRA_USER_PAUSED_EMULATION = "sUserPausedEmulation"; @Retention(SOURCE) @@ -272,6 +274,7 @@ public final class EmulationActivity extends AppCompatActivity mSelectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE); mSelectedGameId = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAMEID); mPlatform = gameToEmulate.getIntExtra(EXTRA_PLATFORM, 0); + mIgnoreWarnings = gameToEmulate.getBooleanExtra(EXTRA_IGNORE_WARNINGS, false); sUserPausedEmulation = gameToEmulate.getBooleanExtra(EXTRA_USER_PAUSED_EMULATION, false); activityRecreated = false; Toast.makeText(this, R.string.emulation_menu_help, Toast.LENGTH_LONG).show(); @@ -327,6 +330,7 @@ public final class EmulationActivity extends AppCompatActivity outState.putString(EXTRA_SELECTED_TITLE, mSelectedTitle); outState.putString(EXTRA_SELECTED_GAMEID, mSelectedGameId); outState.putInt(EXTRA_PLATFORM, mPlatform); + outState.putBoolean(EXTRA_USER_PAUSED_EMULATION, mIgnoreWarnings); outState.putBoolean(EXTRA_USER_PAUSED_EMULATION, sUserPausedEmulation); super.onSaveInstanceState(outState); } @@ -337,6 +341,7 @@ public final class EmulationActivity extends AppCompatActivity mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE); mSelectedGameId = savedInstanceState.getString(EXTRA_SELECTED_GAMEID); mPlatform = savedInstanceState.getInt(EXTRA_PLATFORM); + mIgnoreWarnings = savedInstanceState.getBoolean(EXTRA_IGNORE_WARNINGS); sUserPausedEmulation = savedInstanceState.getBoolean(EXTRA_USER_PAUSED_EMULATION); } @@ -671,6 +676,16 @@ public final class EmulationActivity extends AppCompatActivity } } + public boolean isIgnoringWarnings() + { + return mIgnoreWarnings; + } + + public void setIgnoreWarnings(boolean value) + { + mIgnoreWarnings = value; + } + public static boolean getHasUserPausedEmulation() { return sUserPausedEmulation; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java index 9aa9b8030b..9832b770c6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/AlertMessage.java @@ -17,8 +17,10 @@ public final class AlertMessage extends DialogFragment private static final String ARG_TITLE = "title"; private static final String ARG_MESSAGE = "message"; private static final String ARG_YES_NO = "yesNo"; + private static final String ARG_IS_WARNING = "isWarning"; - public static AlertMessage newInstance(String title, String message, boolean yesNo) + public static AlertMessage newInstance(String title, String message, boolean yesNo, + boolean isWarning) { AlertMessage fragment = new AlertMessage(); @@ -26,6 +28,7 @@ public final class AlertMessage extends DialogFragment args.putString(ARG_TITLE, title); args.putString(ARG_MESSAGE, message); args.putBoolean(ARG_YES_NO, yesNo); + args.putBoolean(ARG_IS_WARNING, isWarning); fragment.setArguments(args); return fragment; @@ -39,6 +42,7 @@ public final class AlertMessage extends DialogFragment String title = requireArguments().getString(ARG_TITLE); String message = requireArguments().getString(ARG_MESSAGE); boolean yesNo = requireArguments().getBoolean(ARG_YES_NO); + boolean isWarning = requireArguments().getBoolean(ARG_IS_WARNING); setCancelable(false); AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity, @@ -71,6 +75,17 @@ public final class AlertMessage extends DialogFragment NativeLibrary.NotifyAlertMessageLock(); }); } + + if (isWarning) + { + builder.setNeutralButton(R.string.ignore_warning_alert_messages, (dialog, which) -> + { + emulationActivity.setIgnoreWarnings(true); + dialog.dismiss(); + NativeLibrary.NotifyAlertMessageLock(); + }); + } + return builder.create(); } diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index ce43f7e576..6e92780e04 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -438,5 +438,6 @@ It can efficiently compress both junk data and encrypted Wii data. %1$d%2$s Disc %1$d GameCube Controller 1 is set to \"None\" + Ignore for this session diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index b4052c42bf..308fda1f04 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -210,7 +210,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass native_library_class = env->FindClass("org/dolphinemu/dolphinemu/NativeLibrary"); 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"); + "(Ljava/lang/String;Ljava/lang/String;ZZ)Z"); s_do_rumble = env->GetStaticMethodID(s_native_library_class, "rumble", "(ID)V"); s_get_update_touch_pointer = env->GetStaticMethodID(s_native_library_class, "updateTouchPointer", "()V"); diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 928892b923..3904647a12 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -151,14 +151,14 @@ void Host_TitleChanged() { } -static bool MsgAlert(const char* caption, const char* text, bool yes_no, Common::MsgType /*style*/) +static bool MsgAlert(const char* caption, const char* text, bool yes_no, Common::MsgType style) { JNIEnv* env = IDCache::GetEnvForThread(); // Execute the Java method. jboolean result = env->CallStaticBooleanMethod( IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(), ToJString(env, caption), - ToJString(env, text), yes_no ? JNI_TRUE : JNI_FALSE); + ToJString(env, text), yes_no ? JNI_TRUE : JNI_FALSE, style == Common::MsgType::Warning); return result != JNI_FALSE; }